diff options
Diffstat (limited to 'services/fxaccounts/tests/xpcshell')
-rw-r--r-- | services/fxaccounts/tests/xpcshell/test_commands_closetab.js | 263 | ||||
-rw-r--r-- | services/fxaccounts/tests/xpcshell/test_keys.js | 12 | ||||
-rw-r--r-- | services/fxaccounts/tests/xpcshell/xpcshell.toml | 2 |
3 files changed, 277 insertions, 0 deletions
diff --git a/services/fxaccounts/tests/xpcshell/test_commands_closetab.js b/services/fxaccounts/tests/xpcshell/test_commands_closetab.js new file mode 100644 index 0000000000..447b80be94 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_commands_closetab.js @@ -0,0 +1,263 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { CloseRemoteTab } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommands.sys.mjs" +); + +const { COMMAND_CLOSETAB, COMMAND_CLOSETAB_TAIL } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +class TelemetryMock { + constructor() { + this._events = []; + this._uuid_counter = 0; + } + + recordEvent(object, method, value, extra = undefined) { + this._events.push({ object, method, value, extra }); + } + + generateFlowID() { + this._uuid_counter += 1; + return this._uuid_counter.toString(); + } + + sanitizeDeviceId(id) { + return id + "-san"; + } +} + +function FxaInternalMock() { + return { + telemetry: new TelemetryMock(), + }; +} + +function promiseObserver(topic) { + return new Promise(resolve => { + let obs = (aSubject, aTopic) => { + Services.obs.removeObserver(obs, aTopic); + resolve(aSubject); + }; + Services.obs.addObserver(obs, topic); + }); +} + +add_task(async function test_closetab_isDeviceCompatible() { + const closeTab = new CloseRemoteTab(null, null); + let device = { name: "My device" }; + Assert.ok(!closeTab.isDeviceCompatible(device)); + device = { name: "My device", availableCommands: {} }; + Assert.ok(!closeTab.isDeviceCompatible(device)); + device = { + name: "My device", + availableCommands: { + "https://identity.mozilla.com/cmd/close-uri/v1": "payload", + }, + }; + // Even though the command is available, we're keeping this feature behind a feature + // flag for now, so it should still show up as "not available" + Assert.ok(!closeTab.isDeviceCompatible(device)); + + // Enable the feature + Services.prefs.setBoolPref( + "identity.fxaccounts.commands.remoteTabManagement.enabled", + true + ); + Assert.ok(closeTab.isDeviceCompatible(device)); + + // clear it for the next test + Services.prefs.clearUserPref( + "identity.fxaccounts.commands.remoteTabManagement.enabled" + ); +}); + +add_task(async function test_closetab_send() { + const commands = { + invoke: sinon.spy((cmd, device, payload) => { + Assert.equal(payload.encrypted, "encryptedpayload"); + }), + }; + const fxai = FxaInternalMock(); + const closeTab = new CloseRemoteTab(commands, fxai); + closeTab._encrypt = async () => { + return "encryptedpayload"; + }; + const targetDevice = { id: "dev1", name: "Device 1" }; + const tab = { url: "https://foo.bar/" }; + + // We add a 0 delay so we can "send" the push immediately + closeTab.enqueueTabToClose(targetDevice, tab, 0); + + // We have a tab queued + Assert.equal(closeTab.pendingClosedTabs.get(targetDevice.id).tabs.length, 1); + + // Wait on the notification to ensure the push sent + await promiseObserver("test:fxaccounts:commands:close-uri:sent"); + + // The push has been sent, we should not have the tabs anymore + Assert.equal( + closeTab.pendingClosedTabs.has(targetDevice.id), + false, + "The device should be removed from the queue after sending." + ); + + // Telemetry shows we sent one successfully + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-sent", + method: COMMAND_CLOSETAB_TAIL, + value: "dev1-san", + // streamID uses the same generator as flowId, so it will be 2 + extra: { flowID: "1", streamID: "2" }, + }, + ]); +}); + +add_task(async function test_multiple_tabs_one_device() { + const commands = sinon.stub({ + invoke: async () => {}, + }); + const fxai = FxaInternalMock(); + const closeTab = new CloseRemoteTab(commands, fxai); + closeTab._encrypt = async () => "encryptedpayload"; + + const targetDevice = { + id: "dev1", + name: "Device 1", + availableCommands: { [COMMAND_CLOSETAB]: "payload" }, + }; + const tab1 = { url: "https://foo.bar/" }; + const tab2 = { url: "https://example.com/" }; + + closeTab.enqueueTabToClose(targetDevice, tab1, 1000); + closeTab.enqueueTabToClose(targetDevice, tab2, 0); + + // We have two tabs queued + Assert.equal(closeTab.pendingClosedTabs.get("dev1").tabs.length, 2); + + // Wait on the notification to ensure the push sent + await promiseObserver("test:fxaccounts:commands:close-uri:sent"); + + Assert.equal( + closeTab.pendingClosedTabs.has(targetDevice.id), + false, + "The device should be removed from the queue after sending." + ); + + // Telemetry shows we sent one successfully + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-sent", + method: COMMAND_CLOSETAB_TAIL, + value: "dev1-san", + extra: { flowID: "1", streamID: "2" }, + }, + ]); +}); + +add_task(async function test_timer_reset_on_new_tab() { + const commands = sinon.stub({ + invoke: async () => {}, + }); + const fxai = FxaInternalMock(); + const closeTab = new CloseRemoteTab(commands, fxai); + closeTab._encrypt = async () => "encryptedpayload"; + + const targetDevice = { + id: "dev1", + name: "Device 1", + availableCommands: { [COMMAND_CLOSETAB]: "payload" }, + }; + const tab1 = { url: "https://foo.bar/" }; + const tab2 = { url: "https://example.com/" }; + + // default wait is 6s + closeTab.enqueueTabToClose(targetDevice, tab1); + + Assert.equal(closeTab.pendingClosedTabs.get(targetDevice.id).tabs.length, 1); + + // Adds a new tab and should reset timer + closeTab.enqueueTabToClose(targetDevice, tab2, 100); + + // We have two tabs queued + Assert.equal(closeTab.pendingClosedTabs.get(targetDevice.id).tabs.length, 2); + + // Wait on the notification to ensure the push sent + await promiseObserver("test:fxaccounts:commands:close-uri:sent"); + + // We only sent one push + sinon.assert.calledOnce(commands.invoke); + Assert.equal(closeTab.pendingClosedTabs.has(targetDevice.id), false); + + // Telemetry shows we sent only one + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-sent", + method: COMMAND_CLOSETAB_TAIL, + value: "dev1-san", + extra: { flowID: "1", streamID: "2" }, + }, + ]); +}); + +add_task(async function test_multiple_devices() { + const commands = sinon.stub({ + invoke: async () => {}, + }); + const fxai = FxaInternalMock(); + const closeTab = new CloseRemoteTab(commands, fxai); + closeTab._encrypt = async () => "encryptedpayload"; + + const device1 = { + id: "dev1", + name: "Device 1", + availableCommands: { [COMMAND_CLOSETAB]: "payload" }, + }; + const device2 = { + id: "dev2", + name: "Device 2", + availableCommands: { [COMMAND_CLOSETAB]: "payload" }, + }; + const tab1 = { url: "https://foo.bar/" }; + const tab2 = { url: "https://example.com/" }; + + closeTab.enqueueTabToClose(device1, tab1, 100); + closeTab.enqueueTabToClose(device2, tab2, 200); + + Assert.equal(closeTab.pendingClosedTabs.get(device1.id).tabs.length, 1); + Assert.equal(closeTab.pendingClosedTabs.get(device2.id).tabs.length, 1); + + // observe the notification to ensure the push sent + await promiseObserver("test:fxaccounts:commands:close-uri:sent"); + + // We should have only sent the first device + sinon.assert.calledOnce(commands.invoke); + Assert.equal(closeTab.pendingClosedTabs.has(device1.id), false); + + // Wait on the notification to ensure the push sent + await promiseObserver("test:fxaccounts:commands:close-uri:sent"); + + // Now we've sent both pushes + sinon.assert.calledTwice(commands.invoke); + + // Two telemetry events to two different devices + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-sent", + method: COMMAND_CLOSETAB_TAIL, + value: "dev1-san", + extra: { flowID: "1", streamID: "2" }, + }, + { + object: "command-sent", + method: COMMAND_CLOSETAB_TAIL, + value: "dev2-san", + extra: { flowID: "3", streamID: "4" }, + }, + ]); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_keys.js b/services/fxaccounts/tests/xpcshell/test_keys.js index 9a25ca90f3..5caa4679c6 100644 --- a/services/fxaccounts/tests/xpcshell/test_keys.js +++ b/services/fxaccounts/tests/xpcshell/test_keys.js @@ -219,6 +219,18 @@ add_task(function test_check_valid_scoped_keys() { }; Assert.equal(keys.validScopedKeys(scopedKeys), true); }); + add_task(function test_valid_kid_with_dash() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + // kid contains another dash. The fingerprint must not be truncated. + kid: "1510726318123-I-Qv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), true); + }); }); add_task(async function test_rejects_bad_scoped_key_data() { diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.toml b/services/fxaccounts/tests/xpcshell/xpcshell.toml index 7fc9c60006..09469fc0b4 100644 --- a/services/fxaccounts/tests/xpcshell/xpcshell.toml +++ b/services/fxaccounts/tests/xpcshell/xpcshell.toml @@ -20,6 +20,8 @@ support-files = [ ["test_commands.js"] +["test_commands_closetab.js"] + ["test_credentials.js"] ["test_device.js"] |