/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { FxAccounts } = ChromeUtils.importESModule( "resource://gre/modules/FxAccounts.sys.mjs" ); const { FxAccountsClient } = ChromeUtils.importESModule( "resource://gre/modules/FxAccountsClient.sys.mjs" ); // We grab some additional stuff via backstage passes. var { AccountState } = ChromeUtils.importESModule( "resource://gre/modules/FxAccounts.sys.mjs" ); function promiseNotification(topic) { return new Promise(resolve => { let observe = () => { Services.obs.removeObserver(observe, topic); resolve(); }; Services.obs.addObserver(observe, topic); }); } // A storage manager that doesn't actually write anywhere. function MockStorageManager() {} MockStorageManager.prototype = { promiseInitialized: Promise.resolve(), initialize(accountData) { this.accountData = accountData; }, finalize() { return Promise.resolve(); }, getAccountData() { return Promise.resolve(this.accountData); }, updateAccountData(updatedFields) { for (let [name, value] of Object.entries(updatedFields)) { if (value == null) { delete this.accountData[name]; } else { this.accountData[name] = value; } } return Promise.resolve(); }, deleteAccountData() { this.accountData = null; return Promise.resolve(); }, }; // Just enough mocks so we can avoid hawk etc. function MockFxAccountsClient() { this._email = "nobody@example.com"; this._verified = false; this.accountStatus = function (uid) { return Promise.resolve(!!uid && !this._deletedOnServer); }; this.signOut = function () { return Promise.resolve(); }; this.registerDevice = function () { return Promise.resolve(); }; this.updateDevice = function () { return Promise.resolve(); }; this.signOutAndDestroyDevice = function () { return Promise.resolve(); }; this.getDeviceList = function () { return Promise.resolve(); }; FxAccountsClient.apply(this); } MockFxAccountsClient.prototype = {}; Object.setPrototypeOf( MockFxAccountsClient.prototype, FxAccountsClient.prototype ); function MockFxAccounts(device = {}) { return new FxAccounts({ fxAccountsClient: new MockFxAccountsClient(), newAccountState(credentials) { // we use a real accountState but mocked storage. let storage = new MockStorageManager(); storage.initialize(credentials); return new AccountState(storage); }, _getDeviceName() { return "mock device name"; }, fxaPushService: { registerPushEndpoint() { return new Promise(resolve => { resolve({ endpoint: "http://mochi.test:8888", }); }); }, }, }); } async function createMockFxA() { let fxa = new MockFxAccounts(); let credentials = { email: "foo@example.com", uid: "1234@lcip.org", sessionToken: "dead", kSync: "beef", kXCS: "cafe", kExtSync: "bacon", kExtKbHash: "cheese", verified: true, }; await fxa._internal.setSignedInUser(credentials); return fxa; } // The tests. add_task(async function testCacheStorage() { let fxa = await createMockFxA(); // Hook what the impl calls to save to disk. let cas = fxa._internal.currentAccountState; let origPersistCached = cas._persistCachedTokens.bind(cas); cas._persistCachedTokens = function () { return origPersistCached().then(() => { Services.obs.notifyObservers(null, "testhelper-fxa-cache-persist-done"); }); }; let promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done"); let tokenData = { token: "token1", somethingelse: "something else" }; let scopeArray = ["foo", "bar"]; cas.setCachedToken(scopeArray, tokenData); deepEqual(cas.getCachedToken(scopeArray), tokenData); deepEqual(cas.oauthTokens, { "bar|foo": tokenData }); // wait for background write to complete. await promiseWritten; // Check the token cache made it to our mocked storage. deepEqual(cas.storageManager.accountData.oauthTokens, { "bar|foo": tokenData, }); // Drop the token from the cache and ensure it is removed from the json. promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done"); await cas.removeCachedToken("token1"); deepEqual(cas.oauthTokens, {}); await promiseWritten; deepEqual(cas.storageManager.accountData.oauthTokens, {}); // sign out and the token storage should end up with null. let storageManager = cas.storageManager; // .signOut() removes the attribute. await fxa.signOut(/* localOnly = */ true); deepEqual(storageManager.accountData, null); });