import { inject , TestBed } from "@angular/core/testing" ; import { LastSyncDateTimeDao } from "../../dao/sync/last-sync-date-time.dao" ; import { SyncService } from "./sync.service" ; import { TEST_SYNCED_ACTIVITIES } from "../../../../shared-fixtures/activities-2015.fixture" ; import { SyncState } from "./sync-state.enum" ; import { SyncedBackupModel } from "./synced-backup.model" ; import { AthleteModel , AthleteSettingsModel , DatedAthleteSettingsModel } from "@elevate/shared/models" ; import { CoreModule } from "../../../core/core.module" ; import { SharedModule } from "../../shared.module" ; import * as _ from "lodash" ; describe ( "SyncService" , ( ) => { const tabId = 101 ; const installedVersion = "2.0.0" ; let athleteModel ; let syncService ; let lastSyncDateTimeDao ; beforeEach ( ( done ) => { TestBed . configureTestingModule ( { imports : [ CoreModule , SharedModule , ] } ) ; athleteModel = _ . cloneDeep ( AthleteModel . DEFAULT_MODEL ) ; syncService = TestBed . get ( SyncService ) ; lastSyncDateTimeDao = TestBed . get ( LastSyncDateTimeDao ) ; spyOn ( syncService , "getCurrentTab" ) . and . callFake ( ( callback ) => { const tab = { id : tabId } ; callback ( tab as chrome . tabs . Tab ) ; } ) ; spyOn ( syncService , "getAppVersion" ) . and . returnValue ( installedVersion ) ; spyOn ( window , "open" ) . and . stub ( ) ; done ( ) ; } ) ; it ( "should be created" , inject ( [ SyncService ] , ( service ) => { expect ( service ) . toBeTruthy ( ) ; } ) ) ; it ( "should get last sync date time" , ( done ) => { const expectedLastSyncDateTime = 666 ; spyOn ( lastSyncDateTimeDao , "fetch" ) . and . returnValue ( Promise . resolve ( expectedLastSyncDateTime ) ) ; const promise = syncService . getLastSyncDateTime ( ) ; promise . then ( ( lastSyncDateTime ) => { expect ( lastSyncDateTime ) . not . toBeNull ( ) ; expect ( lastSyncDateTime ) . toEqual ( expectedLastSyncDateTime ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should save last sync date time (for activities import)" , ( done ) => { const expectedLastSyncDateTime = 9999 ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( expectedLastSyncDateTime ) ) ; const promise = syncService . saveLastSyncTime ( expectedLastSyncDateTime ) ; promise . then ( ( lastSyncDateTime ) => { expect ( lastSyncDateTime ) . not . toBeNull ( ) ; expect ( lastSyncDateTime ) . toEqual ( expectedLastSyncDateTime ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should remove last sync date time (for activities clear)" , ( done ) => { const lastSyncDateDaoClearSpy = spyOn ( syncService . lastSyncDateTimeDao , "clear" ) ; lastSyncDateDaoClearSpy . and . returnValue ( Promise . resolve ( ) ) ; const promise = syncService . clearLastSyncTime ( ) ; promise . then ( ( ) => { expect ( lastSyncDateDaoClearSpy ) . toHaveBeenCalledTimes ( 1 ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } ) ; } ) ; it ( "should prepare export athlete activities" , ( done ) => { const lastSyncDateTime = 99 ; const expectedPeriodAthleteSettings = [ new DatedAthleteSettingsModel ( "2018-05-10" , new AthleteSettingsModel ( 200 , 50 , null , 190 , null , null , 75 ) ) , new DatedAthleteSettingsModel ( "2018-04-15" , new AthleteSettingsModel ( 195 , null , null , 150 , null , null , 76 ) ) , new DatedAthleteSettingsModel ( "2018-02-01" , new AthleteSettingsModel ( 190 , 65 , null , 110 , null , null , 78 ) ) , new DatedAthleteSettingsModel ( null , new AthleteSettingsModel ( 190 , 65 , null , 110 , null , null , 78 ) ) , ] ; athleteModel . datedAthleteSettings = expectedPeriodAthleteSettings ; spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( lastSyncDateTime ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( TEST_SYNCED_ACTIVITIES ) ) ; spyOn ( syncService . athleteService , "fetch" ) . and . returnValue ( Promise . resolve ( athleteModel ) ) ; const promise = syncService . prepareForExport ( ) ; promise . then ( ( syncedBackupModel ) => { expect ( syncedBackupModel ) . not . toBeNull ( ) ; expect ( syncedBackupModel . pluginVersion ) . toEqual ( installedVersion ) ; expect ( syncedBackupModel . lastSyncDateTime ) . toEqual ( lastSyncDateTime ) ; expect ( syncedBackupModel . syncedActivities ) . toEqual ( TEST_SYNCED_ACTIVITIES ) ; expect ( syncedBackupModel . athleteModel ) . toEqual ( athleteModel ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should export athlete activities" , ( done ) => { const lastSyncDateTime = 99 ; spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( lastSyncDateTime ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( TEST_SYNCED_ACTIVITIES ) ) ; spyOn ( syncService . athleteService , "fetch" ) . and . returnValue ( Promise . resolve ( [ ] ) ) ; const prepareForExportSpy = spyOn ( syncService , "prepareForExport" ) . and . callThrough ( ) ; const saveAsSpy = spyOn ( syncService , "saveAs" ) . and . stub ( ) ; const promise = syncService . export ( ) ; promise . then ( ( ) => { expect ( prepareForExportSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveAsSpy ) . toHaveBeenCalledTimes ( 1 ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should not export athlete activities without last sync date" , ( done ) => { spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( null ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( TEST_SYNCED_ACTIVITIES ) ) ; spyOn ( syncService . athleteService , "fetch" ) . and . returnValue ( Promise . resolve ( [ ] ) ) ; const prepareForExportSpy = spyOn ( syncService , "prepareForExport" ) . and . callThrough ( ) ; const saveAsSpy = spyOn ( syncService , "saveAs" ) . and . stub ( ) ; const promise = syncService . export ( ) ; promise . then ( ( ) => { expect ( prepareForExportSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveAsSpy ) . toHaveBeenCalledTimes ( 0 ) ; expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( "Cannot export. No last synchronization date found." ) ; done ( ) ; } ) ; } ) ; it ( "should import athlete activities with a 1.0.0 backup and 1.0.0 compatible backup version threshold (with datedAthleteSettings)" , ( done ) => { const lastSyncDateTime = 99 ; const importedBackupVersion = "1.0.0" ; const compatibleBackupVersionThreshold = "1.0.0" ; const datedAthleteSettingsModels = [ new DatedAthleteSettingsModel ( "2018-05-10" , new AthleteSettingsModel ( 200 , 50 , null , 190 , null , null , 75 ) ) , new DatedAthleteSettingsModel ( "2018-04-15" , new AthleteSettingsModel ( 195 , null , null , 150 , null , null , 76 ) ) , new DatedAthleteSettingsModel ( "2018-02-01" , new AthleteSettingsModel ( 190 , 65 , null , 110 , null , null , 78 ) ) , new DatedAthleteSettingsModel ( null , new AthleteSettingsModel ( 190 , 65 , null , 110 , null , null , 78 ) ) , ] ; athleteModel . datedAthleteSettings = datedAthleteSettingsModels ; spyOn ( syncService , "getCompatibleBackupVersionThreshold" ) . and . returnValue ( compatibleBackupVersionThreshold ) ; const importedSyncedBackupModel = { syncedActivities : TEST_SYNCED_ACTIVITIES , athleteModel : athleteModel , lastSyncDateTime : lastSyncDateTime , pluginVersion : importedBackupVersion } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . syncedActivities ) ) ; spyOn ( syncService . athleteService , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . athleteModel ) ) ; spyOn ( syncService . lastSyncDateTimeDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; spyOn ( syncService . activityDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; const spyClearSyncedData = spyOn ( syncService , "clearSyncedData" ) . and . callThrough ( ) ; const spyClearLocalStorage = spyOn ( syncService . userSettingsService , "clearLocalStorageOnNextLoad" ) . and . returnValue ( Promise . resolve ( ) ) ; const promise = syncService . import ( importedSyncedBackupModel ) ; promise . then ( ( syncedBackupModel ) => { expect ( spyClearSyncedData ) . toHaveBeenCalledTimes ( 1 ) ; expect ( spyClearLocalStorage ) . toHaveBeenCalledTimes ( 1 ) ; expect ( syncedBackupModel ) . not . toBeNull ( ) ; expect ( syncedBackupModel . pluginVersion ) . toEqual ( importedSyncedBackupModel . pluginVersion ) ; expect ( syncedBackupModel . lastSyncDateTime ) . toEqual ( importedSyncedBackupModel . lastSyncDateTime ) ; expect ( syncedBackupModel . syncedActivities ) . toEqual ( importedSyncedBackupModel . syncedActivities ) ; expect ( syncedBackupModel . athleteModel ) . toEqual ( importedSyncedBackupModel . athleteModel ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should import athlete activities with a 1.5.1 backup and 1.2.3 compatible backup version threshold (athleteModel empty)" , ( done ) => { const lastSyncDateTime = 99 ; const importedBackupVersion = "1.5.1" ; const compatibleBackupVersionThreshold = "1.2.3" ; spyOn ( syncService , "getCompatibleBackupVersionThreshold" ) . and . returnValue ( compatibleBackupVersionThreshold ) ; athleteModel = null ; const importedSyncedBackupModel = { syncedActivities : TEST_SYNCED_ACTIVITIES , athleteModel : athleteModel , lastSyncDateTime : lastSyncDateTime , pluginVersion : importedBackupVersion } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . syncedActivities ) ) ; spyOn ( syncService . lastSyncDateTimeDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; spyOn ( syncService . activityDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; const spyClearSyncedData = spyOn ( syncService , "clearSyncedData" ) . and . callThrough ( ) ; const spyResetDatedAthleteSettings = spyOn ( syncService . athleteService , "resetSettings" ) . and . stub ( ) ; const spySaveDatedAthleteSettings = spyOn ( syncService . athleteService , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . athleteModel ) ) ; const spyClearLocalStorage = spyOn ( syncService . userSettingsService , "clearLocalStorageOnNextLoad" ) . and . returnValue ( Promise . resolve ( ) ) ; const promise = syncService . import ( importedSyncedBackupModel ) ; promise . then ( ( syncedBackupModel ) => { expect ( spyResetDatedAthleteSettings ) . toHaveBeenCalledTimes ( 1 ) ; expect ( spySaveDatedAthleteSettings ) . not . toHaveBeenCalled ( ) ; expect ( spyClearSyncedData ) . toHaveBeenCalledTimes ( 1 ) ; expect ( spyClearLocalStorage ) . toHaveBeenCalledTimes ( 1 ) ; expect ( syncedBackupModel ) . not . toBeNull ( ) ; expect ( syncedBackupModel . pluginVersion ) . toEqual ( importedSyncedBackupModel . pluginVersion ) ; expect ( syncedBackupModel . lastSyncDateTime ) . toEqual ( importedSyncedBackupModel . lastSyncDateTime ) ; expect ( syncedBackupModel . syncedActivities ) . toEqual ( importedSyncedBackupModel . syncedActivities ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should not import athlete activities with a 1.4.7 backup and 1.5.0 compatible backup version threshold" , ( done ) => { const lastSyncDateTime = 99 ; const importedBackupVersion = "1.4.7" ; const compatibleBackupVersionThreshold = "1.5.0" ; spyOn ( syncService , "getCompatibleBackupVersionThreshold" ) . and . returnValue ( compatibleBackupVersionThreshold ) ; const expectedErrorMessage = "Imported backup version " + importedBackupVersion + " is not compatible with current installed version " + installedVersion + "." ; athleteModel . datedAthleteSettings = [ ] ; const importedSyncedBackupModel = { syncedActivities : TEST_SYNCED_ACTIVITIES , athleteModel : athleteModel , lastSyncDateTime : lastSyncDateTime , pluginVersion : importedBackupVersion } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . syncedActivities ) ) ; const promise = syncService . import ( importedSyncedBackupModel ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . not . toBeNull ( ) ; expect ( error ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should not import athlete activities with no version provided (=null)" , ( done ) => { const lastSyncDateTime = 99 ; const expectedErrorMessage = "Plugin version is not defined in provided backup file. Try to perform a clean full re-sync." ; athleteModel . datedAthleteSettings = [ ] ; const importedSyncedBackupModel = { syncedActivities : TEST_SYNCED_ACTIVITIES , athleteModel : athleteModel , lastSyncDateTime : lastSyncDateTime , pluginVersion : null } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . syncedActivities ) ) ; const promise = syncService . import ( importedSyncedBackupModel ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should not import athlete activities with no version provided (missing key)" , ( done ) => { const lastSyncDateTime = 99 ; const expectedErrorMessage = "Plugin version is not defined in provided backup file. Try to perform a clean full re-sync." ; const syncedBackupModelPartial = { syncedActivities : TEST_SYNCED_ACTIVITIES , lastSyncDateTime : lastSyncDateTime } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( syncedBackupModelPartial . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( syncedBackupModelPartial . syncedActivities ) ) ; const promise = syncService . import ( syncedBackupModelPartial as SyncedBackupModel ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should not import athlete activities with synced activities empty (=null)" , ( done ) => { const lastSyncDateTime = 99 ; const importedBackupVersion = "1.0.0" ; const expectedErrorMessage = "Activities are not defined or empty in provided backup file. Try to perform a clean full re-sync." ; athleteModel . datedAthleteSettings = [ ] ; const importedSyncedBackupModel = { syncedActivities : null , athleteModel : athleteModel , lastSyncDateTime : lastSyncDateTime , pluginVersion : importedBackupVersion } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . syncedActivities ) ) ; const promise = syncService . import ( importedSyncedBackupModel ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should not import athlete activities with synced activities empty (missing key)" , ( done ) => { const lastSyncDateTime = 99 ; const importedBackupVersion = "1.0.0" ; const expectedErrorMessage = "Activities are not defined or empty in provided backup file. Try to perform a clean full re-sync." ; const importedSyncedBackupModelPartial = { lastSyncDateTime : lastSyncDateTime , pluginVersion : importedBackupVersion } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModelPartial . lastSyncDateTime ) ) ; const promise = syncService . import ( importedSyncedBackupModelPartial as SyncedBackupModel ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should not import athlete activities with synced activities empty (length == 0)" , ( done ) => { const lastSyncDateTime = 99 ; const importedBackupVersion = "1.0.0" ; const expectedErrorMessage = "Activities are not defined or empty in provided backup file. Try to perform a clean full re-sync." ; athleteModel . datedAthleteSettings = [ ] ; const importedSyncedBackupModel = { syncedActivities : [ ] , athleteModel : athleteModel , lastSyncDateTime : lastSyncDateTime , pluginVersion : importedBackupVersion } ; spyOn ( syncService . lastSyncDateTimeDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "save" ) . and . returnValue ( Promise . resolve ( importedSyncedBackupModel . syncedActivities ) ) ; const promise = syncService . import ( importedSyncedBackupModel ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should remove athlete activities" , ( done ) => { spyOn ( syncService . lastSyncDateTimeDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; spyOn ( syncService . activityDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; const spyResolve = spyOn ( Promise , "resolve" ) . and . callThrough ( ) ; const promise = syncService . clearSyncedData ( ) ; promise . then ( ( ) => { expect ( spyResolve ) . toHaveBeenCalled ( ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should reject on remove activities failure (remove not deleted)" , ( done ) => { spyOn ( syncService . lastSyncDateTimeDao , "clear" ) . and . returnValue ( Promise . resolve ( ) ) ; spyOn ( syncService . activityDao , "clear" ) . and . returnValue ( Promise . reject ( "Houston we have a problem" ) ) ; const promise = syncService . clearSyncedData ( ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , error => { expect ( error ) . toEqual ( "Athlete synced data has not been cleared totally. Some properties cannot be deleted. You may need to uninstall/install the software." ) ; done ( ) ; } ) ; } ) ; it ( "should open sync window" , ( done ) => { const fastSync = false ; const forceSync = false ; const expectedUrl = "https://www.strava.com/dashboard?elevateSync=true&fastSync=" + forceSync + "&forceSync=" + forceSync + "&sourceTabId=" + tabId ; syncService . sync ( fastSync , forceSync ) ; expect ( syncService . getCurrentTab ) . toHaveBeenCalled ( ) ; expect ( window . open ) . toHaveBeenCalled ( ) ; expect ( window . open ) . toHaveBeenCalledWith ( expectedUrl , jasmine . any ( String ) , jasmine . any ( String ) ) ; done ( ) ; } ) ; it ( "should provide NOT_SYNCED state" , ( done ) => { const expectedState = SyncState . NOT_SYNCED ; spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( Promise . resolve ( null ) ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( null ) ) ; const promise = syncService . getSyncState ( ) ; promise . then ( ( syncState ) => { expect ( syncState ) . toEqual ( expectedState ) ; done ( ) ; } ) ; } ) ; it ( "should provide PARTIALLY_SYNCED state" , ( done ) => { const expectedState = SyncState . PARTIALLY_SYNCED ; spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( Promise . resolve ( null ) ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( TEST_SYNCED_ACTIVITIES ) ) ; const promise = syncService . getSyncState ( ) ; promise . then ( ( syncState ) => { expect ( syncState ) . toEqual ( expectedState ) ; done ( ) ; } ) ; } ) ; it ( "should provide SYNCED state (1)" , ( done ) => { const expectedState = SyncState . SYNCED ; const lastSyncDateTime = 9999 ; spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( Promise . resolve ( lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( TEST_SYNCED_ACTIVITIES ) ) ; const promise = syncService . getSyncState ( ) ; promise . then ( ( syncState ) => { expect ( syncState ) . toEqual ( expectedState ) ; done ( ) ; } ) ; } ) ; it ( "should provide SYNCED state (2)" , ( done ) => { const expectedState = SyncState . SYNCED ; const lastSyncDateTime = 9999 ; spyOn ( syncService . lastSyncDateTimeDao , "fetch" ) . and . returnValue ( Promise . resolve ( lastSyncDateTime ) ) ; spyOn ( syncService . activityDao , "fetch" ) . and . returnValue ( Promise . resolve ( null ) ) ; const promise = syncService . getSyncState ( ) ; promise . then ( ( syncState ) => { expect ( syncState ) . toEqual ( expectedState ) ; done ( ) ; } ) ; } ) ; } ) ;