diff --git a/packages/client-app/internal_packages/sync-health-checker/spec/sync-health-checker-spec.es6 b/packages/client-app/internal_packages/sync-health-checker/spec/sync-health-checker-spec.es6 new file mode 100644 index 000000000..09dfb581a --- /dev/null +++ b/packages/client-app/internal_packages/sync-health-checker/spec/sync-health-checker-spec.es6 @@ -0,0 +1,44 @@ +import {ipcRenderer} from 'electron' +import SyncHealthChecker from '../lib/sync-health-checker' + +const requestWithErrorResponse = () => { + return { + run: async () => { + throw new Error('ECONNREFUSED'); + }, + } +} + +const activityData = {account1: {time: 1490305104619, activity: ['activity']}} + +const requestWithDataResponse = () => { + return { + run: async () => { + return activityData + }, + } +} + +describe('SyncHealthChecker', () => { + describe('when the worker window is not available', () => { + beforeEach(() => { + spyOn(SyncHealthChecker, '_buildRequest').andCallFake(requestWithErrorResponse) + spyOn(ipcRenderer, 'send') + spyOn(NylasEnv, 'reportError') + }) + it('attempts to restart it', async () => { + await SyncHealthChecker._checkSyncHealth(); + expect(NylasEnv.reportError.calls.length).toEqual(1) + expect(ipcRenderer.send.calls[0].args[0]).toEqual('ensure-worker-window') + }) + }) + describe('when data is returned', () => { + beforeEach(() => { + spyOn(SyncHealthChecker, '_buildRequest').andCallFake(requestWithDataResponse) + }) + it('stores the data', async () => { + await SyncHealthChecker._checkSyncHealth(); + expect(SyncHealthChecker._lastSyncActivity).toEqual(activityData) + }) + }) +}) diff --git a/packages/client-sync/spec/local-sync-worker/sync-process-manager-spec.es6 b/packages/client-sync/spec/local-sync-worker/sync-process-manager-spec.es6 new file mode 100644 index 000000000..8e9875944 --- /dev/null +++ b/packages/client-sync/spec/local-sync-worker/sync-process-manager-spec.es6 @@ -0,0 +1,68 @@ +import {IdentityStore} from 'nylas-exports' +import {createLogger} from '../../src/shared/logger' +import LocalDatabaseConnector from '../../src/shared/local-database-connector' +import SyncProcessManager from '../../src/local-sync-worker/sync-process-manager' +import SyncActivity from '../../src/shared/sync-activity' + +describe("SynccProcessManager", () => { + beforeEach(async () => { + global.Logger = createLogger() + spyOn(IdentityStore, 'identity').andReturn(true) + const db = await LocalDatabaseConnector.forShared(); + await db.Account.create({id: 'test-account'}) + }) + afterEach(async () => { + const db = await LocalDatabaseConnector.forShared(); + const accounts = db.Account.findAll(); + return Promise.all(accounts.map((account) => account.destroy())) + }) + describe("when a sync worker is stuck", () => { + beforeEach(() => { + spyOn(NylasEnv, 'reportError') + spyOn(SyncProcessManager, 'removeWorkerForAccountId').andCallThrough() + spyOn(SyncProcessManager, 'addWorkerForAccount').andCallThrough() + spyOn(SyncActivity, 'getLastSyncActivityForAccount').andReturn({ + time: Date.now() - 2 * SyncProcessManager.MAX_WORKER_SILENCE_MS, + activity: ['activity'], + }) + // Make sure the health check interval isn't automatically started + SyncProcessManager._check_health_interval = 1 + }) + it("detects it and recovers", async () => { + await SyncProcessManager.start() + expect(SyncProcessManager.removeWorkerForAccountId.calls.length).toEqual(0) + expect(SyncProcessManager.addWorkerForAccount.calls.length).toEqual(1) + + await SyncProcessManager._checkHealth() + expect(NylasEnv.reportError.calls.length).toEqual(1) + expect(SyncProcessManager.removeWorkerForAccountId.calls.length).toEqual(1) + expect(SyncProcessManager.addWorkerForAccount.calls.length).toEqual(2) + }) + it("doesn't have zombie workers come back to life", async () => { + await SyncProcessManager.start() + + // Zombify a worker + const zombieSync = () => { + return new Promise(() => {}) // Never resolves + } + const zombieWorker = SyncProcessManager.workers()[0] + const origSync = zombieWorker.syncNow + zombieWorker.syncNow = zombieSync + zombieWorker.interrupt() + zombieWorker.syncNow() + + // Make sure the worker is discarded by the manager + await SyncProcessManager._checkHealth() + expect(NylasEnv.reportError.calls.length).toEqual(1) + expect(SyncProcessManager.removeWorkerForAccountId.calls.length).toEqual(1) + expect(SyncProcessManager.addWorkerForAccount.calls.length).toEqual(2) + + // Try to get the zombie to sync again, check that it doesn't. + const lastStart = zombieWorker._syncStart; + zombieWorker.syncNow = origSync + zombieWorker.interrupt({reason: 'Playing Frankenstein'}) + await zombieWorker.syncNow() + expect(zombieWorker._syncStart).toEqual(lastStart) + }) + }) +})