diff --git a/StoredCredentialForTesting b/StoredCredentialForTesting new file mode 100644 index 000000000000..cef2ff6b3bee Binary files /dev/null and b/StoredCredentialForTesting differ diff --git a/build.gradle b/build.gradle index c7f9e4781ed5..46eebd2308e8 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,11 @@ allprojects { monocleVersion = '1.8.0_20' nattyVersion = '0.12' reflectionsVersion = '0.9.10' + googleCalendarVersion = 'v3-rev220-1.22.0' + googleHttpVersion = '1.21.0' + googleOauthVersion = '1.21.0' + mockitoVersion = '1.+' + libDir = 'lib' } @@ -56,6 +61,10 @@ allprojects { compile "com.google.guava:guava:$guavaVersion" compile "com.joestelmach:natty:$nattyVersion" compile "org.reflections:reflections:$reflectionsVersion" + compile "com.google.apis:google-api-services-calendar:$googleCalendarVersion" + compile "com.google.http-client:google-http-client-jackson2:$googleHttpVersion" + compile "com.google.oauth-client:google-oauth-client-jetty:$googleOauthVersion" + compile "org.mockito:mockito-core:$mockitoVersion" testCompile "junit:junit:$junitVersion" testCompile "org.testfx:testfx-core:$testFxVersion" diff --git a/src/main/java/seedu/agendum/logic/commands/SyncCommand.java b/src/main/java/seedu/agendum/logic/commands/SyncCommand.java new file mode 100644 index 000000000000..aa1390458ae4 --- /dev/null +++ b/src/main/java/seedu/agendum/logic/commands/SyncCommand.java @@ -0,0 +1,60 @@ +package seedu.agendum.logic.commands; + +import seedu.agendum.commons.exceptions.IllegalValueException; + +public class SyncCommand extends Command { + // COMMAND_WORD, COMMAND_FORMAT, COMMAND_DESCRIPTION are for display in help window + public static final String COMMAND_WORD = "sync"; + private static final String COMMAND_FORMAT = "sync "; + private static final String COMMAND_DESCRIPTION = "Turn syncing on or off"; + private static final String MESSAGE_USAGE = COMMAND_WORD + "- " + + COMMAND_DESCRIPTION; + + public static final String SYNC_ON_MESSAGE = "Google Calendar Sync is on"; + public static final String SYNC_OFF_MESSAGE = "Google Calendar Sync is off"; + + public static final String MESSAGE_WRONG_OPTION = "Invalid option for sync."; + + private boolean syncOption; + + //@@author A0003878Y + /** + * Convenience constructor using name + * + * @throws IllegalValueException if any of the raw values are invalid + */ + public SyncCommand(String option) throws IllegalValueException { + + if (option.trim().equalsIgnoreCase("on")) { + syncOption = true; + } else if (option.trim().equalsIgnoreCase("off")) { + syncOption = false; + } else { + throw new IllegalValueException(MESSAGE_WRONG_OPTION); + } + } + + @Override + public CommandResult execute() { + if (syncOption) { + model.activateModelSyncing(); + return new CommandResult(SYNC_ON_MESSAGE); + } else { + model.deactivateModelSyncing(); + return new CommandResult(SYNC_OFF_MESSAGE); + } + } + + //@@author + public static String getName() { + return COMMAND_WORD; + } + + public static String getFormat() { + return COMMAND_FORMAT; + } + + public static String getDescription() { + return COMMAND_DESCRIPTION; + } +} diff --git a/src/main/java/seedu/agendum/logic/parser/Parser.java b/src/main/java/seedu/agendum/logic/parser/Parser.java index 4c4d77f97a0e..d402155232da 100644 --- a/src/main/java/seedu/agendum/logic/parser/Parser.java +++ b/src/main/java/seedu/agendum/logic/parser/Parser.java @@ -122,6 +122,10 @@ public Command parseCommand(String userInput) { case LoadCommand.COMMAND_WORD : return new LoadCommand(arguments); + case SyncCommand.COMMAND_WORD: { + return prepareSync(arguments); + } + default: //@@author A0003878Y Optional alternativeCommand = EditDistanceCalculator.closestCommandMatch(commandWord); @@ -413,7 +417,8 @@ private Set parseIndexes(String args) { } return taskIds; - } + } + //@@author /** @@ -435,4 +440,20 @@ private Command prepareFind(String args) { return new FindCommand(keywordSet); } + //@@author A0003878Y + /** + * Parses arugments in the context of the sync command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareSync(String args) { + try { + return new SyncCommand(args); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } + } + + //@@author } \ No newline at end of file diff --git a/src/main/java/seedu/agendum/model/Model.java b/src/main/java/seedu/agendum/model/Model.java index 1824e7e749ad..355e76c21821 100644 --- a/src/main/java/seedu/agendum/model/Model.java +++ b/src/main/java/seedu/agendum/model/Model.java @@ -66,4 +66,10 @@ void unmarkTasks(List targets) /** Updates the current todolist to the loaded data**/ void handleLoadDataCompleteEvent(LoadDataCompleteEvent event); + /** Turns on model syncing using to a thirds party syncing provider **/ + void activateModelSyncing(); + + /** Turns off model syncing **/ + void deactivateModelSyncing(); + } diff --git a/src/main/java/seedu/agendum/model/ModelManager.java b/src/main/java/seedu/agendum/model/ModelManager.java index d1adf514150f..c7d579eb81ed 100644 --- a/src/main/java/seedu/agendum/model/ModelManager.java +++ b/src/main/java/seedu/agendum/model/ModelManager.java @@ -22,6 +22,9 @@ import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; +import seedu.agendum.sync.Sync; +import seedu.agendum.sync.SyncManager; +import seedu.agendum.sync.SyncProviderGoogle; /** * Represents the in-memory model of the to do list data. @@ -35,6 +38,8 @@ public class ModelManager extends ComponentManager implements Model { private final FilteredList filteredTasks; private final SortedList sortedTasks; + private final SyncManager syncManager; + //@@author A0133367E /** * Signals that an operation to remove a list from the stack of previous lists would fail @@ -59,6 +64,8 @@ public ModelManager(ToDoList src, UserPrefs userPrefs) { sortedTasks = filteredTasks.sorted(); previousLists = new Stack(); backupCurrentToDoList(); + + syncManager = new SyncManager(new SyncProviderGoogle()); } public ModelManager() { @@ -71,6 +78,8 @@ public ModelManager(ReadOnlyToDoList initialData) { sortedTasks = filteredTasks.sorted(); previousLists = new Stack(); backupCurrentToDoList(); + + syncManager = new SyncManager(new SyncProviderGoogle()); } //@@author A0133367E @@ -111,6 +120,9 @@ private void indicateLoadDataRequest(String location) { public synchronized void deleteTasks(List targets) throws TaskNotFoundException { for (ReadOnlyTask target: targets) { toDoList.removeTask(target); + + // Delete tasks in sync manager + syncManager.deleteEvent((Task) target); } logger.fine("[MODEL] --- successfully deleted all specified targets from the to-do list"); @@ -126,6 +138,8 @@ public synchronized void addTask(Task task) throws UniqueTaskList.DuplicateTaskE backupCurrentToDoList(); updateFilteredListToShowAll(); indicateToDoListChanged(); + + syncManager.addNewEvent(task); } @Override @@ -137,6 +151,10 @@ public synchronized void updateTask(ReadOnlyTask target, Task updatedTask) backupCurrentToDoList(); updateFilteredListToShowAll(); indicateToDoListChanged(); + + // Delete old task and add new task + syncManager.deleteEvent((Task) target); + syncManager.addNewEvent(updatedTask); } @Override @@ -226,8 +244,28 @@ public synchronized void loadFromLocation(String location) { indicateChangeSaveLocation(location); indicateLoadDataRequest(location); } + //@@author A0003878Y + + //=========== Sync Methods =============================================================================== + + @Override + public void activateModelSyncing() { + if (syncManager.getSyncStatus() != Sync.SyncStatus.RUNNING) { + syncManager.startSyncing(); + + // Add all current events into sync provider + toDoList.getTasks().forEach(syncManager::addNewEvent); + } + } + + @Override + public void deactivateModelSyncing() { + if (syncManager.getSyncStatus() != Sync.SyncStatus.NOTRUNNING) { + syncManager.stopSyncing(); + } + } + //@@author - //=========== Filtered Task List Accessors =============================================================== @Override diff --git a/src/main/java/seedu/agendum/sync/Sync.java b/src/main/java/seedu/agendum/sync/Sync.java new file mode 100644 index 000000000000..8c945edb754a --- /dev/null +++ b/src/main/java/seedu/agendum/sync/Sync.java @@ -0,0 +1,30 @@ +package seedu.agendum.sync; + +import seedu.agendum.model.task.Task; + +//@@author A0003878Y +public interface Sync { + + /** Enum used to persist SyncManager status **/ + enum SyncStatus { + RUNNING, NOTRUNNING + } + + /** Retrieve sync manager sync status **/ + SyncStatus getSyncStatus(); + + /** Sets sync manager sync status **/ + void setSyncStatus(SyncStatus syncStatus); + + /** Turn on syncing **/ + void startSyncing(); + + /** Turn off syncing **/ + void stopSyncing(); + + /** Add Task to sync provider **/ + void addNewEvent(Task task); + + /** Remove task from sync provider **/ + void deleteEvent(Task task); +} diff --git a/src/main/java/seedu/agendum/sync/SyncManager.java b/src/main/java/seedu/agendum/sync/SyncManager.java new file mode 100644 index 000000000000..4c4b0bf8efaf --- /dev/null +++ b/src/main/java/seedu/agendum/sync/SyncManager.java @@ -0,0 +1,58 @@ +package seedu.agendum.sync; + +import seedu.agendum.commons.core.ComponentManager; +import seedu.agendum.commons.core.LogsCenter; +import seedu.agendum.model.task.Task; + +import java.util.logging.Logger; + +//@@author A0003878Y +public class SyncManager extends ComponentManager implements Sync { + private final Logger logger = LogsCenter.getLogger(SyncManager.class); + private SyncStatus syncStatus = SyncStatus.NOTRUNNING; + + private final SyncProvider syncProvider; + + public SyncManager(SyncProvider syncProvider) { + this.syncProvider = syncProvider; + this.syncProvider.setManager(this); + + syncProvider.startIfNeeded(); + } + + @Override + public SyncStatus getSyncStatus() { + return syncStatus; + } + + @Override + public void setSyncStatus(SyncStatus syncStatus) { + this.syncStatus = syncStatus; + } + + @Override + public void startSyncing() { + syncProvider.start(); + } + + @Override + public void stopSyncing() { + syncProvider.stop(); + } + + @Override + public void addNewEvent(Task task) { + if (syncStatus == SyncStatus.RUNNING) { + if (task.getStartDateTime().isPresent() && task.getEndDateTime().isPresent()) { + syncProvider.addNewEvent(task); + } + } + } + + @Override + public void deleteEvent(Task task) { + if (syncStatus == SyncStatus.RUNNING) { + syncProvider.deleteEvent(task); + } + } +} diff --git a/src/main/java/seedu/agendum/sync/SyncProvider.java b/src/main/java/seedu/agendum/sync/SyncProvider.java new file mode 100644 index 000000000000..1e20639b408b --- /dev/null +++ b/src/main/java/seedu/agendum/sync/SyncProvider.java @@ -0,0 +1,31 @@ +package seedu.agendum.sync; + +import seedu.agendum.model.task.Task; + +//@@author A0003878Y +public abstract class SyncProvider { + + /** Sync provider's keep a reference to the manager so that they can set it's + * sync status **/ + protected Sync syncManager; + + /** Start sync provider and perform initialization **/ + public abstract void start(); + + /** Start sync provider if it needs to be started **/ + public abstract void startIfNeeded(); + + /** Stop sync provider and perform cleanup **/ + public abstract void stop(); + + /** Add event into sync provider **/ + public abstract void addNewEvent(Task task); + + /** Delete event from sync provider **/ + public abstract void deleteEvent(Task task); + + /** Set sync provider's sync manager **/ + public void setManager(Sync syncManager) { + this.syncManager = syncManager; + } +} diff --git a/src/main/java/seedu/agendum/sync/SyncProviderGoogle.java b/src/main/java/seedu/agendum/sync/SyncProviderGoogle.java new file mode 100644 index 000000000000..edc773a288a4 --- /dev/null +++ b/src/main/java/seedu/agendum/sync/SyncProviderGoogle.java @@ -0,0 +1,239 @@ +package seedu.agendum.sync; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.DateTime; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.calendar.model.*; +import com.google.api.services.calendar.model.Calendar; +import seedu.agendum.commons.core.LogsCenter; +import seedu.agendum.model.task.Task; + +import java.io.File; +import java.io.IOException; +import java.time.ZoneId; +import java.util.Collections; +import java.util.Date; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +import static java.lang.Math.abs; +import static seedu.agendum.commons.core.Config.DEFAULT_DATA_DIR; + +//@@author A0003878Y +public class SyncProviderGoogle extends SyncProvider { + private final Logger logger = LogsCenter.getLogger(SyncProviderGoogle.class); + + private static final String CALENDAR_NAME = "Agendum Calendar"; + private static final File DATA_STORE_DIR = new File(DEFAULT_DATA_DIR); + private static final File DATA_STORE_CREDENTIAL = new File(DEFAULT_DATA_DIR + "StoredCredential"); + private static final String CLIENT_ID = "1011464737889-n9avi9id8fur78jh3kqqctp9lijphq2n.apps.googleusercontent.com"; + private static final String CLIENT_SECRET = "ea78y_rPz3G4kwIV3yAF99aG"; + private static FileDataStoreFactory dataStoreFactory; + private static HttpTransport httpTransport; + private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + private static com.google.api.services.calendar.Calendar client; + + private Calendar agendumCalendar; + + // These are blocking queues to ease the producer/consumer problem + private static final ArrayBlockingQueue addEventConcurrentQueue = new ArrayBlockingQueue(200); + private static final ArrayBlockingQueue deleteEventConcurrentQueue = new ArrayBlockingQueue(200); + + public SyncProviderGoogle() { + try { + httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR); + } catch (IOException var3) { + System.err.println(var3.getMessage()); + } catch (Throwable var4) { + var4.printStackTrace(); + } + } + + @Override + public void start() { + logger.info("Initializing Google Calendar Sync"); + try { + Credential t = authorize(); + client = (new com.google.api.services.calendar.Calendar.Builder(httpTransport, JSON_FACTORY, t)).setApplicationName("Agendum").build(); + agendumCalendar = getAgendumCalendar(); + + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + + // Process add & delete consumers into their own separate thread. + Executors.newSingleThreadExecutor().execute(() -> processAddEventQueue()); + Executors.newSingleThreadExecutor().execute(() -> processDeleteEventQueue()); + } catch (IOException var3) { + System.err.println(var3.getMessage()); + } catch (Throwable var4) { + var4.printStackTrace(); + } + } + + @Override + public void startIfNeeded() { + if (DATA_STORE_CREDENTIAL.exists()) { + start(); + } + } + + @Override + public void stop() { + logger.info("Stopping Google Calendar Sync"); + DATA_STORE_CREDENTIAL.delete(); + syncManager.setSyncStatus(Sync.SyncStatus.NOTRUNNING); + } + + @Override + public void addNewEvent(Task task) { + try { + addEventConcurrentQueue.put(task); + logger.info("Task added to GCal add queue"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void deleteEvent(Task task) { + try { + deleteEventConcurrentQueue.put(task); + logger.info("Task added to GCal delete queue"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * Authorize with Google Calendar + * @return Credentail + * @throws Exception + */ + private Credential authorize() throws Exception { + GoogleClientSecrets.Details details = new GoogleClientSecrets.Details(); + details.setClientId(CLIENT_ID); + details.setClientSecret(CLIENT_SECRET); + + GoogleClientSecrets clientSecrets = new GoogleClientSecrets().setInstalled(details); + + GoogleAuthorizationCodeFlow flow = (new GoogleAuthorizationCodeFlow.Builder(httpTransport, JSON_FACTORY, clientSecrets, Collections.singleton("https://www.googleapis.com/auth/calendar"))).setDataStoreFactory(dataStoreFactory).build(); + return (new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver())).authorize("user"); + } + + /** + * Returns a new "Agendum Calendar" in the authenticated user. + * If a calendar with the same name doesn't already exist, it creates one. + * @return + * @throws IOException + */ + private Calendar getAgendumCalendar() throws IOException { + CalendarList feed = client.calendarList().list().execute(); + logger.info("Searching for Agnendum Calendar"); + + for (CalendarListEntry entry : feed.getItems()) { + if (entry.getSummary().equals(CALENDAR_NAME)) { + logger.info(CALENDAR_NAME + " found"); + Calendar calendar = client.calendars().get(entry.getId()).execute(); + logger.info(calendar.toPrettyString()); + return calendar; + } + + } + + logger.info(CALENDAR_NAME + " not found, creating it"); + Calendar entry = new Calendar(); + entry.setSummary(CALENDAR_NAME); + Calendar calendar = client.calendars().insert(entry).execute(); + logger.info(calendar.toPrettyString()); + return calendar; + } + + /** + * Delete Agendum calendar in Google Calendar. + */ + public void deleteAgendumCalendar() { + try { + CalendarList feed = client.calendarList().list().execute(); + logger.info("Deleting Agendum calendar"); + + for (CalendarListEntry entry : feed.getItems()) { + if (entry.getSummary().equals(CALENDAR_NAME)) { + client.calendars().delete(entry.getId()).execute(); + } + + } + } catch (IOException e) + {e.printStackTrace(); + } + } + + /** + * A event loop that continuously processes the add event queue. + * + * `.take();` is a blocking call so it waits until there is something + * in the array before returning. + * + * This method should only be called on non-main thread. + */ + private void processAddEventQueue() { + while (true) { + try { + Task task = addEventConcurrentQueue.take(); + Date startDate = Date.from(task.getStartDateTime().get().atZone(ZoneId.systemDefault()).toInstant()); + Date endDate = Date.from(task.getEndDateTime().get().atZone(ZoneId.systemDefault()).toInstant()); + String id = Integer.toString(abs(task.hashCode())); + + EventDateTime startEventDateTime = new EventDateTime().setDateTime(new DateTime(startDate)); + EventDateTime endEventDateTime = new EventDateTime().setDateTime(new DateTime(endDate)); + + Event newEvent = new Event(); + newEvent.setSummary(String.valueOf(task.getName())); + newEvent.setStart(startEventDateTime); + newEvent.setEnd(endEventDateTime); + newEvent.setId(id); + + Event result = client.events().insert(agendumCalendar.getId(), newEvent).execute(); + logger.info(result.toPrettyString()); + + logger.info("Task processed from GCal add queue"); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * A event loop that continuously processes the delete event queue. + * + * `.take();` is a blocking call so it waits until there is something + * in the array before returning. + * + * This method should only be called on non-main thread. + */ + private void processDeleteEventQueue() { + while (true) { + try { + Task task = deleteEventConcurrentQueue.take(); + String id = Integer.toString(abs(task.hashCode())); + client.events().delete(agendumCalendar.getId(), id).execute(); + + logger.info("Task added to GCal delete queue"); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/test/java/seedu/agendum/logic/LogicManagerTest.java b/src/test/java/seedu/agendum/logic/LogicManagerTest.java index 7fa367635df5..067a81336186 100644 --- a/src/test/java/seedu/agendum/logic/LogicManagerTest.java +++ b/src/test/java/seedu/agendum/logic/LogicManagerTest.java @@ -11,6 +11,7 @@ import seedu.agendum.commons.core.Config; import seedu.agendum.commons.core.EventsCenter; import seedu.agendum.commons.core.UnmodifiableObservableList; +import seedu.agendum.commons.exceptions.IllegalValueException; import seedu.agendum.logic.commands.*; import seedu.agendum.commons.events.ui.ShowHelpRequestEvent; import seedu.agendum.commons.util.FileUtil; @@ -22,6 +23,7 @@ import seedu.agendum.model.ReadOnlyToDoList; import seedu.agendum.model.task.*; import seedu.agendum.storage.XmlToDoListStorage; +import seedu.agendum.sync.SyncProviderGoogleTests; import seedu.agendum.testutil.EventsCollector; import java.io.File; @@ -413,6 +415,24 @@ public void execute_delete_removesCorrectMultipleTasks() throws Exception { } //@author + @Test + public void execute_syncOn_successfull() throws Exception { + SyncProviderGoogleTests.copyTestCredentials(); + assertCommandBehavior("sync on", + SyncCommand.SYNC_ON_MESSAGE); + } + + @Test + public void execute_syncOff_successfull() throws Exception { + assertCommandBehavior("sync off", + SyncCommand.SYNC_OFF_MESSAGE); + } + + @Test + public void execute_syncUnknown_exception() throws Exception { + assertCommandBehavior("sync something", SyncCommand.MESSAGE_WRONG_OPTION, new ToDoList(), Collections.emptyList()); + } + //@@author A0148095X @Test diff --git a/src/test/java/seedu/agendum/sync/SyncManagerTests.java b/src/test/java/seedu/agendum/sync/SyncManagerTests.java new file mode 100644 index 000000000000..c1a08e49cbd5 --- /dev/null +++ b/src/test/java/seedu/agendum/sync/SyncManagerTests.java @@ -0,0 +1,137 @@ +package seedu.agendum.sync; + +import org.junit.Before; +import org.junit.Test; +import seedu.agendum.model.task.Task; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; + +import static org.mockito.Mockito.*; + +// @@author A0003878Y +public class SyncManagerTests { + private SyncManager syncManager; + private SyncProvider mockSyncProvider; + + @Before + public void setUp() { + mockSyncProvider = mock(SyncProvider.class); + syncManager = new SyncManager(mockSyncProvider); + } + + @Test + public void syncManager_setStatusRunning_expectRunning() { + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + assertEquals(syncManager.getSyncStatus(),Sync.SyncStatus.RUNNING); + } + + @Test + public void syncManager_setStatusNotRunning_expectNotRunning() { + syncManager.setSyncStatus(Sync.SyncStatus.NOTRUNNING); + assertEquals(syncManager.getSyncStatus(),Sync.SyncStatus.NOTRUNNING); + } + + @Test + public void syncManager_startSyncing_expectSyncProviderStart() { + syncManager.startSyncing(); + verify(mockSyncProvider).start(); + } + + @Test + public void syncManager_stopSyncing_expectSyncProviderStop() { + syncManager.stopSyncing(); + verify(mockSyncProvider).stop(); + } + + @Test + public void syncManager_addEventWithStartAndEndTime_expectSyncProviderAdd() { + Task mockTask = mock(Task.class); + Optional fakeTime = Optional.of(LocalDateTime.now()); + + when(mockTask.getStartDateTime()).thenReturn(fakeTime); + when(mockTask.getEndDateTime()).thenReturn(fakeTime); + + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + syncManager.addNewEvent(mockTask); + + verify(mockSyncProvider).addNewEvent(mockTask); + } + + @Test + public void syncManager_addEventWithStartTime_expectNoSyncProviderAdd() { + Task mockTask = mock(Task.class); + Optional fakeTime = Optional.of(LocalDateTime.now()); + Optional empty = Optional.empty(); + + when(mockTask.getStartDateTime()).thenReturn(empty); + when(mockTask.getEndDateTime()).thenReturn(fakeTime); + + + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + syncManager.addNewEvent(mockTask); + + verify(mockSyncProvider, never()).addNewEvent(mockTask); + } + + @Test + public void syncManager_addEventWithEndTime_expectNoSyncProviderAdd() { + Task mockTask = mock(Task.class); + Optional fakeTime = Optional.of(LocalDateTime.now()); + Optional empty = Optional.empty(); + + when(mockTask.getStartDateTime()).thenReturn(fakeTime); + when(mockTask.getEndDateTime()).thenReturn(empty); + + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + syncManager.addNewEvent(mockTask); + + verify(mockSyncProvider, never()).addNewEvent(mockTask); + } + + @Test + public void syncManager_addEventWithNoTime_expectNoSyncProviderAdd() { + Task mockTask = mock(Task.class); + Optional empty = Optional.empty(); + + when(mockTask.getStartDateTime()).thenReturn(empty); + when(mockTask.getEndDateTime()).thenReturn(empty); + + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + syncManager.addNewEvent(mockTask); + + verify(mockSyncProvider, never()).addNewEvent(mockTask); + } + + @Test + public void syncManager_addEventWithSyncManagerNotRunning_expectNoSyncProviderAdd() { + Task mockTask = mock(Task.class); + + syncManager.setSyncStatus(Sync.SyncStatus.NOTRUNNING); + syncManager.addNewEvent(mockTask); + + verify(mockSyncProvider, never()).addNewEvent(mockTask); + } + + @Test + public void syncManager_deleteEventWithSyncManagerRunning_expectSyncProviderDelete() { + Task mockTask = mock(Task.class); + + syncManager.setSyncStatus(Sync.SyncStatus.RUNNING); + syncManager.deleteEvent(mockTask); + + verify(mockSyncProvider).deleteEvent(mockTask); + } + + @Test + public void syncManager_deleteEventWithSyncManagerNotRunning_expectNoSyncProviderDelete() { + Task mockTask = mock(Task.class); + + syncManager.setSyncStatus(Sync.SyncStatus.NOTRUNNING); + syncManager.deleteEvent(mockTask); + + verify(mockSyncProvider, never()).deleteEvent(mockTask); + } +} diff --git a/src/test/java/seedu/agendum/sync/SyncProviderGoogleTests.java b/src/test/java/seedu/agendum/sync/SyncProviderGoogleTests.java new file mode 100644 index 000000000000..db8814527b16 --- /dev/null +++ b/src/test/java/seedu/agendum/sync/SyncProviderGoogleTests.java @@ -0,0 +1,109 @@ +package seedu.agendum.sync; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.internal.matchers.Any; +import seedu.agendum.model.task.Task; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.mockito.Mockito.*; +import static seedu.agendum.commons.core.Config.DEFAULT_DATA_DIR; + +// @@author A0003878Y +public class SyncProviderGoogleTests { + private static final File DATA_STORE_CREDENTIAL = new File(DEFAULT_DATA_DIR + "StoredCredential"); + private static final File DATA_STORE_CREDENTIAL_TEST = new File("StoredCredentialForTesting"); + + private SyncProviderGoogle syncProviderGoogle; + private SyncManager mockSyncManager; + + @Before + public void setUp() { + copyTestCredentials(); + + mockSyncManager = mock(SyncManager.class); + syncProviderGoogle = spy(new SyncProviderGoogle()); + syncProviderGoogle.setManager(mockSyncManager); + } + + public static void copyTestCredentials() { + try { + DATA_STORE_CREDENTIAL.delete(); + Files.copy(DATA_STORE_CREDENTIAL_TEST.toPath(), DATA_STORE_CREDENTIAL.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @After + public void tearDown() { + syncProviderGoogle.stop(); + } + + @Test + public void syncProviderGoogle_start_calendarAlreadyExists() { + syncProviderGoogle.deleteAgendumCalendar(); + syncProviderGoogle.start(); + + // Verify if Sync Manager's status was changed + verify(mockSyncManager).setSyncStatus(Sync.SyncStatus.RUNNING); + } + + @Test + public void syncProviderGoogle_start_createCalendar() { + syncProviderGoogle.start(); + + // Verify if Sync Manager's status was changed + verify(mockSyncManager).setSyncStatus(Sync.SyncStatus.RUNNING); + } + + @Test + public void syncProviderGoogle_startIfNeeded_credentialsFound() { + syncProviderGoogle.startIfNeeded(); + + // Verify Sync Provider did start + verify(syncProviderGoogle).start(); + } + + @Test + public void syncProviderGoogle_startIfNeeded_credentialsNotFound() { + DATA_STORE_CREDENTIAL.delete(); + syncProviderGoogle.startIfNeeded(); + + // Verify Sync Provider should not start + verify(syncProviderGoogle, never()).start(); + } + + @Test + public void syncProviderGoogle_addEvent_successful() { + syncProviderGoogle.start(); + + Task mockTask = mock(Task.class); + Optional fakeTime = Optional.of(LocalDateTime.now()); + + when(mockTask.getStartDateTime()).thenReturn(fakeTime); + when(mockTask.getEndDateTime()).thenReturn(fakeTime); + + syncProviderGoogle.addNewEvent(mockTask); + } + + @Test + public void syncProviderGoogle_deleteEvent_successful() { + syncProviderGoogle.start(); + + Task mockTask = mock(Task.class); + Optional fakeTime = Optional.of(LocalDateTime.now()); + + when(mockTask.getStartDateTime()).thenReturn(fakeTime); + when(mockTask.getEndDateTime()).thenReturn(fakeTime); + + syncProviderGoogle.deleteEvent(mockTask); + } + +}