'use strict'; /* global AdvancedTelemetry, MockasyncStorage, MockNavigatorSettings, Gzip, AdvancedTelemetryPing, MockLazyLoader */ require('/shared/js/settings_listener.js'); require('/shared/js/uuid.js'); require('/shared/js/telemetry.js'); require('/shared/test/unit/mocks/mock_navigator_moz_settings.js'); require('/shared/js/gzip/gzip.js'); requireApp('system/test/unit/mock_asyncStorage.js'); requireApp('system/js/advanced_telemetry.js'); requireApp('system/js/app_usage_metrics.js'); require('/shared/test/unit/mocks/mock_lazy_loader.js'); suite('AdvancedTelemetry:', function() { var realMozSettings, realOnLine, realPerformanceNow; var isOnLine = true; var payloadOld = { keyedHistograms: { DEV_TOOLS_MEMORY: { verticalhome: { min: 1, max: 1000, histogram_type: 0, sum: 58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] }, settings: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] } }, DEV_TOOLS_STARTUP: { verticalhome: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] }, settings: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] } } }, addonHistograms: { addon1: { COMM_SMS: { min: 1, max: 1000, histogram_type: 0, sum: 58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0, 1, 2, 5, 12, 29, 70, 170, 412, 1000], counts: [0, 0, 0, 0, 4, 0, 0, 0, 0, 0] } } } }; var payloadNew = { keyedHistograms: { DEV_TOOLS_MEMORY: { verticalhome: { min: 1, max: 1000, histogram_type: 0, sum: 58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] }, settings: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] }, calendar: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] } }, DEV_TOOLS_STARTUP: { verticalhome: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] }, settings: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] } }, DEV_TOOLS_JANK: { verticalhome: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] }, settings: { min: 1, max: 1000, histogram_type:0, sum:58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0] } } }, addonHistograms: { addon1: { COMM_SMS: { min: 1, max: 1000, histogram_type: 0, sum: 58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0, 1, 2, 5, 12, 29, 70, 170, 412, 1000], counts: [0, 0, 0, 0, 4, 0, 0, 0, 0, 0] } }, addon2: { COMM_DIALER: { min: 1, max: 1000, histogram_type: 0, sum: 58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0, 1, 2, 5, 12, 29, 70, 170, 412, 1000], counts: [0, 0, 0, 0, 4, 0, 0, 0, 0, 0] } } } }; var mergedPayload = { keyedHistograms:{ DEV_TOOLS_MEMORY:{ verticalhome:{ min:1,max:1000,histogram_type:0, sum:116, log_sum:21.871920108795166, log_sum_squares:59.90799617767334, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,8,0,0,0,0,0]}, settings:{ min:1,max:1000,histogram_type:0, sum:116, log_sum:21.871920108795166, log_sum_squares:59.90799617767334, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,8,0,0,0,0,0]}, calendar:{ min:1,max:1000,histogram_type:0, sum:58, log_sum:10.935960054397583, log_sum_squares:29.95399808883667, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0]} }, DEV_TOOLS_STARTUP:{ verticalhome:{ min:1,max:1000,histogram_type:0, sum:116, log_sum:21.871920108795166, log_sum_squares:59.90799617767334, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,8,0,0,0,0,0]}, settings:{ min:1,max:1000,histogram_type:0, sum:116, log_sum:21.871920108795166, log_sum_squares:59.90799617767334, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,8,0,0,0,0,0]} }, DEV_TOOLS_JANK:{ verticalhome:{ min:1, max:1000,histogram_type:0, sum:58, log_sum:10.935960054397583, log_sum_squares:29.95399808883667, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0]}, settings:{ min:1,max:1000,histogram_type:0, sum:58, log_sum:10.935960054397583, log_sum_squares:29.95399808883667, ranges:[0,1,2,5,12,29,70,170,412,1000], counts:[0,0,0,0,4,0,0,0,0,0]} } }, addonHistograms:{ addon1: { COMM_SMS: { min: 1, max: 1000, histogram_type: 0, sum: 116, log_sum: 21.871920108795166, log_sum_squares: 59.90799617767334, ranges: [0, 1, 2, 5, 12, 29, 70, 170, 412, 1000], counts: [0, 0, 0, 0, 8, 0, 0, 0, 0, 0]} }, addon2: { COMM_DIALER: { min: 1, max: 1000, histogram_type: 0, sum: 58, log_sum: 10.935960054397583, log_sum_squares: 29.95399808883667, ranges: [0, 1, 2, 5, 12, 29, 70, 170, 412, 1000], counts: [0, 0, 0, 0, 4, 0, 0, 0, 0, 0]} } } }; function navigatorOnLine() { return isOnLine; } function setNavigatorOnLine(value) { isOnLine = value; } suiteSetup(function() { realMozSettings = navigator.mozSettings; navigator.mozSettings = MockNavigatorSettings; window.asyncStorage = MockasyncStorage; window.LazyLoader = MockLazyLoader; realOnLine = Object.getOwnPropertyDescriptor(navigator, 'onLine'); Object.defineProperty(navigator, 'onLine', { configurable: true, get: navigatorOnLine, set: setNavigatorOnLine }); navigator.addIdleObserver = function(o) { setTimeout(function() { o.onidle(); }, o.time * 1000); }; navigator.removeIdleObserver = function() {}; realPerformanceNow = window.performance.now; window.performance.now = function() { return Date.now(); }; AdvancedTelemetry.DEBUG = false; AdvancedTelemetry.LOGINFO = false; }); suiteTeardown(function() { navigator.mozSettings = realMozSettings; delete window.asyncStorage; if (realOnLine) { Object.defineProperty(navigator, 'onLine', realOnLine); } else { delete navigator.onLine; } window.performance.now = realPerformanceNow; }); suite('Sending the Metrics:', function() { var at, clock, XHR, xhr, mockSettings; var transmitSpy; var wrapper, packedWrapper; var gzipCompressedData = new Uint8Array([1,2,3,4,5]); setup(function(done) { wrapper = { type: AdvancedTelemetry.REASON, id: 'uuid', creationDate: 'testDate', version: AdvancedTelemetry.TELEMETRY_VERSION, application: { architecture: 'arm', buildId: 'build', name: AdvancedTelemetry.TELEMETRY_APP_NAME, version: '43', vendor: 'Mozilla', platformVersion: '43', xpcomAbi: 'arm-gcc3', channel: 'default' }, clientId: 'uuid', payload: { keyedHistograms: { DEVTOOLS_HUD_REFLOW_DURATION: { verticalhome: { min: 1, max: 1000, histogram_type: 0, sum: 189, log_sum: 14.329224586486816, log_sum_squares: 53.6346640586853, ranges: [0, 1, 2, 5, 12, 29, 70, 170, 412, 1000], counts: [0, 0, 0, 0, 2, 1, 1, 0, 0, 0] } } }, addonHistograms: { addon3: { rn_metric: { min: 1, max: 10000, histogram_type: 1, sum: 99, sum_squares_lo: 9801, sum_squares_hi: 0, ranges: [0, 1, 1251, 2501, 3751, 5001, 6250, 7500, 8750, 10000], counts: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] } } } } }; packedWrapper = { type: AdvancedTelemetry.REASON, id: 'uuid', creationDate: 'testDate', version: AdvancedTelemetry.TELEMETRY_VERSION, application: { architecture: 'arm', buildId: 'build', name: AdvancedTelemetry.TELEMETRY_APP_NAME, version: '43', vendor: 'Mozilla', platformVersion: '43', xpcomAbi: 'arm-gcc3', channel: 'default' }, clientId: 'uuid', payload: { keyedHistograms: { DEVTOOLS_HUD_REFLOW_DURATION: { verticalhome: { range: [1, 1000], bucket_count:10, histogram_type: 0, values: {'5':0, '12':2, '29':1, '70':1, '170':0}, sum: 189, log_sum:14.329224586486816, log_sum_squares: 53.6346640586853 } } }, addonHistograms: { addon3: { rn_metric: { range: [1, 10000], bucket_count: 10, histogram_type: 1, values: {'0': 0, '1': 1, '1251': 0}, sum: 99, sum_squares_lo: 9801, sum_squares_hi: 0 } } } } }; transmitSpy = this.sinon.spy(AdvancedTelemetry.prototype, 'transmit'); clock = this.sinon.useFakeTimers(); XHR = sinon.useFakeXMLHttpRequest(); XHR.onCreate = function(instance) { xhr = instance; }; MockNavigatorSettings.mSetup(); MockNavigatorSettings.mSyncRepliesOnly = true; mockSettings = MockNavigatorSettings.mSettings; this.sinon.stub(window, 'uuid', function() { return 'uuid'; }); this.sinon.stub(Date.prototype, 'toISOString').returns('testDate'); mockSettings['deviceinfo.platform_build_id'] = 'build'; mockSettings['deviceinfo.platform_version'] = '43'; mockSettings['app.update.channel'] = 'default'; mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'Enhanced'; at = new AdvancedTelemetry(); at.start(); at.startCollecting(); done(); }); teardown(function() { transmitSpy.restore(); clock.restore(); window.uuid.restore(); at.stop(); XHR.restore(); MockasyncStorage.mTeardown(); MockNavigatorSettings.mTeardown(); }); function dispatch(detail) { window.dispatchEvent(new CustomEvent('advanced-telemetry-update', {detail: detail})); clock.tick(); } test('advanced telemetry update should call transmit', function() { this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); at.handleGeckoMessage(payloadNew.payload, function() { MockNavigatorSettings.mReplyToRequests(); assert.equal(transmitSpy.callCount, 1); }); }); test('should retry if not online', function(done) { isOnLine = false; this.sinon.spy(AdvancedTelemetry.prototype, 'startRetryBatch'); this.sinon.spy(AdvancedTelemetry.prototype, 'clearPayload'); at.handleGeckoMessage(payloadNew.payload, function() { MockNavigatorSettings.mReplyToRequests(); sinon.assert.calledOnce(AdvancedTelemetry.prototype.startRetryBatch); sinon.assert.notCalled(AdvancedTelemetry.prototype.clearPayload); done(); }); }); test('should create the XHR properly', function(done) { this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); isOnLine = true; AdvancedTelemetryPing.prototype.send().then(function() { MockNavigatorSettings.mReplyToRequests(); assert.ok(xhr); assert.equal(xhr.method, 'POST'); done(); }); }); test('should format the URL properly', function(done) { this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); var deviceQuery = { 'deviceinfo.platform_version': '43', 'app.update.channel': 'default', 'deviceinfo.platform_build_id': '34' }; var atp = new AdvancedTelemetryPing({id: '1234'}, deviceQuery); atp.send().then(function() { MockNavigatorSettings.mReplyToRequests(); // Check that the URL is properly formatted. // URLformat:/id/reason/appName/appVersion/appUpdateChannel/appBuildID?v=4 var baseURL = AdvancedTelemetry.REPORT_URL; assert.ok(xhr.url.indexOf(baseURL) === 0); var path = xhr.url.substring(baseURL.length + 1).split('/'); assert.equal(path[0], '1234'); assert.equal(path[1], AdvancedTelemetry.REASON); assert.equal(path[2], AdvancedTelemetry.TELEMETRY_APP_NAME); assert.equal(path[3], '43'); assert.equal(path[4], 'default'); let version = path[5].split('?'); assert.equal(version[0], '34'); assert.equal(version[1], 'v=4'); done(); }); }); test('should format the body properly', function(done) { this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); at.handleGeckoMessage(wrapper.payload, function() { MockNavigatorSettings.mReplyToRequests(); var req = JSON.parse(at.request.data); assert.ok(req); assert.equal(req.type, wrapper.type); assert.equal(req.id, wrapper.id); assert.equal(req.creationDate, wrapper.creationDate); assert.equal(req.version, wrapper.version); assert.equal(req.clientId, wrapper.clientId); // Verify the application object picked up the settings correctly. assert.deepEqual(req.application, packedWrapper.application); // Verify that the Histograms are intact and are properly packed. assert.deepEqual(req.payload, packedWrapper.payload); done(); }); }); test('advanced telemetry should gzip compression payload', function(done) { this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); this.sinon.stub(AdvancedTelemetryPing.prototype, 'post', function() { return Promise.resolve(); }); // Even though `getData` is being stubbed, since `gzip.compress` is // stubbed to resolve compressed data, `post` should be called with // the compressed data. this.sinon.stub(AdvancedTelemetryPing.prototype, 'getData', function() { return JSON.parse(JSON.stringify(wrapper.payload)); }); AdvancedTelemetryPing.prototype.send().then(function() { MockNavigatorSettings.mReplyToRequests(); // `post` should be called with the compressed data sinon.assert.calledOnce(AdvancedTelemetryPing.prototype.post); sinon.deepEqual( AdvancedTelemetryPing.prototype.post.getCall(0).args[1], gzipCompressedData); done(); }); }); test('advanced telemetry should handle gzip compression failure', function(done) { this.sinon.stub(Gzip, 'compress', function() { return Promise.reject('compress error'); }); this.sinon.stub(AdvancedTelemetryPing.prototype, 'post', function() { return Promise.resolve(); }); // `gzip.compress` is stubbed to "reject" therefore `post` should be // called with the original, uncompressed payload. this.sinon.stub(AdvancedTelemetryPing.prototype, 'getData', function() { return JSON.parse(JSON.stringify(wrapper.payload)); }); AdvancedTelemetryPing.prototype.send().then(function() { MockNavigatorSettings.mReplyToRequests(); // `post` should be called with the uncompressed payload sinon.assert.calledOnce(AdvancedTelemetryPing.prototype.post); sinon.deepEqual( AdvancedTelemetryPing.prototype.post.getCall(0).args[1], wrapper.payload); done(); }); }); test('should retry on a timeout error', function(done) { var retryspy = this.sinon.spy(window, 'setTimeout'); this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); this.sinon.stub(console, 'info').returns(0); dispatch({payload: wrapper.payload}); // Simulate a timeout xhr.ontimeout(new CustomEvent('timeout')); assert.equal(retryspy.getCall(0).args[1], AdvancedTelemetry.RETRY_INTERVAL); done(); }); test('should retry interval after failed transmit', function(done) { var retryspy = this.sinon.spy(window, 'setTimeout'); this.sinon.stub(console, 'info').returns(0); dispatch({payload: wrapper.payload}); // Simulate a timeout xhr.ontimeout(new CustomEvent('error')); assert.equal(retryspy.getCall(0).args[1], AdvancedTelemetry.RETRY_INTERVAL); done(); }); test('should clear out the payload after a successful transmit', function(done) { this.sinon.stub(Gzip, 'compress', function() { return Promise.resolve(gzipCompressedData); }); this.sinon.spy(AdvancedTelemetry.prototype, 'clearPayload'); this.sinon.stub(console, 'info').returns(0); at.handleGeckoMessage(wrapper.payload, function() { // Simulate a successful send xhr.onload(new CustomEvent('load')); MockNavigatorSettings.mReplyToRequests(); sinon.assert.calledOnce(AdvancedTelemetry.prototype.clearPayload); done(); }); }); test('should clear out the payload after a successful merge', function(done) { this.sinon.spy(AdvancedTelemetry.prototype, 'clearPayload'); this.sinon.stub(console, 'info').returns(0); at.merge = true; at.handleGeckoMessage(wrapper.payload, function() { sinon.assert.calledOnce(AdvancedTelemetry.prototype.clearPayload); sinon.assert.calledWith(AdvancedTelemetry.prototype.clearPayload, false); done(); }); }); test('should NOT clear out the payload after a failed transmit', function(done) { this.sinon.spy(AdvancedTelemetry.prototype, 'clearPayload'); this.sinon.stub(console, 'info').returns(0); at.handleGeckoMessage(wrapper.payload, function() { // Simulate an error xhr.ontimeout(new CustomEvent('error')); sinon.assert.notCalled(AdvancedTelemetry.prototype.clearPayload); done(); }); }); test('should NOT clear out the payload after a transmit timeout', function(done) { this.sinon.spy(AdvancedTelemetry.prototype, 'clearPayload'); this.sinon.stub(console, 'info').returns(0); at.handleGeckoMessage(wrapper.payload, function() { // Simulate a timeout xhr.ontimeout(new CustomEvent('timeout')); sinon.assert.notCalled(AdvancedTelemetry.prototype.clearPayload); done(); }); }); }); suite('Enable/disable:', function() { var mockSettings, at, clock; setup(function() { MockNavigatorSettings.mSetup(); mockSettings = MockNavigatorSettings.mSettings; clock = this.sinon.useFakeTimers(); this.sinon.spy(AdvancedTelemetry.prototype, 'startCollecting'); this.sinon.spy(AdvancedTelemetry.prototype, 'stopCollecting'); at = new AdvancedTelemetry(); clock.tick(); }); teardown(function() { at.stop(); clock.tick(); MockNavigatorSettings.mTeardown(); }); test('starts immediately if enabled', function(done) { mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'Enhanced'; at.start(); clock.tick(); sinon.assert.notCalled(AdvancedTelemetry.prototype.stopCollecting); sinon.assert.calledOnce(AdvancedTelemetry.prototype.startCollecting); done(); }); test('does not start if not enabled', function(done) { mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'None'; at.start(); clock.tick(); sinon.assert.calledOnce(AdvancedTelemetry.prototype.stopCollecting); sinon.assert.notCalled(AdvancedTelemetry.prototype.startCollecting); done(); }); test('starts when enabled', function(done) { mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'None'; at.start(); clock.tick(); sinon.assert.calledOnce(AdvancedTelemetry.prototype.stopCollecting); sinon.assert.notCalled(AdvancedTelemetry.prototype.startCollecting); mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'Enhanced'; MockNavigatorSettings.mTriggerObservers( AdvancedTelemetry.TELEMETRY_ENABLED_KEY, { settingValue: 'Enhanced' }); clock.tick(); sinon.assert.calledOnce(AdvancedTelemetry.prototype.startCollecting); mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'None'; MockNavigatorSettings.mTriggerObservers( AdvancedTelemetry.TELEMETRY_ENABLED_KEY, { settingValue: 'None' }); clock.tick(); done(sinon.assert.calledTwice( AdvancedTelemetry.prototype.stopCollecting)); }); test('stops when disabled and starts again', function(done) { mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'Enhanced'; at.start(); clock.tick(); sinon.assert.notCalled(AdvancedTelemetry.prototype.stopCollecting); sinon.assert.calledOnce(AdvancedTelemetry.prototype.startCollecting); mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'None'; MockNavigatorSettings.mTriggerObservers( AdvancedTelemetry.TELEMETRY_ENABLED_KEY, { settingValue: 'None' }); clock.tick(); sinon.assert.calledOnce(AdvancedTelemetry.prototype.stopCollecting); mockSettings[AdvancedTelemetry.TELEMETRY_ENABLED_KEY] = 'Enhanced'; MockNavigatorSettings.mTriggerObservers( AdvancedTelemetry.TELEMETRY_ENABLED_KEY, { settingValue: 'Enhanced' }); clock.tick(); sinon.assert.calledTwice(AdvancedTelemetry.prototype.startCollecting); done(); }); }); suite('Batch Timings:', function() { var BatchTiming, clock; suiteSetup(function() { BatchTiming = AdvancedTelemetry.BatchTiming; }); setup(function () { clock = this.sinon.useFakeTimers(); }); test('should set full interval the first time', function (done) { this.sinon.stub(Date, 'now').returns(2); var bt = new BatchTiming(true); bt.getInterval(); window.asyncStorage.getItem(AdvancedTelemetry.BATCH_KEY, function(value) { assert.equal(value, '2'); done(); }); }); test('should set full interval after a batch send', function (done) { this.sinon.stub(Date, 'now').returns(2); var bt = new BatchTiming(); bt.getInterval(); window.asyncStorage.getItem(AdvancedTelemetry.BATCH_KEY, function (value) { assert.equal(value, '2'); done(); }); }); test('should not modify the start time if existing time', function (done) { MockasyncStorage[AdvancedTelemetry.BATCH_KEY] = '2'; var bt = new BatchTiming(true); bt.getInterval(); window.asyncStorage.getItem(AdvancedTelemetry.BATCH_KEY, function (value) { assert.equal(value, '2'); done(); }); }); test('should recalculate the interval correctly if existing time set', function (done) { MockasyncStorage.setItem(AdvancedTelemetry.BATCH_KEY, 1437671946520); clock.tick(); this.sinon.stub(Date, 'now').returns(1437671946521); var bt = new BatchTiming(true); window.asyncStorage.getItem(AdvancedTelemetry.BATCH_KEY, function (value) { assert.equal(value, 1437671946520); assert.equal(bt.getInterval(), 1); done(); }); }); test('should safeguard an interval being set for too long', function (done) { MockasyncStorage.setItem(AdvancedTelemetry.BATCH_KEY, 1437681946521); clock.tick(); this.sinon.stub(Date, 'now').returns(1487681946521); var bt = new BatchTiming(true); window.asyncStorage.getItem(AdvancedTelemetry.BATCH_KEY, function (value) { assert.equal(value, 1487681946521); assert.equal(bt.getInterval(), AdvancedTelemetry.REPORT_INTERVAL); done(); }); }); }); suite('Merging Metrics:', function() { var mockSettings, at, clock; // The old, and new payloads are setup to make sure that the merged // payload properly reflects: 1) An item in the old that is not in the new, // 2) An item in the new that is not in the old, and 3) the addition of the // histogram elements is done correctly for sum, log_sum, and // log_sum_squares. setup(function() { mockSettings = MockNavigatorSettings.mSettings; clock = this.sinon.useFakeTimers(); this.sinon.spy(MockasyncStorage, 'setItem'); this.sinon.spy(AdvancedTelemetry.prototype, 'transmit'); at = new AdvancedTelemetry(); clock.tick(); }); teardown(function(done) { at.stop(); clock.tick(); MockasyncStorage.mTeardown(); done(); }); test('should merge correctly with no previous metrics', function() { at.mergeMetrics(payloadNew); sinon.assert.calledOnce(MockasyncStorage.setItem); // The calledWith does a deepEqual check on all the JSON elements. sinon.assert.calledWith(MockasyncStorage.setItem, AdvancedTelemetry.METRICS_KEY, payloadNew); }); test('should merge correct with metrics with existing metrics', function() { // Make a clone so we don't modify it for subsequent tests. var cloneOldPayload = JSON.parse(JSON.stringify(payloadOld)); MockasyncStorage.setItem(AdvancedTelemetry.METRICS_KEY, cloneOldPayload); at.mergeMetrics(payloadNew); // Called once above for test setup. sinon.assert.calledTwice(MockasyncStorage.setItem); // The calledWith does a deepEqual check on all the JSON elements. sinon.assert.calledWith(MockasyncStorage.setItem, AdvancedTelemetry.METRICS_KEY, mergedPayload); }); }); });