diff --git a/scripts/get_users_meta_docs.js b/scripts/get_users_meta_docs.js old mode 100644 new mode 100755 index fe7b906cfde..82f01fe4e42 --- a/scripts/get_users_meta_docs.js +++ b/scripts/get_users_meta_docs.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + const inquirer = require('inquirer'); const PouchDB = require('pouchdb-core'); const fs = require('fs'); @@ -86,7 +88,7 @@ const actionQuestions = [{ } docs.forEach(doc => console.log(JSON.stringify(doc, null, 2) + ',')); } else if (i === 0) { - console.log('\x1b[31m%s\x1b[0m', `There are no documents of type ${type}`); + console.error('\x1b[31m%s\x1b[0m', `There are no documents of type ${type}`); break; } else { console.log('{}]'); @@ -100,7 +102,7 @@ const actionQuestions = [{ let docIndex = 0; if (docs.length === 0) { - console.log('\x1b[31m%s\x1b[0m', `There are no documents of type ${type}`); + console.error('\x1b[31m%s\x1b[0m', `There are no documents of type ${type}`); } else { console.log(JSON.stringify(docs[docIndex], null, 2)); @@ -125,7 +127,7 @@ const actionQuestions = [{ console.log(JSON.stringify(docs[docIndex], null, 2)); if (printMessage) { - console.log('\x1b[31m%s\x1b[0m', `No next document. This is the last one.`); + console.error('\x1b[31m%s\x1b[0m', `No next document. This is the last one.`); } } else if (response.action === 'save_current') { const filePath = path.join(path.resolve(__dirname), docs[docIndex]._id + '.json'); @@ -154,6 +156,6 @@ const actionQuestions = [{ } } } catch(err) { - console.log(err); + console.error(err); } })(); diff --git a/webapp/src/ts/services/telemetry.service.ts b/webapp/src/ts/services/telemetry.service.ts index 228352801ef..e6f8f3fafbf 100644 --- a/webapp/src/ts/services/telemetry.service.ts +++ b/webapp/src/ts/services/telemetry.service.ts @@ -9,13 +9,13 @@ import { SessionService } from '@mm-services/session.service'; providedIn: 'root' }) /** - * TelemetryService: Records, aggregates, and submits telemetry data + * TelemetryService: Records, aggregates, and submits telemetry data. */ export class TelemetryService { // Intentionally scoped to the whole browser (for this domain). We can then tell if multiple users use the same device private readonly DEVICE_ID_KEY = 'medic-telemetry-device-id'; private DB_ID_KEY; - private LAST_AGGREGATED_DATE_KEY; + private FIRST_AGGREGATED_DATE_KEY; private queue = Promise.resolve(); @@ -27,7 +27,7 @@ export class TelemetryService { // Intentionally scoped to the specific user, as they may perform a // different role (online vs. offline being being the most obvious) with different performance implications this.DB_ID_KEY = ['medic', this.sessionService.userCtx().name, 'telemetry-db'].join('-'); - this.LAST_AGGREGATED_DATE_KEY = ['medic', this.sessionService.userCtx().name, 'telemetry-date'].join('-'); + this.FIRST_AGGREGATED_DATE_KEY = ['medic', this.sessionService.userCtx().name, 'telemetry-date'].join('-'); } private getDb() { @@ -54,15 +54,21 @@ export class TelemetryService { return uniqueDeviceId; } - private getLastAggregatedDate() { - let date = parseInt(window.localStorage.getItem(this.LAST_AGGREGATED_DATE_KEY)); + /** + * Returns a Moment object when the first telemetry record was created. + * + * This date is computed and stored in milliseconds (since Unix epoch) + * when we call this method for the first time and after every aggregation. + */ + private getFirstAggregatedDate() { + let date = parseInt(window.localStorage.getItem(this.FIRST_AGGREGATED_DATE_KEY)); if (!date) { date = Date.now(); - window.localStorage.setItem(this.LAST_AGGREGATED_DATE_KEY, date.toString()); + window.localStorage.setItem(this.FIRST_AGGREGATED_DATE_KEY, date.toString()); } - return date; + return moment(date); } private storeIt(db, key, value) { @@ -73,11 +79,17 @@ export class TelemetryService { }); } + // moment when the aggregation starts (the beginning of the current day) + private aggregateStartsAt() { + return moment().startOf('day'); + } + + // if there is telemetry data from previous days, aggregation is performed and the data destroyed private submitIfNeeded(db) { - const monthStart = moment().startOf('month'); - const dbDate = moment(this.getLastAggregatedDate()); + const startOf = this.aggregateStartsAt(); + const dbDate = this.getFirstAggregatedDate(); - if (dbDate.isBefore(monthStart)) { + if (dbDate.isBefore(startOf)) { return this .aggregate(db) .then(() => this.reset(db)); @@ -97,6 +109,7 @@ export class TelemetryService { 'telemetry', metadata.year, metadata.month, + metadata.day, metadata.user, metadata.deviceId, ].join('-'); @@ -109,7 +122,7 @@ export class TelemetryService { this.dbService.get().query('medic-client/doc_by_type', { key: ['form'], include_docs: true }) ]) .then(([ddoc, formResults]) => { - const date = moment(this.getLastAggregatedDate()); + const date = this.getFirstAggregatedDate(); const version = (ddoc.deploy_info && ddoc.deploy_info.version) || 'unknown'; const forms = formResults.rows.reduce((keyToVersion, row) => { keyToVersion[row.doc.internalId] = row.doc._rev; @@ -120,6 +133,7 @@ export class TelemetryService { return { year: date.year(), month: date.month() + 1, + day: date.date(), user: this.sessionService.userCtx().name, deviceId: this.getUniqueDeviceId(), versions: { @@ -204,7 +218,7 @@ export class TelemetryService { private reset(db) { window.localStorage.removeItem(this.DB_ID_KEY); - window.localStorage.removeItem(this.LAST_AGGREGATED_DATE_KEY); + window.localStorage.removeItem(this.FIRST_AGGREGATED_DATE_KEY); return db.destroy(); } @@ -225,7 +239,7 @@ export class TelemetryService { * metric_b: { sum: -16, min: -4, max: -4, count: 4, sumsqr: 64 } * } * - * See: https://wiki.apache.org/couchdb/Built-In_Reduce_Functions#A_stats + * See: https://docs.couchdb.org/en/stable/ddocs/ddocs.html#_stats * * This single month aggregate document is of type 'telemetry', and is * stored in the user's meta DB (which replicates up to the main server) @@ -255,14 +269,14 @@ export class TelemetryService { let db; this.queue = this.queue .then(() => db = this.getDb()) - .then(() => this.storeIt(db, key, value)) .then(() => this.submitIfNeeded(db)) + .then(() => db = this.getDb()) // db is fetched again in case submitIfNeeded dropped the old reference + .then(() => this.storeIt(db, key, value)) .catch(err => console.error('Error in telemetry service', err)) .finally(() => { if (!db || db._destroyed || db._closed) { return; } - try { db.close(); } catch (err) { diff --git a/webapp/tests/karma/ts/services/telemetry.service.spec.ts b/webapp/tests/karma/ts/services/telemetry.service.spec.ts index 29ee09fb1b7..c6898aff9d3 100644 --- a/webapp/tests/karma/ts/services/telemetry.service.spec.ts +++ b/webapp/tests/karma/ts/services/telemetry.service.spec.ts @@ -8,17 +8,19 @@ import { DbService } from '@mm-services/db.service'; import { SessionService } from '@mm-services/session.service'; describe('TelemetryService', () => { - const NOW = new Date(2018, 10, 10, 12, 33).getTime(); + const NOW = new Date(2018, 10, 10, 12, 33).getTime(); // -> 2018-11-10T12:33:00 let service: TelemetryService; let dbService; - let dbInstance; + let metaDb; let sessionService; let clock; - let pouchDb; + let telemetryDb; let storageGetItemStub; let storageSetItemStub; let consoleErrorSpy; + const windowPouchOriginal = window.PouchDB; + const windowScreenOriginal = { availWidth: window.screen.availWidth, availHeight: window.screen.availHeight @@ -28,18 +30,83 @@ describe('TelemetryService', () => { hardwareConcurrency: window.navigator.hardwareConcurrency }; + function defineWindow() { + Object.defineProperty( + window.navigator, + 'userAgent', + { value: 'Agent Smith', configurable: true } + ); + Object.defineProperty( + window.navigator, + 'hardwareConcurrency', + { value: 4, configurable: true } + ); + Object.defineProperty( + window.screen, + 'availWidth', + { value: 768, configurable: true } + ); + Object.defineProperty( + window.screen, + 'availHeight', + { value: 1024, configurable: true } + ); + } + + function restoreWindow() { + Object.defineProperty( + window.navigator, + 'userAgent', + { value: windowNavigatorOriginal.userAgent, configurable: true } + ); + Object.defineProperty( + window.navigator, + 'hardwareConcurrency', + { value: windowNavigatorOriginal.hardwareConcurrency, configurable: true } + ); + Object.defineProperty( + window.screen, + 'availWidth', + { value: windowScreenOriginal.availWidth, configurable: true } + ); + Object.defineProperty( + window.screen, + 'availHeight', + { value: windowScreenOriginal.availHeight, configurable: true } + ); + } + + function subtractDays(numDays) { + return moment() + .subtract(numDays, 'days') + .valueOf() + .toString(); + } + + function sameDay() { + return moment() + .valueOf() + .toString(); + } + beforeEach(() => { - dbInstance = { + defineWindow(); + metaDb = { info: sinon.stub(), put: sinon.stub(), get: sinon.stub(), query: sinon.stub() }; - dbService = { get: () => dbInstance }; + dbService = { get: () => metaDb }; consoleErrorSpy = sinon.spy(console, 'error'); - pouchDb = { + telemetryDb = { post: sinon.stub().resolves(), - close: sinon.stub() + close: sinon.stub(), + query: sinon.stub(), + destroy: sinon.stub().callsFake(() => { + telemetryDb._destroyed = true; + return Promise.resolve(); + }) }; sessionService = { userCtx: sinon.stub().returns({ name: 'greg' }) }; storageGetItemStub = sinon.stub(window.localStorage, 'getItem'); @@ -54,128 +121,62 @@ describe('TelemetryService', () => { service = TestBed.inject(TelemetryService); clock = sinon.useFakeTimers(NOW); - window.PouchDB = () => pouchDb; + window.PouchDB = () => telemetryDb; }); afterEach(() => { clock.restore(); sinon.restore(); window.PouchDB = windowPouchOriginal; - Object.defineProperty( - window.navigator, - 'userAgent', - { value: windowNavigatorOriginal.userAgent, configurable: true } - ); - Object.defineProperty( - window.navigator, - 'hardwareConcurrency', - { value: windowNavigatorOriginal.hardwareConcurrency, configurable: true } - ); - Object.defineProperty( - window.screen, - 'availWidth', - { value: windowScreenOriginal.availWidth, configurable: true } - ); - Object.defineProperty( - window.screen, - 'availHeight', - { value: windowScreenOriginal.availHeight, configurable: true } - ); + restoreWindow(); }); describe('record()', () => { it('should record a piece of telemetry', async () => { - pouchDb.post = sinon.stub().resolves(); - pouchDb.close = sinon.stub(); - storageGetItemStub - .withArgs('medic-greg-telemetry-db') - .returns('dbname'); - storageGetItemStub - .withArgs('medic-greg-telemetry-date') - .returns(Date.now().toString()); + storageGetItemStub.withArgs('medic-greg-telemetry-db').returns('dbname'); + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(Date.now().toString()); await service.record('test', 100); expect(consoleErrorSpy.callCount).to.equal(0); - expect(pouchDb.post.callCount).to.equal(1); - expect(pouchDb.post.args[0][0]).to.deep.include({ key: 'test', value: 100 }); - expect(pouchDb.post.args[0][0].date_recorded).to.be.above(0); - expect(storageGetItemStub.callCount).to.equal(2); - expect(pouchDb.close.callCount).to.equal(1); + expect(telemetryDb.post.callCount).to.equal(1); + expect(telemetryDb.post.args[0][0]).to.deep.include({ key: 'test', value: 100 }); + expect(telemetryDb.post.args[0][0].date_recorded).to.be.above(0); + expect(storageGetItemStub.callCount).to.equal(3); + expect(storageGetItemStub.args[0]).to.deep.equal(['medic-greg-telemetry-db']); + expect(storageGetItemStub.args[1]).to.deep.equal(['medic-greg-telemetry-date']); + expect(storageGetItemStub.args[2]).to.deep.equal(['medic-greg-telemetry-db']); + expect(telemetryDb.close.callCount).to.equal(1); }); it('should default the value to 1 if not passed', async () => { - pouchDb.post = sinon.stub().resolves(); - pouchDb.close = sinon.stub(); - storageGetItemStub - .withArgs('medic-greg-telemetry-db') - .returns('dbname'); - storageGetItemStub - .withArgs('medic-greg-telemetry-date') - .returns(Date.now().toString()); + storageGetItemStub.withArgs('medic-greg-telemetry-db').returns('dbname'); + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(Date.now().toString()); await service.record('test'); expect(consoleErrorSpy.callCount).to.equal(0); - expect(pouchDb.post.args[0][0].value).to.equal(1); - expect(pouchDb.close.callCount).to.equal(1); - }); - - it('should set localStorage values', async () => { - pouchDb.post = sinon.stub().resolves(); - pouchDb.close = sinon.stub(); - storageGetItemStub - .withArgs('medic-greg-telemetry-db') - .returns(undefined); - storageGetItemStub - .withArgs('medic-greg-telemetry-date') - .returns(undefined); - - await service.record('test', 1); - - expect(consoleErrorSpy.callCount).to.equal(0); - expect(storageSetItemStub.callCount).to.equal(2); - expect(storageSetItemStub.args[0][0]).to.equal('medic-greg-telemetry-db'); - expect(storageSetItemStub.args[0][1]).to.match(/medic-user-greg-telemetry-/); // ends with a UUID - expect(storageSetItemStub.args[1][0]).to.equal('medic-greg-telemetry-date'); - expect(storageSetItemStub.args[1][1]).to.equal(NOW.toString()); + expect(telemetryDb.post.args[0][0].value).to.equal(1); + expect(telemetryDb.close.callCount).to.equal(1); }); - it('should aggregate once a month and resets the db', async () => { - storageGetItemStub - .withArgs('medic-greg-telemetry-db') - .returns('dbname'); - storageGetItemStub - .withArgs('medic-greg-telemetry-date') - .returns( - moment() - .subtract(5, 'weeks') - .valueOf() - .toString() - ); - - pouchDb.post = sinon.stub().resolves(); - pouchDb.query = sinon.stub().resolves({ + function setupDbMocks() { + storageGetItemStub.returns('dbname'); + telemetryDb.query.resolves({ rows: [ - { key: 'foo', value: 'stats' }, - { key: 'bar', value: 'more stats' }, + { key: 'foo', value: {sum:2876, min:581, max:2295, count:2, sumsqr:5604586} }, + { key: 'bar', value: {sum:93, min:43, max:50, count:2, sumsqr:4349} }, ], }); - pouchDb.destroy = sinon.stub().callsFake(() => { - pouchDb._destroyed = true; - return Promise.resolve(); - }); - pouchDb.close = sinon.stub(); - - dbInstance.info.resolves({ some: 'stats' }); - dbInstance.put.resolves(); - dbInstance.get + metaDb.info.resolves({ some: 'stats' }); + metaDb.put.resolves(); + metaDb.get .withArgs('_design/medic-client') .resolves({ _id: '_design/medic-client', deploy_info: { version: '3.0.0' } }); - dbInstance.query.resolves({ + metaDb.query.resolves({ rows: [ { id: 'form:anc_followup', @@ -188,28 +189,28 @@ describe('TelemetryService', () => { } ] }); + } - Object.defineProperty(window.navigator, 'userAgent', { value: 'Agent Smith', configurable: true }); - Object.defineProperty(window.navigator, 'hardwareConcurrency', { value: 4, configurable: true }); - Object.defineProperty(window.screen, 'availWidth', { value: 768, configurable: true }); - Object.defineProperty(window.screen, 'availHeight', { value: 1024, configurable: true }); + it('should aggregate once a day and resets the db first', async () => { + setupDbMocks(); + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(subtractDays(5)); await service.record('test', 1); - expect(consoleErrorSpy.callCount).to.equal(0); - expect(pouchDb.post.callCount).to.equal(1); - expect(pouchDb.post.args[0][0]).to.deep.include({ key: 'test', value: 1 }); - expect(dbInstance.put.callCount).to.equal(1); + expect(telemetryDb.post.callCount).to.equal(1); + expect(telemetryDb.post.args[0][0]).to.deep.include({ key: 'test', value: 1 }); - const aggregatedDoc = dbInstance.put.args[0][0]; - expect(aggregatedDoc._id).to.match(/telemetry-2018-10-greg/); + expect(metaDb.put.callCount).to.equal(1); + const aggregatedDoc = metaDb.put.args[0][0]; + expect(aggregatedDoc._id).to.match(/^telemetry-2018-11-5-greg-[\w-]+$/); expect(aggregatedDoc.metrics).to.deep.equal({ - foo: 'stats', - bar: 'more stats', + foo: {sum:2876, min:581, max:2295, count:2, sumsqr:5604586}, + bar: {sum:93, min:43, max:50, count:2, sumsqr:4349}, }); expect(aggregatedDoc.type).to.equal('telemetry'); expect(aggregatedDoc.metadata.year).to.equal(2018); - expect(aggregatedDoc.metadata.month).to.equal(10); + expect(aggregatedDoc.metadata.month).to.equal(11); + expect(aggregatedDoc.metadata.day).to.equal(5); expect(aggregatedDoc.metadata.user).to.equal('greg'); expect(aggregatedDoc.metadata.versions).to.deep.equal({ app: '3.0.0', @@ -227,24 +228,131 @@ describe('TelemetryService', () => { }, deviceInfo: {} }); - expect(dbInstance.query.callCount).to.equal(1); - expect(dbInstance.query.args[0][0]).to.equal('medic-client/doc_by_type'); - expect(dbInstance.query.args[0][1]).to.deep.equal({ key: ['form'], include_docs: true }); - expect(pouchDb.destroy.callCount).to.equal(1); - expect(pouchDb.close.callCount).to.equal(0); + + expect(metaDb.query.callCount).to.equal(1); + expect(metaDb.query.args[0][0]).to.equal('medic-client/doc_by_type'); + expect(metaDb.query.args[0][1]).to.deep.equal({ key: ['form'], include_docs: true }); + expect(telemetryDb.destroy.callCount).to.equal(1); + expect(telemetryDb.close.callCount).to.equal(0); + + expect(consoleErrorSpy.callCount).to.equal(0); // no errors + }); + + it('should not aggregate when recording the day the db was created and next day it should aggregate', async () => { + setupDbMocks(); + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(sameDay()); + + await service.record('test', 10); + + expect(telemetryDb.post.callCount).to.equal(1); // Telemetry entry has been recorded + expect(telemetryDb.post.args[0][0]).to.deep.include({ key: 'test', value: 10 }); + expect(telemetryDb.query.called).to.be.false; // NO telemetry aggregation has + expect(metaDb.put.callCount).to.equal(0); // been recorded yet + + clock = sinon.useFakeTimers(moment(NOW).add(1, 'minutes').valueOf()); // 1 min later ... + await service.record('test', 5); + + expect(telemetryDb.post.callCount).to.equal(2); // second call + expect(telemetryDb.post.args[1][0]).to.deep.include({ key: 'test', value: 5 }); + expect(telemetryDb.query.called).to.be.false; // still NO aggregation has + expect(metaDb.put.callCount).to.equal(0); // been recorded (same day) + + let postCalledAfterQuery = false; + telemetryDb.post.callsFake(async () => postCalledAfterQuery = telemetryDb.query.called); + clock = sinon.useFakeTimers(moment(NOW).add(1, 'days').valueOf()); // 1 day later ... + await service.record('test', 2); + + expect(telemetryDb.post.callCount).to.equal(3); // third call + expect(telemetryDb.post.args[2][0]).to.deep.include({ key: 'test', value: 2 }); + expect(telemetryDb.query.callCount).to.equal(1); // Now aggregation HAS been performed + expect(metaDb.put.callCount).to.equal(1); // and the stats recorded + + // The telemetry record has been recorded after aggregation to not being included in the stats, + // because the record belong to the current date, not the day aggregated (yesterday) + expect(postCalledAfterQuery).to.be.true; + + const aggregatedDoc = metaDb.put.args[0][0]; + expect(aggregatedDoc._id).to.match(/^telemetry-2018-11-10-greg-[\w-]+$/); // Now is 2018-11-11 but aggregation + expect(telemetryDb.destroy.callCount).to.equal(1); // is from the previous day + + expect(consoleErrorSpy.callCount).to.equal(0); // no errors + }); + + it('should aggregate from days with records skipping days without records', async () => { + setupDbMocks(); + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(sameDay()); + + await service.record('datapoint', 12); + + expect(telemetryDb.post.callCount).to.equal(1); + expect(metaDb.put.callCount).to.equal(0); // NO telemetry has been recorded yet + + clock = sinon.useFakeTimers(moment(NOW).add(1, 'minutes').valueOf()); // 1 min later ... + await service.record('another.datapoint'); + + expect(telemetryDb.post.callCount).to.equal(2); // second call + expect(metaDb.put.callCount).to.equal(0); // still NO telemetry has been recorded (same day) + + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(sameDay()); + clock = sinon.useFakeTimers(moment(NOW).add(2, 'days').valueOf()); // 2 days later ... + await service.record('test', 2); + + expect(telemetryDb.post.callCount).to.equal(3); // third call + expect(metaDb.put.callCount).to.equal(1); // Now telemetry IS recorded + + let aggregatedDoc = metaDb.put.args[0][0]; + expect(aggregatedDoc._id).to.match(/^telemetry-2018-11-10-greg-[\w-]+$/); // Today 2018-11-12 but aggregation is + expect(telemetryDb.destroy.callCount).to.equal(1); // from from 2 days ago (not Yesterday) + + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(sameDay()); // same day now is 2 days ahead + + clock = sinon.useFakeTimers(moment(NOW).add(7, 'days').valueOf()); // 7 days later ... + await service.record('point.a', 1); + + expect(telemetryDb.post.callCount).to.equal(4); // 4th call + expect(metaDb.put.callCount).to.equal(2); // Telemetry IS recorded again + aggregatedDoc = metaDb.put.args[1][0]; + expect(aggregatedDoc._id).to.match(/^telemetry-2018-11-12-greg-[\w-]+$/); // Now is Nov 19 but agg. is from Nov 12 + + // A new record is added ... + clock = sinon.useFakeTimers(moment(NOW).add(2, 'hours').valueOf()); // ... 2 hours later ... + await service.record('point.b', 0); // 1 record added + // ...the aggregation count is the same because + // the aggregation was already performed 2 hours ago within the same day + expect(telemetryDb.post.callCount).to.equal(5); // 5th call + expect(metaDb.put.callCount).to.equal(2); // Telemetry count is the same + + expect(consoleErrorSpy.callCount).to.equal(0); // no errors }); + }); + + describe('getDb()', () => { + it('should set localStorage values', async () => { + storageGetItemStub + .withArgs('medic-greg-telemetry-db') + .returns(undefined); + storageGetItemStub + .withArgs('medic-greg-telemetry-date') + .returns(undefined); + + await service.record('test', 1); + + expect(consoleErrorSpy.callCount).to.equal(0); + expect(storageSetItemStub.callCount).to.equal(3); + expect(storageSetItemStub.args[0][0]).to.equal('medic-greg-telemetry-db'); + expect(storageSetItemStub.args[0][1]).to.match(/^medic-user-greg-telemetry-[\w-]+$/); + expect(storageSetItemStub.args[1][0]).to.equal('medic-greg-telemetry-date'); + expect(storageSetItemStub.args[1][1]).to.equal(NOW.toString()); + }); + }); + + describe('storeConflictedAggregate()', () => { it('should deal with conflicts by making the ID unique and noting the conflict in the new document', async () => { storageGetItemStub.withArgs('medic-greg-telemetry-db').returns('dbname'); - storageGetItemStub.withArgs('medic-greg-telemetry-date').returns( - moment() - .subtract(5, 'weeks') - .valueOf() - .toString() - ); - - pouchDb.post = sinon.stub().resolves(); - pouchDb.query = sinon.stub().resolves({ + storageGetItemStub.withArgs('medic-greg-telemetry-date').returns(subtractDays(5)); + + telemetryDb.query = sinon.stub().resolves({ rows: [ { key: 'foo', @@ -256,36 +364,25 @@ describe('TelemetryService', () => { }, ], }); - dbInstance.info.resolves({ some: 'stats' }); - dbInstance.put.onFirstCall().rejects({ status: 409 }); - dbInstance.put.onSecondCall().resolves(); - dbInstance.get.withArgs('_design/medic-client').resolves({ + metaDb.info.resolves({ some: 'stats' }); + metaDb.put.onFirstCall().rejects({ status: 409 }); + metaDb.put.onSecondCall().resolves(); + metaDb.get.withArgs('_design/medic-client').resolves({ _id: '_design/medic-client', deploy_info: { version: '3.0.0' } }); - dbInstance.query.resolves({ rows: [] }); - pouchDb.destroy = sinon.stub().callsFake(() => { - pouchDb._destroyed = true; - return Promise.resolve(); - }); - pouchDb.close = sinon.stub(); - - Object.defineProperty(window.navigator, 'userAgent', { value: 'Agent Smith', configurable: true }); - Object.defineProperty(window.navigator, 'hardwareConcurrency', { value: 4, configurable: true }); - Object.defineProperty(window.screen, 'availWidth', { value: 768, configurable: true }); - Object.defineProperty(window.screen, 'availHeight', { value: 1024, configurable: true }); + metaDb.query.resolves({ rows: [] }); await service.record('test', 1); expect(consoleErrorSpy.callCount).to.equal(0); - expect(dbInstance.put.callCount).to.equal(2); - expect(dbInstance.put.args[1][0]._id).to.match(/conflicted/); - expect(dbInstance.put.args[1][0].metadata.conflicted).to.equal(true); - expect(pouchDb.destroy.callCount).to.equal(1); - expect(pouchDb.close.callCount).to.equal(0); + expect(metaDb.put.callCount).to.equal(2); + expect(metaDb.put.args[1][0]._id).to.match(/^telemetry-2018-11-5-greg-[\w-]+-conflicted-[\w-]+$/); + expect(metaDb.put.args[1][0].metadata.conflicted).to.equal(true); + expect(telemetryDb.destroy.callCount).to.equal(1); + expect(telemetryDb.close.callCount).to.equal(0); }); }); - });