VulpesOS/gaia/apps/system/test/unit/sync_manager_test.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

1724 lines
51 KiB
JavaScript
Executable file

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
/* global asyncStorage */
/* global BaseModule */
/* global ERROR_GET_FXA_ASSERTION */
/* global ERROR_SYNC_APP_KILLED */
/* global ERROR_UNVERIFIED_ACCOUNT */
/* global expect */
/* global FxAccountsClient */
/* global IACHandler */
/* global MockAdvancedTelemetryHelper */
/* global MocksHelper */
/* global MockNavigatormozSetMessageHandler */
/* global MockNavigatorSettings */
/* global MockService */
/* global SyncManager */
/* global SyncRecoverableErrors */
requireApp('system/js/service.js');
requireApp('system/test/unit/mock_asyncStorage.js');
requireApp('system/js/base_module.js');
requireApp('system/js/sync_state_machine.js');
requireApp('system/js/sync_manager.js');
requireApp('system/test/unit/mock_fxa_client.js');
requireApp('system/test/unit/mock_iac_handler.js');
require('/shared/test/unit/mocks/mock_lazy_loader.js');
require('/shared/js/sync/errors.js');
require('/shared/js/advanced_telemetry_helper.js');
require('/shared/test/unit/mocks/mock_service.js');
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
require('/shared/test/unit/mocks/mock_navigator_moz_set_message_handler.js');
require('/shared/test/unit/mocks/mock_advanced_telemetry_helper.js');
var mocksForSyncManager = new MocksHelper([
'asyncStorage',
'FxAccountsClient',
'IACHandler',
'LazyLoader',
'Service'
]).init();
suite('system/SyncManager >', () => {
var realMozSetMessageHandler;
var realMozSettings;
var realTelemetryHelper;
var systemMessageHandlerSpy;
mocksForSyncManager.attachTestHelpers();
suiteSetup(() => {
require('/shared/js/sync/telemetry.js');
realMozSetMessageHandler = navigator.mozSetMessageHandler;
navigator.mozSetMessageHandler = MockNavigatormozSetMessageHandler;
realMozSettings = navigator.mozSettings;
navigator.mozSettings = MockNavigatorSettings;
realTelemetryHelper = window.AdvancedTelemetryHelper;
window.AdvancedTelemetryHelper = MockAdvancedTelemetryHelper;
MockNavigatormozSetMessageHandler.mSetup();
MockNavigatorSettings.mSetup();
systemMessageHandlerSpy = this.sinon.spy(navigator, 'mozSetMessageHandler');
});
suiteTeardown(() => {
navigator.mozSetMessageHandler = realMozSetMessageHandler;
navigator.mozSettings = realMozSettings;
window.AdvancedTelemetryHelper = realTelemetryHelper;
MockNavigatorSettings.mTeardown();
});
suite('Initial state', () => {
var syncManager;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
test('Integrity', () => {
assert.ok(syncManager !== undefined);
sinon.assert.calledOnce(systemMessageHandlerSpy);
sinon.assert.calledWith(systemMessageHandlerSpy, 'request-sync');
});
});
suite('Initial state setting', () => {
var syncManager;
const syncState = 'sync.state';
const syncError = 'sync.state.error';
var nextState;
var nextError;
var updateStateSpy;
var requestStub;
suiteSetup(() => {
nextState = 'enabling';
});
suiteTeardown(() => {
asyncStorage.setItem(syncState, 'disabled');
asyncStorage.setItem(syncError, null);
});
setup(done => {
asyncStorage.setItem(syncState, nextState, () => {
asyncStorage.setItem(syncError, nextError, () => {
syncManager = BaseModule.instantiate('SyncManager');
updateStateSpy = this.sinon.spy(syncManager, 'updateState');
requestStub = this.sinon.stub(MockService, 'request', () => {
return Promise.resolve();
});
this.sinon.stub(syncManager, 'getAccount', () => {
syncManager.user = 'someone';
return Promise.resolve('someone');
});
syncManager.start().then(done);
});
});
});
teardown(() => {
syncManager.stop();
updateStateSpy.restore();
requestStub.restore();
});
[{
syncStateValue: 'enabling',
nextSyncStateValue: 'errored',
shouldDisable: true
}, {
syncStateValue: 'errored',
nextSyncStateValue: 'errored',
nextSyncUnverified: true,
shouldDisable: true
}, {
syncStateValue: 'errored',
nextSyncStateValue: 'enabled',
shouldDisable: false
}, {
syncStateValue: 'enabled',
nextSyncStateValue: 'success',
shouldDisable: false
}, {
syncStateValue: 'success',
nextSyncStateValue: 'syncing',
shouldDisable: false
}, {
syncStateValue: 'syncing',
nextSyncStateValue: 'disabling',
shouldDisable: false
}, {
syncStateValue: 'disabling',
nextSyncStateValue: 'disabled',
shouldDisable: true
}].forEach(config => {
test('sync.state ' + config.syncStateValue, () => {
if (config.shouldDisable) {
this.sinon.assert.calledOnce(updateStateSpy);
assert.ok(updateStateSpy.calledWith('disabled'));
this.sinon.assert.notCalled(requestStub);
} else {
this.sinon.assert.notCalled(updateStateSpy);
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:enable'));
}
if (config.nextSyncUnverified) {
nextError = ERROR_UNVERIFIED_ACCOUNT;
}
nextState = config.nextSyncStateValue;
});
});
test('sync.state disabled', () => {
this.sinon.assert.notCalled(updateStateSpy);
this.sinon.assert.notCalled(requestStub);
nextState = 'enabled';
});
});
suite('Sync management IAC based API', () => {
var syncManager;
var requestStub;
var getPortStub;
var getAccountStub;
var port;
var telemetryHelperSpy;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
requestStub = this.sinon.stub(MockService, 'request', () => {
return Promise.resolve();
});
getPortStub = this.sinon.stub(IACHandler, 'getPort', () => {
return port;
});
getAccountStub = this.sinon.stub(FxAccountsClient, 'getAccount',
successCb => {
successCb({
email: 'user@domain.org'
});
});
telemetryHelperSpy = this.sinon.spy(window, 'AdvancedTelemetryHelper');
});
teardown(() => {
requestStub.restore();
getPortStub.restore();
getAccountStub.restore();
telemetryHelperSpy.restore();
});
['enable', 'disable', 'sync'].forEach(name => {
test('receive ' + name + ' IAC request', done => {
window.dispatchEvent(new CustomEvent('iac-gaia-sync-management', {
detail: {
name: name
}
}));
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:' + name));
setTimeout(() => {
assert.isTrue(telemetryHelperSpy.calledWithNew());
var counter = telemetryHelperSpy.getCall(0).returnValue;
assert.equal(counter.name, 'telemetry_gaia_sync_user_action_' + name);
assert.equal(window.AdvancedTelemetryHelper.HISTOGRAM_COUNT,
counter.type);
done();
});
});
});
test('getInfo request', done => {
var id = Date.now();
var sentMessage;
port = {
postMessage: function(message) {
sentMessage = message;
}
};
window.dispatchEvent(new CustomEvent('iac-gaia-sync-management', {
detail: {
name: 'getInfo',
id: id
}
}));
setTimeout(() => {
this.sinon.assert.notCalled(requestStub);
this.sinon.assert.calledOnce(getPortStub);
assert.equal(sentMessage.id, id);
assert.equal(sentMessage.state, syncManager.state);
assert.equal(sentMessage.lastSync, syncManager.lastSync);
assert.equal(sentMessage.user, 'user@domain.org');
done();
});
});
test('getInfo request - missing id', () => {
window.dispatchEvent(new CustomEvent('iac-gaia-sync-management', {
detail: {
name: 'getInfo'
}
}));
this.sinon.assert.notCalled(requestStub);
this.sinon.assert.notCalled(getPortStub);
});
test('receive unknown IAC request', () => {
window.dispatchEvent(new CustomEvent('iac-gaia-sync-management', {
detail: {
name: 'whatever'
}
}));
this.sinon.assert.notCalled(requestStub);
});
});
suite('onsyncdisabled', () => {
var syncManager;
var realNavigatorSync;
var unregisterSyncSpy;
var updateStateSpy;
var removeEventListenerSpy;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
realNavigatorSync = navigator.sync;
navigator.sync = {
unregister: function() {
return Promise.resolve();
}
};
unregisterSyncSpy = this.sinon.spy(navigator.sync, 'unregister');
updateStateSpy = this.sinon.spy(syncManager, 'updateState');
removeEventListenerSpy = this.sinon.spy(window, 'removeEventListener');
});
suiteTeardown(() => {
syncManager.stop();
unregisterSyncSpy.restore();
updateStateSpy.restore();
removeEventListenerSpy.restore();
navigator.sync = realNavigatorSync;
});
test('onsyncdisabled received', done => {
window.dispatchEvent(new CustomEvent('onsyncdisabled'));
setTimeout(() => {
this.sinon.assert.calledOnce(unregisterSyncSpy);
assert.ok(unregisterSyncSpy.calledWith('gaia::system::firefoxsync'));
this.sinon.assert.calledOnce(updateStateSpy);
this.sinon.assert.calledOnce(removeEventListenerSpy);
assert.ok(
removeEventListenerSpy.calledWith('mozFxAccountsUnsolChromeEvent')
);
done();
});
});
});
suite('onsyncenabled', () => {
var syncManager;
var realNavigatorSync;
var registerSyncSpy;
var registerError;
var registrationsError;
var syncRegistrations = [];
var updateStateSpy;
var requestStub;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
realNavigatorSync = navigator.sync;
});
suiteTeardown(() => {
navigator.sync = realNavigatorSync;
syncManager.stop();
});
setup(() => {
navigator.sync = {
registrations: function() {
return registrationsError ? Promise.reject(registrationsError)
: Promise.resolve(syncRegistrations);
},
register: function() {
return registerError ? Promise.reject(registerError)
: Promise.resolve();
}
};
registerSyncSpy = this.sinon.spy(navigator.sync, 'register');
updateStateSpy = this.sinon.spy(syncManager, 'updateState');
requestStub = this.sinon.stub(MockService, 'request', () => {
return Promise.resolve();
});
syncManager.user = 'someone';
});
teardown(() => {
registerSyncSpy.restore();
updateStateSpy.restore();
requestStub.restore();
});
test('onsyncenabled received - success', done => {
window.dispatchEvent(new CustomEvent('onsyncenabled'));
setTimeout(() => {
this.sinon.assert.calledOnce(registerSyncSpy);
assert.ok(registerSyncSpy.calledWith('gaia::system::firefoxsync'));
this.sinon.assert.calledOnce(updateStateSpy);
done();
});
});
test('onsyncenabled received - already registered', done => {
teardown(() => {
syncRegistrations = [];
});
syncRegistrations = [{
task: 'gaia::system::firefoxsync'
}];
window.dispatchEvent(new CustomEvent('onsyncenabled'));
setTimeout(() => {
this.sinon.assert.notCalled(registerSyncSpy);
this.sinon.assert.calledOnce(updateStateSpy);
done();
});
});
test('onsyncenabled received - navigator.sync.registrations error',
done => {
teardown(() => {
registrationsError = null;
});
registrationsError = 'error';
window.dispatchEvent(new CustomEvent('onsyncenabled'));
setTimeout(() => {
this.sinon.assert.notCalled(registerSyncSpy);
this.sinon.assert.calledOnce(updateStateSpy);
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:error'));
done();
});
});
test('onsyncenabled received - navigator.sync.register error', done => {
teardown(() => {
registerError = null;
});
registerError = 'error';
window.dispatchEvent(new CustomEvent('onsyncenabled'));
setTimeout(() => {
this.sinon.assert.calledOnce(registerSyncSpy);
this.sinon.assert.calledOnce(updateStateSpy);
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:error'));
done();
});
});
test('onsyncenabled received - no user should disable sync', done => {
syncManager.user = null;
window.dispatchEvent(new CustomEvent('onsyncenabled'));
setTimeout(() => {
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:disable'));
done();
});
});
});
suite('onsyncenabling', () => {
var syncManager;
var updateStateStub;
var requestStub;
var getAssertionStub;
var getAssertionError;
var getKeysStub;
var getKeysError;
var trySyncStub;
var trySyncError;
var getAccountStub;
var addEventListenerSpy;
var saveDefaultSettingsStub;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
updateStateStub = this.sinon.stub(syncManager, 'updateState', () => {
syncManager.updateStateDeferred = Promise.resolve();
});
addEventListenerSpy = this.sinon.spy(window, 'addEventListener');
requestStub = this.sinon.stub(MockService, 'request', () => {
return Promise.resolve();
});
getAssertionStub = this.sinon.stub(syncManager, 'getAssertion', () => {
return getAssertionError ? Promise.reject(getAssertionError)
: Promise.resolve();
});
getKeysStub = this.sinon.stub(syncManager, 'getKeys', () => {
return getKeysError ? Promise.reject(getKeysError)
: Promise.resolve();
});
trySyncStub = this.sinon.stub(syncManager, 'trySync', () => {
return trySyncError ? Promise.reject(trySyncError)
: Promise.resolve();
});
getAccountStub = this.sinon.stub(syncManager, 'getAccount', () => {
return Promise.resolve();
});
saveDefaultSettingsStub = this.sinon.stub(syncManager,
'saveDefaultSettings',
() => {
return Promise.resolve();
});
});
teardown(() => {
addEventListenerSpy.restore();
updateStateStub.restore();
requestStub.restore();
getAssertionStub.restore();
getKeysStub.restore();
trySyncStub.restore();
getAccountStub.restore();
saveDefaultSettingsStub.restore();
});
test('onsyncenabling received - success', done => {
window.dispatchEvent(new CustomEvent('onsyncenabling'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.calledOnce(getKeysStub);
this.sinon.assert.calledOnce(trySyncStub);
this.sinon.assert.calledOnce(getAccountStub);
this.sinon.assert.calledOnce(addEventListenerSpy);
this.sinon.assert.calledWith(addEventListenerSpy,
'mozFxAccountsUnsolChromeEvent');
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:success'));
done();
});
});
test('onsyncenabling received - getAssertion error', done => {
teardown(() => {
getAssertionError = null;
});
getAssertionError = 'UNVERIFIED_ACCOUNT';
window.dispatchEvent(new CustomEvent('onsyncenabling'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.notCalled(getKeysStub);
this.sinon.assert.notCalled(trySyncStub);
this.sinon.assert.calledOnce(getAccountStub);
this.sinon.assert.calledOnce(addEventListenerSpy);
this.sinon.assert.calledWith(addEventListenerSpy,
'mozFxAccountsUnsolChromeEvent');
this.sinon.assert.calledOnce(requestStub);
assert.equal(requestStub.getCall(0).args[0], 'SyncStateMachine:error');
assert.equal(requestStub.getCall(0).args[1],
ERROR_UNVERIFIED_ACCOUNT);
done();
});
});
test('onsyncenabling received - getKeys error', done => {
teardown(() => {
getKeysError = null;
});
getKeysError = Date.now();
window.dispatchEvent(new CustomEvent('onsyncenabling'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.calledOnce(getKeysStub);
this.sinon.assert.notCalled(trySyncStub);
this.sinon.assert.notCalled(getAccountStub);
this.sinon.assert.calledOnce(addEventListenerSpy);
this.sinon.assert.calledWith(addEventListenerSpy,
'mozFxAccountsUnsolChromeEvent');
this.sinon.assert.calledOnce(requestStub);
assert.equal(requestStub.getCall(0).args[0], 'SyncStateMachine:error');
assert.equal(requestStub.getCall(0).args[1], getKeysError);
done();
});
});
test('onsyncenabling received - trySync error', done => {
teardown(() => {
trySyncError = null;
});
trySyncError = Date.now();
window.dispatchEvent(new CustomEvent('onsyncenabling'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.calledOnce(getKeysStub);
this.sinon.assert.calledOnce(trySyncStub);
this.sinon.assert.calledOnce(getAccountStub);
this.sinon.assert.calledOnce(addEventListenerSpy);
this.sinon.assert.calledWith(addEventListenerSpy,
'mozFxAccountsUnsolChromeEvent');
this.sinon.assert.calledOnce(requestStub);
assert.equal(requestStub.getCall(0).args[0], 'SyncStateMachine:error');
assert.equal(requestStub.getCall(0).args[1], trySyncError);
done();
});
});
});
suite('onsyncerrored', () => {
var syncManager;
var updateStateSpy;
var requestStub;
var logoutSpy;
const ERROR_SYNC_APP_KILLED = 'fxsync-error-app-killed';
const ERROR_SYNC_APP_SYNC_IN_PROGRESS =
'fxsync-error-app-fxsync-in-progress';
const ERROR_SYNC_APP_GENERIC = 'fxsync-error-app-generic';
const ERROR_SYNC_APP_TRY_LATER = 'fxsync-error-app-try-later';
const ERROR_UNVERIFIED_ACCOUNT = 'fxsync-error-unverified-account';
const ERROR_DIALOG_CLOSED_BY_USER = 'fxsync-error-dialog-closed-by-user';
const ERROR_GET_FXA_ASSERTION = 'fxsync-error-get-fxa-assertion';
const ERROR_INVALID_SYNC_ACCOUNT = 'fxsync-error-invalid-account';
const ERROR_OFFLINE = 'fxsync-error-offline';
const ERROR_REQUEST_SYNC_REGISTRATION =
'fxsync-error-request-fxsync-registration';
const ERROR_SYNC_INVALID_REQUEST_OPTIONS =
'fxsync-error-invalid-request-options';
const ERROR_SYNC_REQUEST = 'fxsync-error-request-failed';
const ERROR_NO_KEY_FETCH_TOKEN = 'fxsync-error-no-key-fetch-token';
const ERROR_UNKNOWN = 'fxsync-error-unknown';
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
updateStateSpy = this.sinon.spy(syncManager, 'updateState');
requestStub = this.sinon.stub(MockService, 'request', () => {
return Promise.resolve();
});
logoutSpy = this.sinon.spy(FxAccountsClient, 'logout');
});
teardown(() => {
updateStateSpy.restore();
requestStub.restore();
logoutSpy.restore();
});
const errors = [
ERROR_SYNC_APP_KILLED,
ERROR_SYNC_APP_SYNC_IN_PROGRESS,
ERROR_SYNC_APP_GENERIC,
ERROR_SYNC_APP_TRY_LATER,
ERROR_UNVERIFIED_ACCOUNT,
ERROR_DIALOG_CLOSED_BY_USER,
ERROR_GET_FXA_ASSERTION,
ERROR_INVALID_SYNC_ACCOUNT,
ERROR_OFFLINE,
ERROR_REQUEST_SYNC_REGISTRATION,
ERROR_SYNC_INVALID_REQUEST_OPTIONS,
ERROR_SYNC_REQUEST,
ERROR_NO_KEY_FETCH_TOKEN,
ERROR_UNKNOWN
];
errors.forEach(error => {
test(`onerrored received while NOT syncing - ${error}`, done => {
window.dispatchEvent(new CustomEvent('onsyncerrored', {
detail: {
args: [error]
}
}));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateSpy);
assert.equal(syncManager.error, error);
if (error == ERROR_UNVERIFIED_ACCOUNT) {
this.sinon.assert.notCalled(requestStub);
} else if (error == ERROR_INVALID_SYNC_ACCOUNT) {
assert.ok(requestStub.calledWith('SyncStateMachine:enable'));
} else if (error == ERROR_NO_KEY_FETCH_TOKEN) {
assert.ok(logoutSpy.calledOnce);
} else {
assert.ok(requestStub.calledWith('SyncStateMachine:disable'));
}
done();
});
});
});
errors.forEach(error => {
test(`onerrored received while syncing - ${error}`, done => {
window.dispatchEvent(new CustomEvent('onsyncerrored', {
detail: {
args: [error],
from: 'syncing'
}
}));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateSpy);
assert.equal(syncManager.error, error);
if (error == ERROR_UNVERIFIED_ACCOUNT) {
this.sinon.assert.notCalled(requestStub);
} else if (SyncRecoverableErrors.indexOf(error) > -1 ||
error == ERROR_INVALID_SYNC_ACCOUNT) {
assert.ok(requestStub.calledWith('SyncStateMachine:enable'));
} else if (error == ERROR_NO_KEY_FETCH_TOKEN) {
assert.ok(logoutSpy.calledOnce);
} else {
assert.ok(requestStub.calledWith('SyncStateMachine:disable'));
}
done();
});
});
});
});
suite('onsyncsyncing', () => {
var syncManager;
var updateStateStub;
var unregisterSyncStub;
var requestStub;
var getAssertionStub;
var getAssertionError;
var getKeysStub;
var getKeysError;
var realMozApps;
var realUuid;
var port;
var portStub;
var result;
var assertion = 'assertion';
var keys = 'keys';
var id;
suiteSetup(() => {
realMozApps = navigator.mozApps;
realUuid = window.uuid;
port = {
postMessage: function() {},
set onmessage(callback) {
setTimeout(() => {
callback({ data: result });
});
}
};
var app = {
connect: function() {
return Promise.resolve([port]);
}
};
var onsuccess = {
set onsuccess(callback) {
setTimeout(() => {
callback({ target: { result: app } });
});
}
};
navigator.mozApps = {
getSelf: function() {
return onsuccess;
}
};
window.uuid = () => {
return id;
};
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
navigator.mozApps = realMozApps;
window.uuid = realUuid;
});
setup(() => {
updateStateStub = this.sinon.stub(syncManager, 'updateState');
unregisterSyncStub = this.sinon.stub(syncManager,
'unregisterSyncRequest');
requestStub = this.sinon.stub(MockService, 'request', () => {
return Promise.resolve();
});
getAssertionStub = this.sinon.stub(syncManager, 'getAssertion', () => {
return getAssertionError ? Promise.reject(getAssertionError)
: Promise.resolve(assertion);
});
getKeysStub = this.sinon.stub(syncManager, 'getKeys', () => {
return getKeysError ? Promise.reject(getKeysError)
: Promise.resolve(keys);
});
portStub = this.sinon.stub(port, 'postMessage');
syncManager.state = 'syncing';
});
teardown(() => {
updateStateStub.restore();
unregisterSyncStub.restore();
requestStub.restore();
getKeysStub.restore();
portStub.restore();
getAssertionStub.restore();
});
test('onsyncsyncing - success', done => {
id = Date.now();
result = {
id: id
};
var previousLastSync = syncManager.lastSync;
syncManager._settings['sync.collections.history.enabled'] = true;
syncManager._settings['sync.collections.history.readonly'] = true;
window.dispatchEvent(new CustomEvent('onsyncsyncing'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(unregisterSyncStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.calledOnce(getKeysStub);
setTimeout(() => {
this.sinon.assert.calledOnce(portStub);
assert.equal(portStub.getCall(0).args[0].assertion, assertion);
assert.equal(portStub.getCall(0).args[0].keys, keys);
assert.ok(portStub.getCall(0).args[0].collections.history);
assert.ok(portStub.getCall(0).args[0].collections.history.readonly);
setTimeout(() => {
assert.notEqual(syncManager.lastSync, previousLastSync);
this.sinon.assert.calledOnce(requestStub);
assert.ok(requestStub.calledWith('SyncStateMachine:success'));
done();
});
});
});
});
test('onsyncsyncing - no collections selected', done => {
syncManager._settings['sync.collections.history.enabled'] = false;
syncManager._settings['sync.collections.passwords.enabled'] = false;
var previousLastSync = syncManager.lastSync;
window.dispatchEvent(new CustomEvent('onsyncsyncing'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(unregisterSyncStub);
this.sinon.assert.notCalled(getAssertionStub);
this.sinon.assert.notCalled(getKeysStub);
this.sinon.assert.notCalled(portStub);
this.sinon.assert.calledOnce(requestStub);
assert.equal(syncManager.lastSync, previousLastSync);
assert.ok(requestStub.calledWith('SyncStateMachine:success'));
done();
});
});
test('onsyncsyncing - getAssertion error', done => {
teardown(() => {
getAssertionError = null;
});
getAssertionError = 'error';
syncManager._settings['sync.collections.history.enabled'] = true;
var previousLastSync = syncManager.lastSync;
window.dispatchEvent(new CustomEvent('onsyncsyncing'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(unregisterSyncStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.notCalled(getKeysStub);
setTimeout(() => {
this.sinon.assert.notCalled(portStub);
this.sinon.assert.calledOnce(requestStub);
assert.equal(syncManager.lastSync, previousLastSync);
assert.ok(requestStub.calledWith('SyncStateMachine:error'));
done();
});
});
});
test('onsyncsyncing - getKeys error', done => {
teardown(() => {
getKeysError = null;
});
getKeysError = 'error';
syncManager._settings['sync.collections.history.enabled'] = true;
var previousLastSync = syncManager.lastSync;
window.dispatchEvent(new CustomEvent('onsyncsyncing'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(unregisterSyncStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.calledOnce(getKeysStub);
setTimeout(() => {
this.sinon.assert.notCalled(portStub);
this.sinon.assert.calledOnce(requestStub);
assert.equal(syncManager.lastSync, previousLastSync);
assert.ok(requestStub.calledWith('SyncStateMachine:error'));
done();
});
});
});
test('onsyncsyncing - sync app error', done => {
id = Date.now();
result = {
id: id,
error: 'error'
};
syncManager._settings['sync.collections.passwords.enabled'] = true;
syncManager._settings['sync.collections.passwords.readonly'] = false;
var previousLastSync = syncManager.lastSync;
window.dispatchEvent(new CustomEvent('onsyncsyncing'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(unregisterSyncStub);
this.sinon.assert.calledOnce(getAssertionStub);
this.sinon.assert.calledOnce(getKeysStub);
setTimeout(() => {
this.sinon.assert.calledOnce(portStub);
assert.equal(portStub.getCall(0).args[0].assertion, assertion);
assert.equal(portStub.getCall(0).args[0].keys, keys);
assert.ok(portStub.getCall(0).args[0].collections.passwords);
assert.ok(
!portStub.getCall(0).args[0].collections.passwords.readonly
);
setTimeout(() => {
this.sinon.assert.calledOnce(requestStub);
assert.equal(syncManager.lastSync, previousLastSync);
assert.ok(requestStub.calledWith('SyncStateMachine:error'));
done();
});
});
});
});
test('onsyncing - offline', done => {
Object.defineProperty(navigator, 'onLine', {
configurable: true,
get: () => {
return false;
},
set: () => {}
});
var previousLastSync = syncManager.lastSync;
window.dispatchEvent(new CustomEvent('onsyncsyncing'));
setTimeout(() => {
this.sinon.assert.calledOnce(updateStateStub);
this.sinon.assert.calledOnce(unregisterSyncStub);
this.sinon.assert.notCalled(getAssertionStub);
this.sinon.assert.notCalled(getKeysStub);
this.sinon.assert.notCalled(portStub);
this.sinon.assert.calledOnce(requestStub);
assert.equal(syncManager.lastSync, previousLastSync);
assert.ok(requestStub.calledWith('SyncStateMachine:success'));
done();
});
});
});
suite('Firefox Accounts - onlogout', () => {
var syncManager;
var requestSpy;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
requestSpy = this.sinon.spy(MockService, 'request');
});
suiteTeardown(() => {
syncManager.stop();
requestSpy.restore();
});
test('FxA logout should disable Sync', done => {
requestSpy.reset();
window.dispatchEvent(new CustomEvent('mozFxAccountsUnsolChromeEvent', {
detail: {
eventName: 'onlogout'
}
}));
setTimeout(() => {
assert.ok(requestSpy.calledWith('SyncStateMachine:disable'));
done();
});
});
});
suite('Firefox Accounts - onverified', () => {
var syncManager;
var requestSpy;
var getAccountStub;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
requestSpy = this.sinon.spy(MockService, 'request');
getAccountStub = this.sinon.stub(FxAccountsClient, 'getAccount',
successCb => {
successCb({
email: 'user@domain.org'
});
});
});
suiteTeardown(() => {
syncManager.stop();
requestSpy.restore();
});
test('FxA onverified should enable Sync', done => {
requestSpy.reset();
syncManager.state = 'errored';
syncManager.error = ERROR_UNVERIFIED_ACCOUNT;
window.dispatchEvent(new CustomEvent('mozFxAccountsUnsolChromeEvent', {
detail: {
eventName: 'onverified'
}
}));
setTimeout(() => {
assert.ok(requestSpy.calledWith('SyncStateMachine:enable'));
done();
});
});
test('FxA onverified and not ERROR_UNVERIFIED_ACCOUNT error ' +
'should not enable Sync', done => {
requestSpy.reset();
syncManager.state = 'errored';
syncManager.error = ERROR_GET_FXA_ASSERTION;
window.dispatchEvent(new CustomEvent('mozFxAccountsUnsolChromeEvent', {
detail: {
eventName: 'onverified'
}
}));
setTimeout(() => {
this.sinon.assert.notCalled(requestSpy);
done();
});
});
});
suite('FxA getAssertion', () => {
var syncManager;
var getAssertionStub;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
teardown(() => {
syncManager.stop();
});
setup(() => {
getAssertionStub = this.sinon.stub(FxAccountsClient, 'getAssertion',
(options, successCb, errorCb) => {
successCb('assertion');
});
});
teardown(() => {
getAssertionStub.restore();
});
test('No audience', done => {
syncManager._settings['sync.fxa.audience'] = null;
syncManager.getAssertion().then(result => {
assert.equal(result, 'assertion');
assert.ok(getAssertionStub.calledOnce);
assert.deepEqual(getAssertionStub.args[0][0], { audience: null });
done();
});
});
test('Audience set', done => {
syncManager._settings['sync.fxa.audience'] = 'audience';
syncManager.getAssertion().then(result => {
assert.equal(result, 'assertion');
assert.ok(getAssertionStub.calledOnce);
assert.deepEqual(getAssertionStub.args[0][0], { audience: 'audience' });
done();
});
});
});
suite('updateState', () => {
var syncManager;
var queryStub;
var queryStubResult = 'qsState';
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
queryStub = this.sinon.stub(MockService, 'query', () => {
return queryStubResult;
});
});
teardown(() => {
queryStub.restore();
});
test('updateState with params should not query state machine', () => {
var state = 'state';
syncManager.updateState(state);
assert.equal(syncManager.state, state);
this.sinon.assert.notCalled(queryStub);
});
test('updateState w/o params should query state machine', () => {
syncManager.updateState();
assert.equal(syncManager.state, queryStubResult);
this.sinon.assert.calledOnce(queryStub);
assert.ok(queryStub.calledWith('SyncStateMachine.state'));
});
});
suite('Synchronizer killed while syncing', () => {
var syncManager;
var realMozApps;
var realUuid;
var port;
var _onclose;
var requestSpy;
var deferred = {};
deferred.promise = new Promise(resolve => {
deferred.resolve = resolve;
});
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
requestSpy = this.sinon.spy(MockService, 'request');
realMozApps = navigator.mozApps;
realUuid = window.uuid;
port = {
set onclose(cb) {
_onclose = cb;
},
postMessage() {
setTimeout(() => {
_onclose();
deferred.resolve();
});
}
};
var ports = [port];
var app = {
connect() {
return Promise.resolve(ports);
}
};
var onsuccess = {
set onsuccess(callback) {
setTimeout(() => {
callback({ target: { result: app } });
});
}
};
navigator.mozApps = {
getSelf: function() {
return onsuccess;
}
};
window.uuid = () => {
return Date.now();
};
});
suiteTeardown(() => {
syncManager.stop();
navigator.mozApps = realMozApps;
window.uuid = realUuid;
requestSpy.restore();
});
test('Should throw error if port closes while syncing', done => {
syncManager.state = 'syncing';
deferred.promise.then(() => {
assert.equal(requestSpy.getCall(0).args[0], 'SyncStateMachine:error');
assert.equal(requestSpy.getCall(0).args[1],
ERROR_SYNC_APP_KILLED);
done();
});
syncManager.doSync();
});
});
suite('appterminated', () => {
var syncManager;
var isSyncAppStub;
var isSyncApp;
function appterminated() {
window.dispatchEvent(new CustomEvent('appterminated', {
detail: {
origin: 'whatever'
}
}));
}
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
isSyncAppStub = this.sinon.stub(syncManager, 'isSyncApp', () => {
return isSyncApp ? Promise.resolve() : Promise.reject();
});
});
teardown(() => {
isSyncAppStub.restore();
});
test('No port', done => {
syncManager._port = undefined;
appterminated();
setTimeout(() => {
assert.ok(isSyncAppStub.notCalled);
done();
});
});
test('Is Sync app', done => {
isSyncApp = true;
syncManager._port = {};
appterminated();
setTimeout(() => {
assert.isNull(syncManager._port);
done();
});
});
test('Is not Sync app', done => {
isSyncApp = false;
syncManager._port = {};
appterminated();
setTimeout(() => {
assert.ok(syncManager._port);
done();
});
});
});
suite('isSyncApp', () => {
var syncManager;
var realMozApps;
var connections = [];
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
realMozApps = navigator.mozApps;
var app = {
getConnections: function() {
return Promise.resolve(connections);
}
};
var onsuccess = {
set onsuccess(callback) {
setTimeout(() => {
callback({ target: { result: app } });
});
}
};
navigator.mozApps = {
getSelf: function() {
return onsuccess;
}
};
});
suiteTeardown(() => {
syncManager.stop();
navigator.mozApps = realMozApps;
});
setup(() => {
connections = [];
});
[{
test: 'No origin',
origin: undefined
}, {
test: 'No connections',
origin: 'origin'
}, {
test: 'No matching connection',
origin: 'origin',
connections: [{
keyword: 'keyword',
origin: 'anotherorigin'
}]
}].forEach(config => {
connections = config.connections;
test(config.test, done => {
expect(syncManager.isSyncApp(config.origin))
.to.be.rejectedWith(undefined).and.notify(done);
});
});
test('Matching connection', done => {
connections = [{
keyword: 'gaia::sync::request',
origin: 'origin',
subscriber: 'origin'
}];
syncManager.isSyncApp('origin').then(() => {
assert.ok(false, 'Unexpected resolution');
done();
}).catch(() => {
assert.ok(true, 'Expected rejection');
done();
});
});
});
suite('cleanup', () => {
var syncManager;
var unregisterSyncStub;
var removeEventListenerStub;
var cancelSyncStub;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
unregisterSyncStub = this.sinon.stub(syncManager,
'unregisterSyncRequest');
removeEventListenerStub = this.sinon.stub(window, 'removeEventListener');
cancelSyncStub = this.sinon.stub(syncManager, 'cancelSync');
});
teardown(() => {
unregisterSyncStub.restore();
removeEventListenerStub.restore();
cancelSyncStub.restore();
});
test('Cleanup', () => {
syncManager.cleanup();
assert.ok(unregisterSyncStub.calledOnce);
assert.ok(removeEventListenerStub.calledOnce);
assert.ok(cancelSyncStub.notCalled);
});
test('Cleanup while syncing', () => {
syncManager.state = 'syncing';
syncManager.cleanup();
assert.ok(unregisterSyncStub.calledOnce);
assert.ok(removeEventListenerStub.calledOnce);
assert.ok(cancelSyncStub.calledOnce);
});
});
suite('updateStatePreference', () => {
var syncManager;
var updateStatePreferenceSpy;
var initialSettingValue;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
initialSettingValue =
MockNavigatorSettings.mSettings['services.sync.enabled'];
});
suiteTeardown(() => {
syncManager.stop();
MockNavigatorSettings.mSettings['services.sync.enabled'] =
initialSettingValue;
});
setup(() => {
updateStatePreferenceSpy = this.sinon.spy(syncManager,
'updateStatePreference');
});
teardown(() => {
updateStatePreferenceSpy.restore();
});
['enabled',
'enabling'].forEach(state => {
test(state + ' should update setting', done => {
MockNavigatorSettings.mSettings['services.sync.enabled'] = false;
syncManager.state = state;
assert.ok(updateStatePreferenceSpy.calledOnce);
assert.ok(MockNavigatorSettings.mSettings['services.sync.enabled']);
syncManager.updateStateDeferred.then(done);
window.dispatchEvent(new CustomEvent('mozPrefChromeEvent', {
detail: {
prefName: 'services.sync.enabled',
value: true
}
}));
});
});
test('disabled should update setting', done => {
MockNavigatorSettings.mSettings['services.sync.enabled'] = true;
syncManager.state = 'disabled';
assert.ok(updateStatePreferenceSpy.calledOnce);
assert.ok(!MockNavigatorSettings.mSettings['services.sync.enabled']);
syncManager.updateStateDeferred.then(done);
window.dispatchEvent(new CustomEvent('mozPrefChromeEvent', {
detail: {
prefName: 'services.sync.enabled',
value: false
}
}));
});
['errored',
'success',
'syncing'].forEach(state => {
test(state + ' should not update setting', () => {
var initialValue =
MockNavigatorSettings.mSettings['services.sync.enabled'];
syncManager.state = state;
assert.ok(updateStatePreferenceSpy.notCalled);
assert.equal(MockNavigatorSettings.mSettings['services.sync.enabled'],
initialValue);
});
});
});
suite('trySync', () => {
var syncManager;
var iacRequestStub;
var iacRequestError;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
iacRequestStub = this.sinon.stub(syncManager, 'iacRequest', () => {
return Promise.resolve(iacRequestError ? {
error: {
message: iacRequestError
}
} : undefined);
});
});
teardown(() => {
iacRequestStub.restore();
});
test('trySync success', done => {
syncManager.trySync().then(done).catch(error => {
assert.ok(false, 'Unexpected error ' + error);
done();
});
});
test('trySync error', done => {
iacRequestError = Date.now();
syncManager.trySync().then(() => {
assert.ok(false, 'Unexpected success');
done();
}).catch(error => {
assert.equal(error, iacRequestError);
done();
});
});
});
suite('Sync after enabled', () => {
var syncManager;
var registerSyncStub;
var requestStub;
suiteSetup(() => {
syncManager = BaseModule.instantiate('SyncManager');
syncManager.start();
});
suiteTeardown(() => {
syncManager.stop();
});
setup(() => {
registerSyncStub = this.sinon.stub(syncManager, 'registerSyncRequest',
() => {
return Promise.resolve();
});
requestStub = this.sinon.stub(MockService, 'request');
});
teardown(() => {
registerSyncStub.restore();
requestStub.restore();
});
test('should sync after enabled if coming from enabling', () => {
syncManager._handle_onsyncenabled({
detail: {
from: 'enabling'
}
});
setTimeout(() => {
assert.ok(requestStub.calledOnce);
});
});
});
suite('User settings', () => {
var syncManager;
var currentDeferred;
function getDeferred() {
var deferred = {};
deferred.promise = new Promise(resolve => {
deferred.resolve = resolve;
});
return deferred;
}
var _defaults = {};
var _user = {};
var promiseResolve = () => {
return Promise.resolve();
};
suiteSetup(() => {
syncManager = new SyncManager();
syncManager.start();
this.sinon.stub(syncManager, 'updateState', () => {
syncManager.updateStateDeferred = Promise.resolve();
});
this.sinon.stub(syncManager, 'getAssertion', promiseResolve);
this.sinon.stub(syncManager, 'getKeys', promiseResolve);
this.sinon.stub(syncManager, 'trySync', promiseResolve);
this.sinon.stub(syncManager, 'getAccount', promiseResolve);
this.sinon.stub(MockService, 'request', (what) => {
if (what !== 'SyncStateMachine:success') {
return;
}
currentDeferred.resolve();
});
SyncManager.SETTINGS.forEach(setting => {
_defaults[setting] = Date.now();
MockNavigatorSettings.mSettings[setting] = _defaults[setting];
syncManager._settings[setting] = _defaults[setting];
});
});
suiteTeardown(() => {
syncManager.stop();
});
// Because we are using MockNavigatorMozSettings, the settings observers
// set by BaseModule are not fired, so we need to do it manually.
function copySettings() {
SyncManager.SETTINGS.forEach(setting => {
syncManager._settings[setting] =
MockNavigatorSettings.mSettings[setting];
});
}
test('should save and restore user settings on login/logout', done => {
var user = 'user@mozilla.org';
syncManager.user = user;
syncManager._handle_onsyncenabling();
currentDeferred = getDeferred();
currentDeferred.promise.then(() => {
// Enabling should save default settings into asyncStorage.
return Promise.all(SyncManager.SETTINGS.map(setting => {
return new Promise(resolve => {
asyncStorage.getItem(setting, value => {
expect(value).to.equal(_defaults[setting]);
resolve();
});
});
}));
}).then(() => {
// Local changes to settings.
return Promise.all(SyncManager.SETTINGS.map(setting => {
return new Promise(resolve => {
_user[setting] = Date.now();
MockNavigatorSettings.mSettings[setting] = _user[setting];
syncManager._settings[setting] = _user[setting];
resolve();
});
}));
}).then(() => {
syncManager.user = user;
syncManager._handle_onsyncdisabling();
copySettings();
currentDeferred = getDeferred();
return currentDeferred.promise;
}).then(() => {
// Disabling should restore default setting from asyncStorage.
// And save user settings.
return Promise.all(SyncManager.SETTINGS.map(setting => {
return new Promise(resolve => {
asyncStorage.getItem(setting, value => {
expect(value).to.equal(null);
expect(MockNavigatorSettings.mSettings[setting])
.to.equal(_defaults[setting]);
asyncStorage.getItem(user + '.' + setting, value => {
console.log(`value ${value} == ${_user[setting]}`);
expect(value).to.equal(_user[setting]);
resolve();
});
});
});
}));
}).then(() => {
// Login with the same user should restore user settings.
syncManager.user = user;
syncManager._handle_onsyncenabling();
copySettings();
currentDeferred = getDeferred();
return currentDeferred.promise;
}).then(() => {
return Promise.all(SyncManager.SETTINGS.map(setting => {
return new Promise(resolve => {
expect(MockNavigatorSettings.mSettings[setting])
.to.equal(_user[setting]);
resolve();
});
}));
}).then(() => {
syncManager.user = user;
syncManager._handle_onsyncdisabling();
currentDeferred = getDeferred();
return currentDeferred.promise;
}).then(() => {
// Login with a new user should restore default settings.
syncManager.user = 'newuser@mozilla.org';
syncManager._handle_onsyncenabling();
copySettings();
currentDeferred = getDeferred();
return currentDeferred.promise;
}).then(() => {
return Promise.all(SyncManager.SETTINGS.map(setting => {
return new Promise(resolve => {
expect(MockNavigatorSettings.mSettings[setting])
.to.equal(_defaults[setting]);
resolve();
});
}));
}).then(() => {
done();
}).catch(error => {
assert.ok(false, error);
});
});
});
});