mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
Add MongoDB 8 compatibility follow-up fixes
This commit is contained in:
@@ -76,12 +76,12 @@ function Collection ({ ctx, env, app, colName, storageColName, fallbackGetDate,
|
||||
* Parse limit (max document count) from query string
|
||||
*/
|
||||
self.parseLimit = function parseLimit (req, res) {
|
||||
const maxLimit = app.get('API3_MAX_LIMIT');
|
||||
const maxLimit = parseInt(app.get('API3_MAX_LIMIT'), 10) || apiConst.API3_MAX_LIMIT;
|
||||
let limit = maxLimit;
|
||||
|
||||
if (req.query.limit) {
|
||||
if (!isNaN(req.query.limit) && req.query.limit > 0 && req.query.limit <= maxLimit) {
|
||||
limit = parseInt(req.query.limit);
|
||||
limit = parseInt(req.query.limit, 10);
|
||||
}
|
||||
else {
|
||||
opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LIMIT);
|
||||
|
||||
@@ -6,7 +6,7 @@ const _ = require('lodash')
|
||||
, validate = require('./validate.js')
|
||||
, opTools = require('../../shared/operationTools')
|
||||
, dateTools = require('../../shared/dateTools')
|
||||
, FieldsProjector = require('../../shared/fieldsProjector')
|
||||
, treatmentDuration = require('../../../treatmentDuration')
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -78,6 +78,8 @@ async function applyPatch (opCtx, identifier, doc, storageDoc) {
|
||||
doc.modifiedBy = auth.subject.name;
|
||||
}
|
||||
|
||||
treatmentDuration.normalizeTreatmentDuration(doc, storageDoc);
|
||||
|
||||
const matchedCount = await col.storage.updateOne(identifier, doc);
|
||||
|
||||
if (!matchedCount)
|
||||
@@ -86,10 +88,7 @@ async function applyPatch (opCtx, identifier, doc, storageDoc) {
|
||||
res.setHeader('Last-Modified', now.toUTCString());
|
||||
opTools.sendJSONStatus(res, apiConst.HTTP.OK);
|
||||
|
||||
const fieldsProjector = new FieldsProjector('_all');
|
||||
const patchedDocs = await col.storage.findOne(identifier, fieldsProjector);
|
||||
const patchedDoc = patchedDocs[0];
|
||||
fieldsProjector.applyProjection(patchedDoc);
|
||||
const patchedDoc = Object.assign({}, storageDoc, doc);
|
||||
ctx.bus.emit('storage-socket-update', { colName: col.colName, doc: patchedDoc });
|
||||
|
||||
col.autoPrune();
|
||||
|
||||
@@ -121,7 +121,7 @@ function parseSkip (req, res) {
|
||||
|
||||
if (req.query.skip) {
|
||||
if (!isNaN(req.query.skip) && req.query.skip >= 0) {
|
||||
skip = parseInt(req.query.skip);
|
||||
skip = parseInt(req.query.skip, 10);
|
||||
}
|
||||
else {
|
||||
opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_SKIP);
|
||||
@@ -137,4 +137,4 @@ module.exports = {
|
||||
parseFilter,
|
||||
parseSort,
|
||||
parseSkip
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ const apiConst = require('../../const.json')
|
||||
, validate = require('./validate.js')
|
||||
, path = require('path')
|
||||
, opTools = require('../../shared/operationTools')
|
||||
, treatmentDuration = require('../../../treatmentDuration')
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,8 @@ async function replace (opCtx, doc, storageDoc, options) {
|
||||
doc.subject = auth.subject.name;
|
||||
}
|
||||
|
||||
treatmentDuration.normalizeTreatmentDuration(doc);
|
||||
|
||||
const matchedCount = await col.storage.replaceOne(storageDoc.identifier, doc);
|
||||
|
||||
if (!matchedCount)
|
||||
|
||||
@@ -4,6 +4,18 @@ const utils = require('./utils')
|
||||
, _ = require('lodash')
|
||||
;
|
||||
|
||||
/**
|
||||
* Ensure Mongo limit/skip receive integers even when callers pass env strings.
|
||||
*/
|
||||
function toSafeInt (value, defaultValue) {
|
||||
if (value === null || value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const parsed = parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : defaultValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find single document by identifier
|
||||
@@ -73,11 +85,13 @@ function findMany (col, args) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
const filter = utils.parseFilter(args.filter, logicalOperator, args.onlyValid);
|
||||
const safeLimit = toSafeInt(args.limit, 1000);
|
||||
const safeSkip = toSafeInt(args.skip, 0);
|
||||
|
||||
col.find(filter)
|
||||
.sort(args.sort)
|
||||
.limit(args.limit)
|
||||
.skip(args.skip)
|
||||
.limit(safeLimit)
|
||||
.skip(safeSkip)
|
||||
.project(args.projection)
|
||||
.toArray(function mongoDone (err, result) {
|
||||
|
||||
|
||||
+23
-1
@@ -43,6 +43,27 @@ function init () {
|
||||
&& !Object.prototype.hasOwnProperty.call(obj[key], 'mills')) {
|
||||
obj[key].mills = new Date(obj[key].sysTime).getTime();
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(obj[key], 'durationInMilliseconds')
|
||||
&& !Object.prototype.hasOwnProperty.call(obj[key], 'duration')) {
|
||||
var durationInMilliseconds = Number(obj[key].durationInMilliseconds) || 0;
|
||||
if (durationInMilliseconds > 0) {
|
||||
obj[key].duration = Math.round(durationInMilliseconds / 60000);
|
||||
}
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(obj[key], 'endmills') || obj[key].endmills == null) {
|
||||
var baseMills = Number(obj[key].mills);
|
||||
if (Number.isFinite(baseMills)) {
|
||||
obj[key].mills = baseMills;
|
||||
if (Object.prototype.hasOwnProperty.call(obj[key], 'durationInMilliseconds')) {
|
||||
var endmillsDuration = Number(obj[key].durationInMilliseconds) || 0;
|
||||
if (endmillsDuration > 0) {
|
||||
obj[key].endmills = baseMills + endmillsDuration;
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(obj[key], 'duration')) {
|
||||
obj[key].endmills = baseMills + times.mins(Number(obj[key].duration) || 0).msecs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -65,7 +86,8 @@ function init () {
|
||||
const oldElement = oldData[i];
|
||||
let found = false;
|
||||
for (let j = 0; j < newData.length; j++) {
|
||||
if (oldElement._id == newData[j]._id) {
|
||||
if ((oldElement._id && oldElement._id == newData[j]._id)
|
||||
|| (oldElement.identifier && oldElement.identifier === newData[j].identifier)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
+18
-10
@@ -10,6 +10,20 @@ function getRemoteIP (req) {
|
||||
return address.ip;
|
||||
}
|
||||
|
||||
// Only coerce canonical 24-char hex strings to ObjectId.
|
||||
// Preserve custom string ids and existing ObjectId instances.
|
||||
function safeObjectID (id) {
|
||||
if (id instanceof ObjectID) {
|
||||
return id;
|
||||
}
|
||||
|
||||
if (typeof id === 'string' && /^[0-9a-fA-F]{24}$/.test(id)) {
|
||||
return new ObjectID(id);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function init (env, ctx, server) {
|
||||
|
||||
function websocket () {
|
||||
@@ -214,13 +228,7 @@ function init (env, ctx, server) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
var id;
|
||||
try {
|
||||
id = new ObjectID(data._id);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
id = new ObjectID();
|
||||
}
|
||||
var id = safeObjectID(data._id);
|
||||
|
||||
ctx.store.collection(collection).updateOne({ '_id': id }
|
||||
, { $set: data.data }
|
||||
@@ -269,7 +277,7 @@ function init (env, ctx, server) {
|
||||
return;
|
||||
}
|
||||
|
||||
var objId = new ObjectID(data._id);
|
||||
var objId = safeObjectID(data._id);
|
||||
ctx.store.collection(collection).updateOne({ '_id': objId }, { $unset: data.data }
|
||||
, function(err, results) {
|
||||
|
||||
@@ -434,7 +442,7 @@ function init (env, ctx, server) {
|
||||
if (array.length > 0) {
|
||||
console.log(LOG_DEDUP + 'Found similiar', array[0]);
|
||||
array[0].created_at = data.data.created_at;
|
||||
var objId = new ObjectID(array[0]._id);
|
||||
var objId = safeObjectID(array[0]._id);
|
||||
ctx.store.collection(collection).updateOne({ '_id': objId }, { $set: { created_at: data.data.created_at } });
|
||||
if (callback) {
|
||||
callback([array[0]]);
|
||||
@@ -553,7 +561,7 @@ function init (env, ctx, server) {
|
||||
return;
|
||||
}
|
||||
|
||||
var objId = new ObjectID(data._id);
|
||||
var objId = safeObjectID(data._id);
|
||||
ctx.store.collection(collection).deleteOne({ '_id': objId }
|
||||
, function(err, stat) {
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
const times = require('./times');
|
||||
|
||||
function hasOwnProperty (obj, field) {
|
||||
return obj && Object.prototype.hasOwnProperty.call(obj, field);
|
||||
}
|
||||
|
||||
function toMills (value) {
|
||||
if (value === null || typeof value === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && value.trim() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const numeric = Number(value);
|
||||
if (Number.isFinite(numeric)) {
|
||||
return numeric;
|
||||
}
|
||||
|
||||
const dateMills = new Date(value).getTime();
|
||||
return Number.isFinite(dateMills) ? dateMills : null;
|
||||
}
|
||||
|
||||
function resolveBaseMills (doc, fallbackDoc) {
|
||||
return toMills(doc && doc.mills)
|
||||
?? toMills(fallbackDoc && fallbackDoc.mills)
|
||||
?? toMills(doc && doc.created_at)
|
||||
?? toMills(doc && doc.date)
|
||||
?? toMills(fallbackDoc && fallbackDoc.created_at)
|
||||
?? toMills(fallbackDoc && fallbackDoc.date);
|
||||
}
|
||||
|
||||
function normalizeTreatmentDuration (doc, fallbackDoc) {
|
||||
const baseMills = resolveBaseMills(doc, fallbackDoc);
|
||||
|
||||
if ((!hasOwnProperty(doc, 'endmills') || doc.endmills == null) && baseMills !== null) {
|
||||
if (hasOwnProperty(doc, 'durationInMilliseconds')) {
|
||||
const durationInMilliseconds = Number(doc.durationInMilliseconds) || 0;
|
||||
if (durationInMilliseconds > 0) {
|
||||
doc.endmills = baseMills + durationInMilliseconds;
|
||||
}
|
||||
} else if (hasOwnProperty(doc, 'duration')) {
|
||||
doc.endmills = baseMills + times.mins(Number(doc.duration) || 0).msecs;
|
||||
} else if (hasOwnProperty(fallbackDoc, 'durationInMilliseconds')) {
|
||||
const durationInMilliseconds = Number(fallbackDoc.durationInMilliseconds) || 0;
|
||||
if (durationInMilliseconds > 0) {
|
||||
doc.endmills = baseMills + durationInMilliseconds;
|
||||
}
|
||||
} else if (hasOwnProperty(fallbackDoc, 'duration')) {
|
||||
doc.endmills = baseMills + times.mins(Number(fallbackDoc.duration) || 0).msecs;
|
||||
}
|
||||
}
|
||||
|
||||
const endMills = hasOwnProperty(doc, 'endmills') ? Number(doc.endmills) : NaN;
|
||||
if (Number.isFinite(baseMills) && Number.isFinite(endMills) && endMills >= baseMills) {
|
||||
doc.durationInMilliseconds = endMills - baseMills;
|
||||
doc.duration = Math.round(doc.durationInMilliseconds / 60000);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
normalizeTreatmentDuration,
|
||||
resolveBaseMills
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
describe('API3 PATCH operation', function () {
|
||||
const should = require('should');
|
||||
const security = require('../lib/api3/security');
|
||||
const patchOperation = require('../lib/api3/generic/patch/operation');
|
||||
|
||||
let originalAuthenticate;
|
||||
let originalDemandPermission;
|
||||
|
||||
beforeEach(() => {
|
||||
originalAuthenticate = security.authenticate;
|
||||
originalDemandPermission = security.demandPermission;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
security.authenticate = originalAuthenticate;
|
||||
security.demandPermission = originalDemandPermission;
|
||||
});
|
||||
|
||||
it('emits the merged patched document without re-reading storage', async () => {
|
||||
const date = 1741255200000;
|
||||
const storageDoc = {
|
||||
identifier: 'test-patch-operation',
|
||||
date: date,
|
||||
utcOffset: 0,
|
||||
app: 'nightscout-test',
|
||||
device: 'ns://pump',
|
||||
eventType: 'Temp Basal',
|
||||
absolute: 1.2,
|
||||
duration: 30,
|
||||
srvModified: date - 1000
|
||||
};
|
||||
const patchDoc = {
|
||||
absolute: 0.7,
|
||||
duration: 0,
|
||||
durationInMilliseconds: 26584
|
||||
};
|
||||
const events = [];
|
||||
|
||||
security.authenticate = async () => ({ subject: { name: 'patch-user' } });
|
||||
security.demandPermission = async () => true;
|
||||
|
||||
const col = {
|
||||
colName: 'treatments',
|
||||
storage: {
|
||||
identifyingFilter: identifier => ({ identifier }),
|
||||
findOneFilter: async () => [Object.assign({}, storageDoc)],
|
||||
updateOne: async () => ({ updated: 1 }),
|
||||
findOne: async () => {
|
||||
throw new Error('PATCH should not re-read the document after update');
|
||||
}
|
||||
},
|
||||
resolveDates: doc => new Date(doc.srvModified),
|
||||
autoPrune: () => {
|
||||
events.push({ name: 'auto-prune' });
|
||||
}
|
||||
};
|
||||
const ctx = {
|
||||
bus: {
|
||||
emit: (name, payload) => {
|
||||
events.push({ name, payload });
|
||||
}
|
||||
}
|
||||
};
|
||||
const req = {
|
||||
body: Object.assign({}, patchDoc),
|
||||
params: { identifier: storageDoc.identifier },
|
||||
get: () => null
|
||||
};
|
||||
const res = createResponse();
|
||||
const handler = patchOperation(ctx, {}, {}, col);
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
res.statusCode.should.equal(200);
|
||||
should.exist(res.headers['Last-Modified']);
|
||||
|
||||
const updateEvent = events.find(event => event.name === 'storage-socket-update');
|
||||
should.exist(updateEvent);
|
||||
updateEvent.payload.colName.should.equal('treatments');
|
||||
updateEvent.payload.doc.should.containEql({
|
||||
identifier: storageDoc.identifier,
|
||||
absolute: 0.7,
|
||||
duration: 0,
|
||||
durationInMilliseconds: 26584,
|
||||
modifiedBy: 'patch-user'
|
||||
});
|
||||
updateEvent.payload.doc.endmills.should.equal(date + 26584);
|
||||
});
|
||||
});
|
||||
|
||||
function createResponse () {
|
||||
return {
|
||||
headers: {},
|
||||
headersSent: false,
|
||||
statusCode: null,
|
||||
body: null,
|
||||
setHeader (name, value) {
|
||||
this.headers[name] = value;
|
||||
},
|
||||
status (code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json (body) {
|
||||
this.body = body;
|
||||
this.headersSent = true;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -235,5 +235,44 @@ describe('API3 PATCH', function() {
|
||||
self.cache.nextShouldEql(self.col, body)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should normalize endmills when patching durationInMilliseconds', async () => {
|
||||
const tempBasalDoc = {
|
||||
date: self.validDoc.date + 1,
|
||||
utcOffset: -180,
|
||||
app: testConst.TEST_APP,
|
||||
device: testConst.TEST_DEVICE + ' API3 PATCH duration',
|
||||
eventType: 'Temp Basal',
|
||||
absolute: 1.2,
|
||||
duration: 30
|
||||
};
|
||||
tempBasalDoc.identifier = opTools.calculateIdentifier(tempBasalDoc);
|
||||
|
||||
let res = await self.instance.post(`${self.url}`, self.jwt.create)
|
||||
.send(tempBasalDoc)
|
||||
.expect(201);
|
||||
|
||||
res.body.status.should.equal(201);
|
||||
self.cache.nextShouldEql(self.col, tempBasalDoc)
|
||||
|
||||
res = await self.instance.patch(`${self.url}/${tempBasalDoc.identifier}`, self.jwt.update)
|
||||
.send({
|
||||
absolute: 0.7,
|
||||
duration: 0,
|
||||
durationInMilliseconds: 26584
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
res.body.status.should.equal(200);
|
||||
|
||||
const body = await self.get(tempBasalDoc.identifier);
|
||||
body.absolute.should.equal(0.7);
|
||||
body.duration.should.equal(0);
|
||||
body.durationInMilliseconds.should.equal(26584);
|
||||
body.endmills.should.equal(tempBasalDoc.date + 26584);
|
||||
body.modifiedBy.should.equal(self.subject.apiUpdate.name);
|
||||
|
||||
self.cache.nextShouldEql(self.col, body)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -277,5 +277,17 @@ describe('API3 SEARCH', function() {
|
||||
apiApp.set('API3_MAX_LIMIT', limitBackup);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respect string API3_MAX_LIMIT defaults', async () => {
|
||||
const apiApp = self.instance.ctx.apiApp
|
||||
, limitBackup = apiApp.get('API3_MAX_LIMIT');
|
||||
apiApp.set('API3_MAX_LIMIT', '5');
|
||||
let res = await self.instance.get(`${self.url}`, self.jwt.read)
|
||||
.expect(200);
|
||||
|
||||
res.body.status.should.equal(200);
|
||||
res.body.result.length.should.equal(5);
|
||||
apiApp.set('API3_MAX_LIMIT', limitBackup);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const find = require('../lib/api3/storage/mongoCollection/find');
|
||||
|
||||
describe('API3 mongoCollection findMany', function () {
|
||||
function createStubCollection (observed) {
|
||||
return {
|
||||
find: function () {
|
||||
return this;
|
||||
},
|
||||
sort: function () {
|
||||
return this;
|
||||
},
|
||||
limit: function (value) {
|
||||
observed.limit = value;
|
||||
return this;
|
||||
},
|
||||
skip: function (value) {
|
||||
observed.skip = value;
|
||||
return this;
|
||||
},
|
||||
project: function () {
|
||||
return this;
|
||||
},
|
||||
toArray: function (callback) {
|
||||
callback(null, []);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it('coerces string limit and skip before calling Mongo', async function () {
|
||||
const observed = {};
|
||||
const col = createStubCollection(observed);
|
||||
|
||||
const result = await find.findMany(col, {
|
||||
filter: [],
|
||||
sort: { created_at: -1 },
|
||||
limit: '5',
|
||||
skip: '2',
|
||||
projection: {}
|
||||
});
|
||||
|
||||
observed.limit.should.equal(5);
|
||||
observed.skip.should.equal(2);
|
||||
result.should.eql([]);
|
||||
});
|
||||
|
||||
|
||||
it('coerces float-like values to integers before calling Mongo', async function () {
|
||||
const observed = {};
|
||||
const col = createStubCollection(observed);
|
||||
|
||||
await find.findMany(col, {
|
||||
filter: [],
|
||||
sort: { created_at: -1 },
|
||||
limit: 5.9,
|
||||
skip: 2.4,
|
||||
projection: {}
|
||||
});
|
||||
|
||||
observed.limit.should.equal(5);
|
||||
observed.skip.should.equal(2);
|
||||
});
|
||||
});
|
||||
@@ -297,6 +297,47 @@ describe('API3 UPDATE', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should normalize endmills when replacing durationInMilliseconds', async () => {
|
||||
const tempBasalDoc = {
|
||||
identifier: utils.randomString('32', 'aA#'),
|
||||
date: (new Date()).getTime() + 1,
|
||||
utcOffset: -180,
|
||||
app: testConst.TEST_APP,
|
||||
device: testConst.TEST_DEVICE + ' API3 UPDATE duration',
|
||||
eventType: 'Temp Basal',
|
||||
absolute: 1.2,
|
||||
duration: 30
|
||||
};
|
||||
|
||||
let res = await self.instance.put(`${self.url}/${tempBasalDoc.identifier}`, self.jwt.all)
|
||||
.send(tempBasalDoc)
|
||||
.expect(201);
|
||||
|
||||
res.body.status.should.equal(201);
|
||||
self.cache.nextShouldEql(self.col, tempBasalDoc)
|
||||
|
||||
const replacedDoc = Object.assign({}, tempBasalDoc, {
|
||||
absolute: 0.7,
|
||||
duration: 0,
|
||||
durationInMilliseconds: 26584
|
||||
});
|
||||
|
||||
res = await self.instance.put(`${self.url}/${tempBasalDoc.identifier}`, self.jwt.update)
|
||||
.send(replacedDoc)
|
||||
.expect(200);
|
||||
|
||||
res.body.status.should.equal(200);
|
||||
self.cache.nextShouldEql(self.col, replacedDoc)
|
||||
|
||||
const body = await self.get(tempBasalDoc.identifier);
|
||||
body.absolute.should.equal(0.7);
|
||||
body.duration.should.equal(0);
|
||||
body.durationInMilliseconds.should.equal(26584);
|
||||
body.endmills.should.equal(tempBasalDoc.date + 26584);
|
||||
body.subject.should.equal(self.subject.apiUpdate.name);
|
||||
});
|
||||
|
||||
|
||||
it('should not update deleted document', async () => {
|
||||
let res = await self.instance.delete(self.urlIdent, self.jwt.delete)
|
||||
.expect(200);
|
||||
@@ -312,4 +353,3 @@ describe('API3 UPDATE', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
+26
-1
@@ -41,6 +41,32 @@ describe('ddata', function ( ) {
|
||||
done( );
|
||||
});
|
||||
|
||||
it('processRawDataForRuntime derives duration and endmills from durationInMilliseconds', function () {
|
||||
var ddata = require('../lib/data/ddata')();
|
||||
var createdAt = '2026-03-06T10:00:00.000Z';
|
||||
var result = ddata.processRawDataForRuntime([{
|
||||
_id: '507f1f77bcf86cd799439011',
|
||||
created_at: createdAt,
|
||||
durationInMilliseconds: 26584
|
||||
}])[0];
|
||||
|
||||
result.mills.should.equal(new Date(createdAt).getTime());
|
||||
result.duration.should.equal(0);
|
||||
result.endmills.should.equal(result.mills + 26584);
|
||||
});
|
||||
|
||||
it('idMergePreferNew matches records by identifier when _id is missing', function () {
|
||||
var ddata = require('../lib/data/ddata')();
|
||||
var merged = ddata.idMergePreferNew(
|
||||
[{ _id: 'mongo-id', identifier: 'loop-id', carbs: 15 }],
|
||||
[{ identifier: 'loop-id', carbs: 0 }]
|
||||
);
|
||||
|
||||
merged.length.should.equal(1);
|
||||
merged[0].carbs.should.equal(0);
|
||||
merged[0].identifier.should.equal('loop-id');
|
||||
});
|
||||
|
||||
// TODO: ensure partition function gets called via:
|
||||
// Properties
|
||||
// * ddata.devicestatus
|
||||
@@ -57,4 +83,3 @@ describe('ddata', function ( ) {
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
});
|
||||
}
|
||||
|
||||
function treatmentsCollection() {
|
||||
return self.ctx.store.collection(self.env.treatments_collection);
|
||||
}
|
||||
|
||||
describe('dbAdd with treatments collection', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
@@ -261,6 +265,97 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('dbUpdate supports custom string _id values', function (done) {
|
||||
connectAndAuthorize(function (err, socket) {
|
||||
if (err) return done(err);
|
||||
|
||||
var legacyId = 'legacy-string-id-update';
|
||||
var createdAt = new Date().toISOString();
|
||||
|
||||
treatmentsCollection().insertOne({
|
||||
_id: legacyId,
|
||||
eventType: 'Note',
|
||||
created_at: createdAt,
|
||||
notes: 'legacy original'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
|
||||
socket.emit('dbUpdate', {
|
||||
collection: 'treatments',
|
||||
_id: legacyId,
|
||||
data: {
|
||||
notes: 'legacy updated'
|
||||
}
|
||||
}, function (updateResult) {
|
||||
should.exist(updateResult);
|
||||
updateResult.result.should.equal('success');
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
doc.notes.should.equal('legacy updated');
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbUpdate with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dbUpdateUnset operations', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dbUpdateUnset supports custom string _id values', function (done) {
|
||||
connectAndAuthorize(function (err, socket) {
|
||||
if (err) return done(err);
|
||||
|
||||
var legacyId = 'legacy-string-id-unset';
|
||||
var createdAt = new Date().toISOString();
|
||||
|
||||
treatmentsCollection().insertOne({
|
||||
_id: legacyId,
|
||||
eventType: 'Note',
|
||||
created_at: createdAt,
|
||||
notes: 'remove me'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
|
||||
socket.emit('dbUpdateUnset', {
|
||||
collection: 'treatments',
|
||||
_id: legacyId,
|
||||
data: {
|
||||
notes: 1
|
||||
}
|
||||
}, function (updateResult) {
|
||||
should.exist(updateResult);
|
||||
updateResult.result.should.equal('success');
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
should.not.exist(doc.notes);
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbUpdateUnset with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dbRemove operations', function () {
|
||||
@@ -300,6 +395,97 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('dbRemove supports custom string _id values', function (done) {
|
||||
connectAndAuthorize(function (err, socket) {
|
||||
if (err) return done(err);
|
||||
|
||||
var legacyId = 'legacy-string-id-remove';
|
||||
var createdAt = new Date().toISOString();
|
||||
|
||||
treatmentsCollection().insertOne({
|
||||
_id: legacyId,
|
||||
eventType: 'Note',
|
||||
created_at: createdAt,
|
||||
notes: 'delete me'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
|
||||
socket.emit('dbRemove', {
|
||||
collection: 'treatments',
|
||||
_id: legacyId
|
||||
}, function (removeResult) {
|
||||
should.exist(removeResult);
|
||||
removeResult.result.should.equal('success');
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.not.exist(doc);
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbRemove with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dbAdd dedupe operations', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dbAdd dedupe updates custom string _id records without ObjectId conversion', function (done) {
|
||||
connectAndAuthorize(function (err, socket) {
|
||||
if (err) return done(err);
|
||||
|
||||
var legacyId = 'legacy-string-id-dedupe';
|
||||
var originalCreatedAt = new Date().toISOString();
|
||||
var dedupedCreatedAt = new Date(Date.now() + 1000).toISOString();
|
||||
|
||||
treatmentsCollection().insertOne({
|
||||
_id: legacyId,
|
||||
eventType: 'Note',
|
||||
created_at: originalCreatedAt,
|
||||
notes: 'existing legacy note'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
|
||||
socket.emit('dbAdd', {
|
||||
collection: 'treatments',
|
||||
data: {
|
||||
eventType: 'Note',
|
||||
created_at: dedupedCreatedAt,
|
||||
notes: 'incoming legacy note'
|
||||
}
|
||||
}, function (result) {
|
||||
should.exist(result);
|
||||
result.should.be.instanceof(Array);
|
||||
result.length.should.equal(1);
|
||||
result[0]._id.should.equal(legacyId);
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
doc.created_at.should.equal(dedupedCreatedAt);
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbAdd dedupe with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user