/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Tests for the FxA push service. /* eslint-disable mozilla/use-chromeutils-generateqi */ const { FXA_PUSH_SCOPE_ACCOUNT_UPDATE, ONLOGOUT_NOTIFICATION, ON_ACCOUNT_DESTROYED_NOTIFICATION, ON_DEVICE_CONNECTED_NOTIFICATION, ON_DEVICE_DISCONNECTED_NOTIFICATION, ON_PASSWORD_CHANGED_NOTIFICATION, ON_PASSWORD_RESET_NOTIFICATION, ON_PROFILE_CHANGE_NOTIFICATION, ON_PROFILE_UPDATED_NOTIFICATION, ON_VERIFY_LOGIN_NOTIFICATION, log, } = ChromeUtils.importESModule( "resource://gre/modules/FxAccountsCommon.sys.mjs" ); const { FxAccountsPushService } = ChromeUtils.importESModule( "resource://gre/modules/FxAccountsPush.sys.mjs" ); XPCOMUtils.defineLazyServiceGetter( this, "PushService", "@mozilla.org/push/Service;1", "nsIPushService" ); initTestLogging("Trace"); log.level = Log.Level.Trace; const MOCK_ENDPOINT = "http://mochi.test:8888"; // tests do not allow external connections, mock the PushService let mockPushService = { pushTopic: PushService.pushTopic, subscriptionChangeTopic: PushService.subscriptionChangeTopic, subscribe(scope, principal, cb) { cb(Cr.NS_OK, { endpoint: MOCK_ENDPOINT, }); }, unsubscribe(scope, principal, cb) { cb(Cr.NS_OK, true); }, }; let mockFxAccounts = { checkVerificationStatus() {}, updateDeviceRegistration() {}, }; let mockLog = { trace() {}, debug() {}, warn() {}, error() {}, }; add_task(async function initialize() { let pushService = new FxAccountsPushService(); equal(pushService.initialize(), false); }); add_task(async function registerPushEndpointSuccess() { let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: mockFxAccounts, }); let subscription = await pushService.registerPushEndpoint(); equal(subscription.endpoint, MOCK_ENDPOINT); }); add_task(async function registerPushEndpointFailure() { let failPushService = Object.assign(mockPushService, { subscribe(scope, principal, cb) { cb(Cr.NS_ERROR_ABORT); }, }); let pushService = new FxAccountsPushService({ pushService: failPushService, fxai: mockFxAccounts, }); let subscription = await pushService.registerPushEndpoint(); equal(subscription, null); }); add_task(async function unsubscribeSuccess() { let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: mockFxAccounts, }); let result = await pushService.unsubscribe(); equal(result, true); }); add_task(async function unsubscribeFailure() { let failPushService = Object.assign(mockPushService, { unsubscribe(scope, principal, cb) { cb(Cr.NS_ERROR_ABORT); }, }); let pushService = new FxAccountsPushService({ pushService: failPushService, fxai: mockFxAccounts, }); let result = await pushService.unsubscribe(); equal(result, null); }); add_test(function observeLogout() { let customLog = Object.assign(mockLog, { trace(msg) { if (msg === "FxAccountsPushService unsubscribe") { // logout means we unsubscribe run_next_test(); } }, }); let pushService = new FxAccountsPushService({ pushService: mockPushService, log: customLog, }); pushService.observe(null, ONLOGOUT_NOTIFICATION); }); add_test(function observePushTopicVerify() { let emptyMsg = { QueryInterface() { return this; }, }; let customAccounts = Object.assign(mockFxAccounts, { checkVerificationStatus() { // checking verification status on push messages without data run_next_test(); }, }); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: customAccounts, }); pushService.observe( emptyMsg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_test(function observePushTopicDeviceConnected() { let msg = { data: { json: () => ({ command: ON_DEVICE_CONNECTED_NOTIFICATION, data: { deviceName: "My phone", }, }), }, QueryInterface() { return this; }, }; let obs = (subject, topic, data) => { Services.obs.removeObserver(obs, topic); run_next_test(); }; Services.obs.addObserver(obs, ON_DEVICE_CONNECTED_NOTIFICATION); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: mockFxAccounts, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_task(async function observePushTopicDeviceDisconnected_current_device() { const deviceId = "bogusid"; let msg = { data: { json: () => ({ command: ON_DEVICE_DISCONNECTED_NOTIFICATION, data: { id: deviceId, }, }), }, QueryInterface() { return this; }, }; let signoutCalled = false; let { FxAccounts } = ChromeUtils.importESModule( "resource://gre/modules/FxAccounts.sys.mjs" ); const fxAccountsMock = new FxAccounts({ newAccountState() { return { async getUserAccountData() { return { device: { id: deviceId } }; }, }; }, signOut() { signoutCalled = true; }, })._internal; const deviceDisconnectedNotificationObserved = new Promise(resolve => { Services.obs.addObserver(function obs(subject, topic, data) { Services.obs.removeObserver(obs, topic); equal(data, JSON.stringify({ isLocalDevice: true })); resolve(); }, ON_DEVICE_DISCONNECTED_NOTIFICATION); }); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: fxAccountsMock, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); await deviceDisconnectedNotificationObserved; ok(signoutCalled); }); add_task(async function observePushTopicDeviceDisconnected_another_device() { const deviceId = "bogusid"; let msg = { data: { json: () => ({ command: ON_DEVICE_DISCONNECTED_NOTIFICATION, data: { id: deviceId, }, }), }, QueryInterface() { return this; }, }; let signoutCalled = false; let { FxAccounts } = ChromeUtils.importESModule( "resource://gre/modules/FxAccounts.sys.mjs" ); const fxAccountsMock = new FxAccounts({ newAccountState() { return { async getUserAccountData() { return { device: { id: "thelocaldevice" } }; }, }; }, signOut() { signoutCalled = true; }, })._internal; const deviceDisconnectedNotificationObserved = new Promise(resolve => { Services.obs.addObserver(function obs(subject, topic, data) { Services.obs.removeObserver(obs, topic); equal(data, JSON.stringify({ isLocalDevice: false })); resolve(); }, ON_DEVICE_DISCONNECTED_NOTIFICATION); }); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: fxAccountsMock, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); await deviceDisconnectedNotificationObserved; ok(!signoutCalled); }); add_test(function observePushTopicAccountDestroyed() { const uid = "bogusuid"; let msg = { data: { json: () => ({ command: ON_ACCOUNT_DESTROYED_NOTIFICATION, data: { uid, }, }), }, QueryInterface() { return this; }, }; let customAccounts = Object.assign(mockFxAccounts, { _handleAccountDestroyed() { // checking verification status on push messages without data run_next_test(); }, }); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: customAccounts, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_test(function observePushTopicVerifyLogin() { let url = "http://localhost/newLogin"; let title = "bogustitle"; let body = "bogusbody"; let msg = { data: { json: () => ({ command: ON_VERIFY_LOGIN_NOTIFICATION, data: { body, title, url, }, }), }, QueryInterface() { return this; }, }; let obs = (subject, topic, data) => { Services.obs.removeObserver(obs, topic); Assert.equal(data, JSON.stringify(msg.data.json().data)); run_next_test(); }; Services.obs.addObserver(obs, ON_VERIFY_LOGIN_NOTIFICATION); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: mockFxAccounts, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_test(function observePushTopicProfileUpdated() { let msg = { data: { json: () => ({ command: ON_PROFILE_UPDATED_NOTIFICATION, }), }, QueryInterface() { return this; }, }; let obs = (subject, topic, data) => { Services.obs.removeObserver(obs, topic); run_next_test(); }; Services.obs.addObserver(obs, ON_PROFILE_CHANGE_NOTIFICATION); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: mockFxAccounts, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_test(function observePushTopicPasswordChanged() { let msg = { data: { json: () => ({ command: ON_PASSWORD_CHANGED_NOTIFICATION, }), }, QueryInterface() { return this; }, }; let pushService = new FxAccountsPushService({ pushService: mockPushService, }); pushService._onPasswordChanged = function () { run_next_test(); }; pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_test(function observePushTopicPasswordReset() { let msg = { data: { json: () => ({ command: ON_PASSWORD_RESET_NOTIFICATION, }), }, QueryInterface() { return this; }, }; let pushService = new FxAccountsPushService({ pushService: mockPushService, }); pushService._onPasswordChanged = function () { run_next_test(); }; pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); }); add_task(async function commandReceived() { let msg = { data: { json: () => ({ command: "fxaccounts:command_received", data: { url: "https://api.accounts.firefox.com/auth/v1/account/device/commands?index=42&limit=1", }, }), }, QueryInterface() { return this; }, }; let fxAccountsMock = {}; const promiseConsumeRemoteMessagesCalled = new Promise(res => { fxAccountsMock.commands = { pollDeviceCommands() { res(); }, }; }); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: fxAccountsMock, }); pushService.observe( msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); await promiseConsumeRemoteMessagesCalled; }); add_test(function observeSubscriptionChangeTopic() { let customAccounts = Object.assign(mockFxAccounts, { updateDeviceRegistration() { // subscription change means updating the device registration run_next_test(); }, }); let pushService = new FxAccountsPushService({ pushService: mockPushService, fxai: customAccounts, }); pushService.observe( null, mockPushService.subscriptionChangeTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE ); });