VulpesOS/gaia/apps/findmydevice/test/unit/findmydevice_commands_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

506 lines
18 KiB
JavaScript
Executable file

/* global MocksHelper */
/* global MockSettingsListener */
/* global MockGeolocation */
/* global Commands */
/* global FindMyDevice */
/* global MockNavigatorSettings */
/* global PasscodeHelper */
'use strict';
require('/shared/test/unit/mocks/mocks_helper.js');
require('/shared/test/unit/mocks/mock_settings_url.js');
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
require('/shared/test/unit/mocks/mock_settings_listener.js');
require('/shared/test/unit/mocks/mock_settings_helper.js');
require('/shared/test/unit/mocks/mock_audio.js');
require('/shared/test/unit/mocks/mock_device_storage.js');
require('/shared/test/unit/mocks/mock_geolocation.js');
require('/shared/js/passcode_helper.js');
var mocksForFindMyDevice = new MocksHelper([
'SettingsListener', 'SettingsURL', 'SettingsHelper', 'Audio',
'DeviceStorage', 'Geolocation'
]).init();
suite('FindMyDevice >', function() {
var realMozPower;
var realKillSwitch;
var realMozApps;
var fakeClock;
var realMozSettings;
mocksForFindMyDevice.attachTestHelpers();
var subject;
setup(function(done) {
realMozPower = navigator.mozPower;
navigator.mozPower = {
factoryReset: function(reason) {},
};
this.sinon.spy(navigator.mozPower, 'factoryReset');
realKillSwitch = navigator.mozKillSwitch;
navigator.mozKillSwitch = {
enable: function() {}
};
realMozApps = navigator.mozApps;
navigator.mozApps = {
getSelf: function() {
var app = {
manifestURL: 'app://findmydevice.gaiamobile.org/manifest.webapp',
origin: 'app://findmydevice.gaiamobile.org/'
};
var request = {result: app};
setTimeout(function() {
request.onsuccess.call(request);
});
return request;
}
};
// replace shared/js/dump.js
window.DUMP = function() {};
fakeClock = this.sinon.useFakeTimers();
realMozSettings = navigator.mozSettings;
navigator.mozSettings = MockNavigatorSettings;
window.FindMyDevice = {
beginHighPriority: this.sinon.stub(),
endHighPriority: this.sinon.stub()
};
require('/js/commands.js', function() {
subject = Commands;
done();
});
});
suite('Ring command', function() {
var duration = 2;
setup(function() {
this.sinon.spy(window, 'setTimeout');
this.sinon.spy(window, 'clearTimeout');
});
test('Basic invocation', function() {
var ringtone = 'user selected ringtone';
MockSettingsListener.mCallbacks['dialer.ringtone'](ringtone);
Commands.invokeCommand('ring', [duration, function(retval) {
var lock = MockSettingsListener.getSettingsLock().locks.pop();
var channel = Commands._ringer.mozAudioChannelType;
assert.equal(channel, 'ringer', 'use notification channel');
assert.equal(lock['audio.volume.notification'], 15,
'volume set to maximum');
assert.equal(Commands._ringer.paused, false, 'must be playing');
assert.equal(Commands._ringer.src, ringtone, 'must use ringtone');
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
}]);
fakeClock.tick(duration * 1000);
assert.equal(Commands._ringer.paused, true, 'must have stopped');
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
});
test('Stop ringing if duration is zero', function() {
Commands.invokeCommand('ring', [duration, function(retval) {
assert.equal(Commands._ringer.paused, false, 'must be playing');
sinon.assert.called(window.setTimeout);
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
sinon.assert.notCalled(FindMyDevice.endHighPriority);
}]);
fakeClock.tick(duration/2 * 1000);
var timeoutId = Commands._ringTimeoutId;
assert.isFalse(Commands._ringer.paused, 'must not be done ringing');
Commands.invokeCommand('ring', [0, function(retval) {}]);
assert.equal(Commands._ringer.paused, true, 'must have stopped');
assert.isNull(Commands._ringTimeoutId, 'timeout must have been canceled');
sinon.assert.calledWith(window.clearTimeout, timeoutId);
sinon.assert.calledTwice(FindMyDevice.beginHighPriority);
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
sinon.assert.calledTwice(FindMyDevice.endHighPriority);
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
});
test('Should do nothing if already ringing', function() {
Commands.invokeCommand('ring', [duration, function(retval) {
assert.isFalse(Commands._ringer.paused, 'must be playing');
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
sinon.assert.notCalled(FindMyDevice.endHighPriority);
}]);
fakeClock.tick(duration/2 * 1000);
assert.isFalse(Commands._ringer.paused, 'must not be done ringing');
Commands.invokeCommand('ring', [duration, function(retval) {}]);
fakeClock.tick(duration/2 * 1000);
assert.isTrue(Commands._ringer.paused, 'must have stopped');
assert.isNull(Commands._ringTimeoutId, 'no timeouts should be set');
sinon.assert.calledTwice(FindMyDevice.beginHighPriority);
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
sinon.assert.calledTwice(FindMyDevice.endHighPriority);
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
});
test('Should do nothing if not ringing and duration is zero', function() {
Commands.invokeCommand('ring', [0, function(retval) {}]);
assert.isTrue(Commands._ringer.paused, 'must not have started');
assert.isNull(Commands._ringTimeoutId);
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
});
});
test('Erase: ensure factoryReset is called with "wipe"', function(done) {
subject.invokeCommand('erase', [function(retval, error) {
assert.equal(navigator.mozPower.factoryReset.calledWith('wipe'), true);
done();
}]);
});
suite('Track command', function() {
test('Track receives positions correctly', function() {
// track for a period of time
var duration = (subject.TRACK_UPDATE_INTERVAL_MS) / 1000;
subject.invokeCommand('track', [duration, function(retval, position) {
assert.equal(retval, true);
// ensure, while tracking, the locations match what the mock makes
assert.deepEqual(position, MockGeolocation.fakePosition ,
'check the position is what the mock provided');
}]);
fakeClock.tick(2* subject.TRACK_UPDATE_INTERVAL_MS);
});
test('Track command', function() {
// track for 3 intervals
var duration = (3 * subject.TRACK_UPDATE_INTERVAL_MS) / 1000;
subject.invokeCommand('track', [duration, function(retval, position) {}]);
// after 2 intervals...
fakeClock.tick(2 * subject.TRACK_UPDATE_INTERVAL_MS);
// we're mid-track, check we have a watch and timeout IDs
assert.notEqual(subject._watchPositionId, null);
assert.notEqual(subject._trackTimeoutId, null);
// after another interval...
fakeClock.tick(1 * subject.TRACK_UPDATE_INTERVAL_MS);
// .. tracking should have stopped.
// ensure we no longer have track and watch IDs.
assert.equal(subject._trackTimeoutId, null);
assert.equal(subject._watchPositionId, null);
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
// check wakelock count
var begin = FindMyDevice.beginHighPriority.callCount;
var end = FindMyDevice.beginHighPriority.callCount;
assert.equal(begin, end, 'begin and end count should match');
});
// We assume for this test that the interval used by the geolocation mock
// is significantly smaller than TRACK_UPDATE_INTERVAL.
test('Track command should throttle observed positions', function() {
// track for a long duration (e.g. 10 intervals)
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
var positions = 0;
subject.invokeCommand('track', [duration, function(retval, position) {
positions++;
}]);
// set the clock to just before the interval, ensure no locations are seen
var amountBefore = 2;
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS - amountBefore);
assert.equal(positions, 0, 'no update intervals should have passed');
// set the clock to almost two intervals, ensure only one location is seen
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
assert.equal(positions, 1, '1 update interval should have passed');
});
test('Track command should update its duration if invoked while running',
function() {
// track for a long duration (e.g. 10 intervals)
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
var positions = 0;
subject.invokeCommand('track', [duration, function(retval, position) {
positions++;
}]);
// ensure the position count updates as expected (one position per
// interval)
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
assert.equal(positions, 1, '1 update interval should have passed');
// reset the duration to a shorter value:
duration = 2 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
subject.invokeCommand('track', [duration, function(retval, position) {
positions++;
}]);
// allow the timer to progress to after the duration completes
fakeClock.tick(5 * subject.TRACK_UPDATE_INTERVAL_MS);
// ensure tracking stopped at the end of the new duration:
// position count should be at 3
assert.equal(positions, 3, '2 more (3) intervals should have passed');
// and the track and watch IDs should be null
assert.equal(subject._trackTimeoutId, null);
assert.equal(subject._watchPositionId, null);
// check wakelock count
var begin = FindMyDevice.beginHighPriority.callCount;
var end = FindMyDevice.beginHighPriority.callCount;
assert.equal(begin, end, 'begin and end count should match');
});
test('Track command should stop if duration is zero',
function() {
// track for a long duration (e.g. 10 intervals)
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
var positions = 0;
subject.invokeCommand('track', [duration, function(retval, position) {
positions++;
}]);
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
// after a single interval, stop the tracking
subject.invokeCommand('track', [0, function(retval) {
assert.equal(retval, true);
}]);
// allow more intervals to pass
fakeClock.tick(2 * subject.TRACK_UPDATE_INTERVAL_MS);
// and ensure the position count remains at 1
assert.equal(positions, 1);
// check the track and watch IDs should be null
assert.equal(subject._trackTimeoutId, null);
assert.equal(subject._watchPositionId, null);
});
test('track should not leak wakelocks with nonzero duration', function() {
// start tracking
var duration = 9 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
subject.invokeCommand('track', [duration, function() {}]);
// once tracking is complete, check the wakelock call counts match
fakeClock.tick(10 * subject.TRACK_UPDATE_INTERVAL_MS);
var begin = FindMyDevice.beginHighPriority.callCount;
var end = FindMyDevice.beginHighPriority.callCount;
assert.equal(begin, end, 'begin and end count should match');
});
test('track should not leak wakelocks with zero duration', function() {
subject.invokeCommand('track', [0, function() {}]);
var begin = FindMyDevice.beginHighPriority.callCount;
var end = FindMyDevice.beginHighPriority.callCount;
assert.equal(begin, end, 'begin and end count should match');
});
test('track should not leak wakelocks if stopped mid-track', function() {
// track for a long duration (e.g. 10 intervals)
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
subject.invokeCommand('track', [duration, function() {}]);
// after a single interval, stop the tracking
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
subject.invokeCommand('track', [0, function(retval) {}]);
// go past the original duration
fakeClock.tick(10 * subject.TRACK_UPDATE_INTERVAL_MS);
// check the wakelock call counts
var begin = FindMyDevice.beginHighPriority.callCount;
var end = FindMyDevice.beginHighPriority.callCount;
assert.equal(begin, end, 'begin and end count should match');
});
});
test('Bug 1027325 - correctly check that passcode lock is set', function() {
MockSettingsListener.mTriggerCallback('lockscreen.enabled', true);
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
'1234');
assert.equal(true, subject.deviceHasPasscode());
});
test('Bug 1027325 - correctly check that passcode lock is unset', function() {
MockSettingsListener.mTriggerCallback('lockscreen.enabled', false);
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
false);
assert.equal(false, subject.deviceHasPasscode());
});
test('Bug 1027325 - correctly check that lockscreen is set, but passcode ' +
'lock is unset', function() {
MockSettingsListener.mTriggerCallback('lockscreen.enabled', true);
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
false);
assert.equal(false, subject.deviceHasPasscode());
});
suite('KillSwitch command', function() {
suite('With API existing', function() {
setup(function() {
this.sinon.spy(navigator.mozKillSwitch, 'enable');
});
test('Basic invocation', function(done) {
subject.invokeCommand('killswitch', [ function(retval, error) {
sinon.assert.calledOnce(navigator.mozKillSwitch.enable);
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
} ]);
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
done();
});
});
suite('Without API existing', function() {
setup(function() {
navigator.mozKillSwitch = undefined;
});
test('No invocation', function(done) {
subject.invokeCommand('killswitch', [ function(retval, error) {
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
} ]);
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
done();
});
});
});
test('List of accepted commands', function() {
MockSettingsListener.mTriggerCallback('geolocation.enabled', true);
var allCommands = ['track', 'erase', 'ring', 'lock', 'killswitch'];
var enabledCommands = subject.getEnabledCommands();
assert.deepEqual(enabledCommands.sort(), allCommands.sort());
// track should be disabled when geolocation is disabled
MockSettingsListener.mTriggerCallback('geolocation.enabled', false);
allCommands = ['erase', 'ring', 'lock', 'killswitch'];
enabledCommands = subject.getEnabledCommands();
assert.deepEqual(enabledCommands.sort(), allCommands.sort());
});
teardown(function() {
navigator.mozPower = realMozPower;
navigator.mozKillSwitch = realKillSwitch;
navigator.mozApps = realMozApps;
navigator.mozSettings = realMozSettings;
delete window.DUMP;
fakeClock.restore();
delete window.FindMyDevice;
});
});
suite('FindMyDevice (with real clock) >', function() {
var realMozSettings;
mocksForFindMyDevice.attachTestHelpers();
var subject;
setup(function(done) {
// replace shared/js/dump.js
window.DUMP = function() {};
realMozSettings = navigator.mozSettings;
MockNavigatorSettings.mSetup();
navigator.mozSettings = MockNavigatorSettings;
window.FindMyDevice = {
beginHighPriority: this.sinon.stub(),
endHighPriority: this.sinon.stub()
};
require('/js/commands.js', function() {
subject = Commands;
done();
});
});
test('Lock command', function(done) {
var code = '1234', message = 'locked!';
var DIGEST_VALUE = 'lockscreen.passcode-lock.digest.value';
var settingsLock = MockSettingsListener.getSettingsLock();
var oldDigest = settingsLock.get(DIGEST_VALUE);
subject.invokeCommand('lock', [message, code, function(retval) {
assert.equal(retval, true);
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
var lock = MockSettingsListener.getSettingsLock().locks.pop();
assert.deepEqual({
'lockscreen.enabled': true,
'lockscreen.notifications-preview.enabled': false,
'lockscreen.passcode-lock.enabled': true,
'lockscreen.lock-message': message,
'lockscreen.lock-immediately': true
}, lock, 'check that the correct settings were set');
assert.notEqual(oldDigest, lock[DIGEST_VALUE],
'check that Passcode digest changed');
done();
}]);
});
test('Bug 1201530 - check existing passcode override', function() {
var code = '2342', message = 'already locked!';
var stubPasscodeSet = sinon.stub(PasscodeHelper, 'set', function() {
return Promise.resolve(true);
});
// Simulate already-locked device.
MockSettingsListener.mTriggerCallback('lockscreen.enabled', true);
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
true);
subject.invokeCommand('lock', [message, code, function() {}]);
assert.isTrue(stubPasscodeSet.calledWith(code),
'existing passcode is not overridden');
stubPasscodeSet.restore();
});
teardown(function() {
// clean up sinon.js stubs
navigator.mozSettings = realMozSettings;
MockNavigatorSettings.mTeardown();
delete window.DUMP;
delete window.FindMyDevice;
});
});