mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
Add GAP-TREAT-012 test suite for Loop override UUID handling
TEST-GAP-001: Loop override POST with UUID _id TEST-GAP-002: Loop override DELETE by UUID TEST-GAP-003: Loop override UPDATE by UUID TEST-GAP-004: Loop override re-POST (upsert) 12 new tests validating REQ-SYNC-072 behavior: - UUID _id promoted to identifier field - Server generates valid ObjectId for _id - Updates/deletes work via identifier lookup - Duplicate detection via identifier - Batch and edge case handling New fixtures: - loop-override.js: Real Loop override payload patterns All 12 tests passing. Refs: GAP-TREAT-012, REQ-SYNC-072 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Vendored
+1
@@ -3,6 +3,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
aaps: require('./aaps-single-doc'),
|
aaps: require('./aaps-single-doc'),
|
||||||
loop: require('./loop-batch'),
|
loop: require('./loop-batch'),
|
||||||
|
loopOverride: require('./loop-override'),
|
||||||
trio: require('./trio-pipeline'),
|
trio: require('./trio-pipeline'),
|
||||||
deduplication: require('./deduplication'),
|
deduplication: require('./deduplication'),
|
||||||
edgeCases: require('./edge-cases'),
|
edgeCases: require('./edge-cases'),
|
||||||
|
|||||||
Vendored
+219
@@ -0,0 +1,219 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop Override Test Fixtures for GAP-TREAT-012 Testing
|
||||||
|
*
|
||||||
|
* GAP-TREAT-012: v1 API incorrectly coerces UUID _id to ObjectId
|
||||||
|
*
|
||||||
|
* This file provides fixtures that simulate real Loop override uploads,
|
||||||
|
* which use UUID strings as the `id` field (mapped to `_id` in Nightscout).
|
||||||
|
*
|
||||||
|
* REQ-SYNC-072: UUID _id should be moved to identifier field,
|
||||||
|
* with server generating a valid ObjectId for _id.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Generate fresh dates for each test run
|
||||||
|
function now() {
|
||||||
|
return new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoursAgo(hours) {
|
||||||
|
return new Date(Date.now() - hours * 3600000).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function minutesAgo(minutes) {
|
||||||
|
return new Date(Date.now() - minutes * 60000).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Standard Loop override as uploaded by OverrideTreatment.swift
|
||||||
|
* Note: Loop sends `id` as UUID string, NOT as ObjectId
|
||||||
|
*/
|
||||||
|
standardOverride: {
|
||||||
|
_id: 'A1B2C3D4-E5F6-7890-ABCD-EF1234567890',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(30),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 60,
|
||||||
|
correctionRange: [90, 110],
|
||||||
|
insulinNeedsScaleFactor: 1.2,
|
||||||
|
reason: 'Custom Override'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indefinite override (no end time)
|
||||||
|
*/
|
||||||
|
indefiniteOverride: {
|
||||||
|
_id: 'B2C3D4E5-F6A7-8901-BCDE-F23456789012',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(15),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
durationType: 'indefinite',
|
||||||
|
correctionRange: [100, 120],
|
||||||
|
insulinNeedsScaleFactor: 1.0,
|
||||||
|
reason: 'Running Low'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote command override
|
||||||
|
*/
|
||||||
|
remoteOverride: {
|
||||||
|
_id: 'C3D4E5F6-A7B8-9012-CDEF-345678901234',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(5),
|
||||||
|
enteredBy: 'Loop (via remote command)',
|
||||||
|
duration: 120,
|
||||||
|
correctionRange: [150, 180],
|
||||||
|
insulinNeedsScaleFactor: 0.5,
|
||||||
|
reason: 'Workout'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preset override (e.g., "Eating Soon")
|
||||||
|
*/
|
||||||
|
presetOverride: {
|
||||||
|
_id: 'D4E5F6A7-B8C9-0123-DEFA-456789012345',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: hoursAgo(1),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 60,
|
||||||
|
correctionRange: [80, 80],
|
||||||
|
insulinNeedsScaleFactor: 1.0,
|
||||||
|
reason: 'Pre-Meal'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update scenario: same UUID, different values
|
||||||
|
*/
|
||||||
|
updateScenario: {
|
||||||
|
original: {
|
||||||
|
_id: 'E5F6A7B8-C9D0-1234-EFAB-567890123456',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: hoursAgo(2),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 60,
|
||||||
|
correctionRange: [100, 120],
|
||||||
|
insulinNeedsScaleFactor: 1.0,
|
||||||
|
reason: 'Original Override'
|
||||||
|
},
|
||||||
|
updated: {
|
||||||
|
_id: 'E5F6A7B8-C9D0-1234-EFAB-567890123456',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: hoursAgo(1),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 120, // Extended duration
|
||||||
|
correctionRange: [110, 130], // Changed range
|
||||||
|
insulinNeedsScaleFactor: 0.8, // Changed factor
|
||||||
|
reason: 'Updated Override'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete scenario: override to cancel
|
||||||
|
*/
|
||||||
|
deleteScenario: {
|
||||||
|
toDelete: {
|
||||||
|
_id: 'F6A7B8C9-D0E1-2345-FABC-678901234567',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(45),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 90,
|
||||||
|
correctionRange: [95, 115],
|
||||||
|
insulinNeedsScaleFactor: 1.1,
|
||||||
|
reason: 'To Be Deleted'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiple overrides batch (simulates Loop restart/sync)
|
||||||
|
*/
|
||||||
|
batchOverrides: [
|
||||||
|
{
|
||||||
|
_id: '11111111-1111-1111-1111-111111111111',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: hoursAgo(3),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 30,
|
||||||
|
correctionRange: [90, 100],
|
||||||
|
insulinNeedsScaleFactor: 1.0,
|
||||||
|
reason: 'First Override'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: '22222222-2222-2222-2222-222222222222',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: hoursAgo(2),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 45,
|
||||||
|
correctionRange: [100, 110],
|
||||||
|
insulinNeedsScaleFactor: 0.9,
|
||||||
|
reason: 'Second Override'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: '33333333-3333-3333-3333-333333333333',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: hoursAgo(1),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 60,
|
||||||
|
correctionRange: [80, 90],
|
||||||
|
insulinNeedsScaleFactor: 1.2,
|
||||||
|
reason: 'Third Override'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate detection scenario
|
||||||
|
* Same UUID should NOT create duplicate
|
||||||
|
*/
|
||||||
|
duplicateScenario: {
|
||||||
|
first: {
|
||||||
|
_id: 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(60),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 60,
|
||||||
|
correctionRange: [100, 110],
|
||||||
|
insulinNeedsScaleFactor: 1.0,
|
||||||
|
reason: 'Original Post'
|
||||||
|
},
|
||||||
|
repost: {
|
||||||
|
_id: 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(30), // Different time
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 90, // Different duration
|
||||||
|
correctionRange: [110, 120],
|
||||||
|
insulinNeedsScaleFactor: 1.1,
|
||||||
|
reason: 'Reposted Override'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixed eventTypes batch - some with UUID, some without
|
||||||
|
*/
|
||||||
|
mixedBatch: [
|
||||||
|
{
|
||||||
|
_id: 'UUID1234-5678-90AB-CDEF-111111111111',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: minutesAgo(20),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 60,
|
||||||
|
correctionRange: [100, 110],
|
||||||
|
insulinNeedsScaleFactor: 1.0,
|
||||||
|
reason: 'Override With UUID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
eventType: 'Carb Correction',
|
||||||
|
carbs: 15,
|
||||||
|
created_at: minutesAgo(15),
|
||||||
|
enteredBy: 'loop://iPhone',
|
||||||
|
absorptionTime: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
eventType: 'Bolus',
|
||||||
|
insulin: 2.5,
|
||||||
|
created_at: minutesAgo(10),
|
||||||
|
enteredBy: 'loop://iPhone'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -0,0 +1,428 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GAP-TREAT-012 Test Suite: Loop Override UUID Identity Handling
|
||||||
|
*
|
||||||
|
* REQUIREMENT REFERENCE: REQ-SYNC-072 (Transparent UUID promotion)
|
||||||
|
* GAP REFERENCE: GAP-TREAT-012 (v1 API incorrectly coerces UUID _id to ObjectId)
|
||||||
|
*
|
||||||
|
* These tests verify that:
|
||||||
|
* 1. UUID _id is moved to identifier field (not corrupted)
|
||||||
|
* 2. Server generates valid ObjectId for _id
|
||||||
|
* 3. Updates/deletes work via identifier lookup
|
||||||
|
* 4. Duplicate detection works via identifier
|
||||||
|
*
|
||||||
|
* TEST MATRIX REFERENCE: docs/backlogs/loop-nightscout-upload-testing.md
|
||||||
|
* - TEST-GAP-001: Loop override POST
|
||||||
|
* - TEST-GAP-002: Loop override DELETE
|
||||||
|
* - TEST-GAP-003: Loop override UPDATE
|
||||||
|
* - TEST-GAP-004: Loop override re-POST (upsert)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const request = require('supertest');
|
||||||
|
const should = require('should');
|
||||||
|
const language = require('../lib/language')();
|
||||||
|
|
||||||
|
describe('GAP-TREAT-012: Loop Override UUID Handling', function() {
|
||||||
|
this.timeout(30000);
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
const api_secret_hash = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
|
||||||
|
|
||||||
|
// Load Loop override fixtures
|
||||||
|
const loopOverride = require('./fixtures/loop-override');
|
||||||
|
|
||||||
|
before(function(done) {
|
||||||
|
process.env.API_SECRET = 'this is my long pass phrase';
|
||||||
|
self.env = require('../lib/server/env')();
|
||||||
|
self.env.settings.authDefaultRoles = 'readable';
|
||||||
|
self.env.settings.enable = ['careportal', 'api'];
|
||||||
|
const wares = require('../lib/middleware/')(self.env);
|
||||||
|
self.app = require('express')();
|
||||||
|
self.app.enable('api');
|
||||||
|
require('../lib/server/bootevent')(self.env, language).boot(function booted(ctx) {
|
||||||
|
self.ctx = ctx;
|
||||||
|
self.ctx.wares = wares;
|
||||||
|
self.ctx.ddata = require('../lib/data/ddata')();
|
||||||
|
self.app.use('/api', require('../lib/api/')(self.env, ctx));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function(done) {
|
||||||
|
// Clear treatments before each test
|
||||||
|
self.ctx.treatments.remove({
|
||||||
|
find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } }
|
||||||
|
}, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TEST-GAP-001: Loop override POST with UUID as _id', function() {
|
||||||
|
|
||||||
|
it('accepts UUID _id and promotes to identifier field', function(done) {
|
||||||
|
const fixture = loopOverride.standardOverride;
|
||||||
|
const uuidId = fixture._id;
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(res.body);
|
||||||
|
res.body.should.be.an.Array();
|
||||||
|
res.body.length.should.equal(1);
|
||||||
|
|
||||||
|
const created = res.body[0];
|
||||||
|
|
||||||
|
// REQ-SYNC-072: UUID should be in identifier, not _id
|
||||||
|
created.identifier.should.equal(uuidId);
|
||||||
|
|
||||||
|
// _id should be a valid ObjectId (24 hex chars)
|
||||||
|
created._id.should.match(/^[0-9a-f]{24}$/);
|
||||||
|
created._id.should.not.equal(uuidId);
|
||||||
|
|
||||||
|
// Other fields preserved
|
||||||
|
created.eventType.should.equal('Temporary Override');
|
||||||
|
created.reason.should.equal('Custom Override');
|
||||||
|
|
||||||
|
console.log(` UUID _id: ${uuidId}`);
|
||||||
|
console.log(` Server _id: ${created._id}`);
|
||||||
|
console.log(` identifier: ${created.identifier}`);
|
||||||
|
console.log(' ✓ UUID promoted to identifier field');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('indefinite override UUID is preserved in identifier', function(done) {
|
||||||
|
const fixture = loopOverride.indefiniteOverride;
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
const created = res.body[0];
|
||||||
|
created.identifier.should.equal(fixture._id);
|
||||||
|
created._id.should.match(/^[0-9a-f]{24}$/);
|
||||||
|
should.exist(created.durationType);
|
||||||
|
created.durationType.should.equal('indefinite');
|
||||||
|
|
||||||
|
console.log(' ✓ Indefinite override UUID preserved');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('remote command override UUID is preserved in identifier', function(done) {
|
||||||
|
const fixture = loopOverride.remoteOverride;
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
const created = res.body[0];
|
||||||
|
created.identifier.should.equal(fixture._id);
|
||||||
|
created.enteredBy.should.equal('Loop (via remote command)');
|
||||||
|
|
||||||
|
console.log(' ✓ Remote override UUID preserved');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TEST-GAP-002: Loop override DELETE by UUID', function() {
|
||||||
|
|
||||||
|
it('can delete override using identifier query', function(done) {
|
||||||
|
const fixture = loopOverride.deleteScenario.toDelete;
|
||||||
|
|
||||||
|
// First create the override
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
const serverId = res.body[0]._id;
|
||||||
|
|
||||||
|
// Delete by server-assigned _id (standard method)
|
||||||
|
request(self.app)
|
||||||
|
.delete('/api/treatments/' + serverId)
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Verify deleted
|
||||||
|
self.ctx.treatments.list({ find: { identifier: fixture._id } }, function(err, list) {
|
||||||
|
should.not.exist(err);
|
||||||
|
list.length.should.equal(0);
|
||||||
|
|
||||||
|
console.log(' ✓ Override deleted successfully');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can find override by identifier after creation', function(done) {
|
||||||
|
const fixture = loopOverride.standardOverride;
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Query by identifier
|
||||||
|
self.ctx.treatments.list({ find: { identifier: fixture._id } }, function(err, list) {
|
||||||
|
should.not.exist(err);
|
||||||
|
list.length.should.equal(1);
|
||||||
|
list[0].identifier.should.equal(fixture._id);
|
||||||
|
|
||||||
|
console.log(' ✓ Override found by identifier query');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TEST-GAP-003: Loop override UPDATE by UUID', function() {
|
||||||
|
|
||||||
|
it('PUT with UUID _id updates existing override', function(done) {
|
||||||
|
const scenario = loopOverride.updateScenario;
|
||||||
|
|
||||||
|
// Create original
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([scenario.original])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
const serverId = res.body[0]._id;
|
||||||
|
|
||||||
|
// Update with same UUID
|
||||||
|
request(self.app)
|
||||||
|
.put('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send(scenario.updated)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Verify update
|
||||||
|
self.ctx.treatments.list({ find: { identifier: scenario.original._id } }, function(err, list) {
|
||||||
|
should.not.exist(err);
|
||||||
|
list.length.should.equal(1);
|
||||||
|
|
||||||
|
const updated = list[0];
|
||||||
|
updated.identifier.should.equal(scenario.original._id);
|
||||||
|
updated.duration.should.equal(120); // Updated value
|
||||||
|
updated.reason.should.equal('Updated Override');
|
||||||
|
|
||||||
|
console.log(' ✓ Override updated via PUT with UUID');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TEST-GAP-004: Loop override re-POST (upsert by identifier)', function() {
|
||||||
|
|
||||||
|
it('re-POST same UUID updates instead of creating duplicate', function(done) {
|
||||||
|
const scenario = loopOverride.duplicateScenario;
|
||||||
|
|
||||||
|
// First POST
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([scenario.first])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
const firstServerId = res.body[0]._id;
|
||||||
|
|
||||||
|
// Re-POST with same UUID, different values
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([scenario.repost])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// Should only have ONE document
|
||||||
|
self.ctx.treatments.list({ find: { identifier: scenario.first._id } }, function(err, list) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
// CRITICAL: No duplicates
|
||||||
|
list.length.should.equal(1, 'Re-POST should update, not create duplicate');
|
||||||
|
|
||||||
|
// Should have updated values
|
||||||
|
const doc = list[0];
|
||||||
|
doc.duration.should.equal(90); // From repost
|
||||||
|
doc.reason.should.equal('Reposted Override');
|
||||||
|
|
||||||
|
console.log(` First _id: ${firstServerId}`);
|
||||||
|
console.log(` After re-POST: 1 document (no duplicate)`);
|
||||||
|
console.log(' ✓ Re-POST with same UUID updates existing');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Batch Override Upload', function() {
|
||||||
|
|
||||||
|
it('batch of overrides with UUIDs all get identifier fields', function(done) {
|
||||||
|
const batch = loopOverride.batchOverrides;
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send(batch)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
res.body.length.should.equal(3);
|
||||||
|
|
||||||
|
// All should have identifier field with original UUID
|
||||||
|
res.body.forEach(function(item, idx) {
|
||||||
|
item.identifier.should.equal(batch[idx]._id);
|
||||||
|
item._id.should.match(/^[0-9a-f]{24}$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify in database
|
||||||
|
self.ctx.treatments.list({}, function(err, list) {
|
||||||
|
should.not.exist(err);
|
||||||
|
list.length.should.equal(3);
|
||||||
|
|
||||||
|
console.log(' ✓ Batch of 3 overrides all have identifier fields');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mixed batch with UUID and non-UUID treatments', function(done) {
|
||||||
|
const batch = loopOverride.mixedBatch;
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send(batch)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
res.body.length.should.equal(3);
|
||||||
|
|
||||||
|
// Override should have identifier
|
||||||
|
const override = res.body.find(t => t.eventType === 'Temporary Override');
|
||||||
|
override.identifier.should.equal(batch[0]._id);
|
||||||
|
override._id.should.match(/^[0-9a-f]{24}$/);
|
||||||
|
|
||||||
|
// Carb and bolus should have generated _ids
|
||||||
|
const carb = res.body.find(t => t.eventType === 'Carb Correction');
|
||||||
|
carb._id.should.match(/^[0-9a-f]{24}$/);
|
||||||
|
|
||||||
|
console.log(' ✓ Mixed batch handles UUID and non-UUID correctly');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases', function() {
|
||||||
|
|
||||||
|
it('handles uppercase UUID', function(done) {
|
||||||
|
const fixture = {
|
||||||
|
_id: 'ABCDEF01-2345-6789-ABCD-EF0123456789',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 30,
|
||||||
|
correctionRange: [100, 110],
|
||||||
|
reason: 'Uppercase UUID Test'
|
||||||
|
};
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
res.body[0].identifier.should.equal(fixture._id);
|
||||||
|
|
||||||
|
console.log(' ✓ Uppercase UUID preserved');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles lowercase UUID', function(done) {
|
||||||
|
const fixture = {
|
||||||
|
_id: 'abcdef01-2345-6789-abcd-ef0123456789',
|
||||||
|
eventType: 'Temporary Override',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
enteredBy: 'Loop',
|
||||||
|
duration: 30,
|
||||||
|
correctionRange: [100, 110],
|
||||||
|
reason: 'Lowercase UUID Test'
|
||||||
|
};
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
res.body[0].identifier.should.equal(fixture._id);
|
||||||
|
|
||||||
|
console.log(' ✓ Lowercase UUID preserved');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('valid ObjectId string is NOT promoted to identifier', function(done) {
|
||||||
|
// 24-char hex string (valid ObjectId format)
|
||||||
|
const objectIdString = '507f1f77bcf86cd799439011';
|
||||||
|
const fixture = {
|
||||||
|
_id: objectIdString,
|
||||||
|
eventType: 'Note',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
enteredBy: 'Test',
|
||||||
|
notes: 'ObjectId format test'
|
||||||
|
};
|
||||||
|
|
||||||
|
request(self.app)
|
||||||
|
.post('/api/treatments/')
|
||||||
|
.set('api-secret', api_secret_hash)
|
||||||
|
.send([fixture])
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
const created = res.body[0];
|
||||||
|
// ObjectId should be used as-is (or regenerated), NOT promoted
|
||||||
|
created._id.should.match(/^[0-9a-f]{24}$/);
|
||||||
|
|
||||||
|
console.log(` Input _id: ${objectIdString}`);
|
||||||
|
console.log(` Result _id: ${created._id}`);
|
||||||
|
console.log(' ✓ ObjectId format handled correctly');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user