mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
This test passes locally in ~50ms but occasionally times out at 30s in constrained GitHub runners. Adding retries(2) allows it to recover from transient CI resource contention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
407 lines
15 KiB
JavaScript
407 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Deduplication Behavior Tests
|
|
*
|
|
* REQUIREMENT REFERENCE: docs/proposals/mongodb-modernization-implementation-plan.md
|
|
*
|
|
* DEDUPLICATION RULES BY CLIENT:
|
|
* - AAPS: Uses pumpId + pumpType + pumpSerial for treatment deduplication
|
|
* Uses date + device + type for entry deduplication
|
|
* - Loop: Uses syncIdentifier field for deduplication
|
|
* - Trio: Uses id field (UUID) for deduplication
|
|
*
|
|
* CRITICAL BEHAVIORS:
|
|
* - Duplicate detection must work correctly
|
|
* - Response must indicate deduplication occurred
|
|
* - Original document ID must be returned for deduplicated items
|
|
* - Cross-client duplicates should NOT deduplicate (different fields)
|
|
*
|
|
* CLIENTS AFFECTED: Loop, Trio, AAPS
|
|
*/
|
|
|
|
const request = require('supertest');
|
|
const should = require('should');
|
|
const language = require('../lib/language')();
|
|
const fixtures = require('./fixtures');
|
|
|
|
describe('v1 API Deduplication Behavior', function() {
|
|
this.timeout(30000);
|
|
const self = this;
|
|
|
|
const api_secret_hash = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
|
|
|
|
// Use before() instead of beforeEach() for app setup - boots once for all tests
|
|
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, entries, and devicestatus before each test using fast deleteMany
|
|
self.ctx.treatments.remove({
|
|
find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } }
|
|
}, function() {
|
|
// Use deleteMany for faster cleanup of entries
|
|
self.ctx.entries().deleteMany({}, function() {
|
|
// Also clear devicestatus to reduce database load
|
|
self.ctx.devicestatus.remove({
|
|
find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } }
|
|
}, done);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('AAPS Deduplication - pumpId based', function() {
|
|
|
|
it('duplicate pumpId+pumpType+pumpSerial is detected and rejected', function(done) {
|
|
const fixture = fixtures.deduplication.aapsDuplicatePumpId;
|
|
|
|
// Insert first treatment
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
should.exist(res.body[0]._id);
|
|
const firstId = res.body[0]._id;
|
|
|
|
// Attempt to insert duplicate
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
// Duplicate should be detected
|
|
console.log(` First insert _id: ${firstId}`);
|
|
console.log(` Second insert response: ${JSON.stringify(res.body)}`);
|
|
|
|
// Verify only one document exists in database
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(1, 'Duplicate should not create second document');
|
|
list[0]._id.toString().should.equal(firstId, 'Only original document should exist');
|
|
|
|
console.log(' ✓ AAPS pumpId deduplication working');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Retry on timeout - CI runners can be slow under load
|
|
it('duplicate entry with same date+device+type is detected', function(done) {
|
|
this.timeout(60000); // 60s for slow CI
|
|
this.retries(2);
|
|
const fixture = fixtures.deduplication.aapsDuplicateEntry;
|
|
|
|
// Insert first entry
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', api_secret_hash)
|
|
.set('Accept', 'application/json')
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
should.exist(res.body[0]._id);
|
|
const firstId = res.body[0]._id;
|
|
|
|
// Attempt to insert duplicate
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', api_secret_hash)
|
|
.set('Accept', 'application/json')
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
|
|
// Verify only one entry exists
|
|
self.ctx.entries.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(1, 'Duplicate entry should not create second document');
|
|
|
|
console.log(' ✓ AAPS entry deduplication working');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Loop Deduplication - syncIdentifier based', function() {
|
|
|
|
it('duplicate syncIdentifier is detected and rejected', function(done) {
|
|
const fixture = fixtures.deduplication.loopDuplicateSyncId;
|
|
|
|
// Insert first treatment
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
should.exist(res.body[0]._id);
|
|
const firstId = res.body[0]._id;
|
|
|
|
// Attempt to insert duplicate
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
console.log(` First insert _id: ${firstId}`);
|
|
console.log(` Second insert response: ${JSON.stringify(res.body)}`);
|
|
|
|
// Verify only one document exists
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(1, 'Duplicate syncIdentifier should not create second document');
|
|
|
|
const doc = list[0];
|
|
doc.syncIdentifier.should.equal(fixture.first.syncIdentifier);
|
|
|
|
console.log(' ✓ Loop syncIdentifier deduplication working');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('duplicate dose with syncIdentifier is detected', function(done) {
|
|
const fixture = fixtures.deduplication.loopDuplicateDose;
|
|
|
|
// Insert first dose
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
const firstId = res.body[0]._id;
|
|
|
|
// Attempt to insert duplicate
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
// Verify deduplication
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(1, 'Duplicate dose should not create second document');
|
|
|
|
console.log(' ✓ Loop dose deduplication working');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Trio Deduplication - id field based', function() {
|
|
|
|
it('duplicate id field (UUID) is detected and rejected', function(done) {
|
|
const fixture = fixtures.deduplication.trioDuplicateId;
|
|
|
|
// Insert first treatment
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
should.exist(res.body[0]._id);
|
|
const firstId = res.body[0]._id;
|
|
|
|
// Attempt to insert duplicate
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
console.log(` First insert _id: ${firstId}`);
|
|
console.log(` Second insert response: ${JSON.stringify(res.body)}`);
|
|
|
|
// Verify only one document exists
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(1, 'Duplicate Trio id should not create second document');
|
|
|
|
const doc = list[0];
|
|
doc.id.should.equal(fixture.first.id);
|
|
|
|
console.log(' ✓ Trio id field deduplication working');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('duplicate temporary target with id is detected', function(done) {
|
|
const fixture = fixtures.deduplication.trioDuplicateTempTarget;
|
|
|
|
// Insert first temp target
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
const firstId = res.body[0]._id;
|
|
|
|
// Attempt to insert duplicate
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
// Verify deduplication
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(1, 'Duplicate temp target should not create second document');
|
|
|
|
console.log(' ✓ Trio temp target deduplication working');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Batch with Mixed Duplicates', function() {
|
|
|
|
it('batch containing duplicates inserts only unique items', function(done) {
|
|
const batch = fixtures.deduplication.batchWithDuplicates;
|
|
|
|
// Batch has: note-1, note-2, note-1 (dup), note-3
|
|
// Expected: 3 unique notes (note-1, note-2, note-3)
|
|
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send(batch)
|
|
.end(function(err, res) {
|
|
console.log(` Batch sent: ${batch.length} items`);
|
|
console.log(` Response: ${JSON.stringify(res.body)}`);
|
|
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
|
|
console.log(` Database contains: ${list.length} documents`);
|
|
|
|
// Count unique id values
|
|
const uniqueIds = new Set(list.map(item => item.id));
|
|
console.log(` Unique id values: ${uniqueIds.size} (${Array.from(uniqueIds).join(', ')})`);
|
|
|
|
// Should have 3 unique notes
|
|
uniqueIds.size.should.equal(3, 'Should have 3 unique notes (note-1, note-2, note-3)');
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Cross-Client Duplicate Detection', function() {
|
|
|
|
it('AAPS and Trio uploads of same event do NOT deduplicate (different fields)', function(done) {
|
|
const crossClient = fixtures.deduplication.crossClientDuplicates;
|
|
|
|
// Insert AAPS upload first
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([crossClient.aapsUpload])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
const aapsId = res.body[0]._id;
|
|
|
|
// Insert Trio upload of "same" event
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([crossClient.trioUploadSameEvent])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
const trioId = res.body[0]._id;
|
|
|
|
// Verify BOTH documents exist (no cross-client deduplication)
|
|
self.ctx.treatments.list({}, function(err, list) {
|
|
should.not.exist(err);
|
|
list.length.should.equal(2, 'AAPS and Trio should NOT deduplicate each other');
|
|
|
|
const ids = list.map(item => item._id.toString());
|
|
ids.should.containEql(aapsId);
|
|
ids.should.containEql(trioId);
|
|
|
|
console.log(' ✓ Cross-client uploads do NOT deduplicate (expected behavior)');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Deduplication Response Format', function() {
|
|
|
|
it('deduplicated item returns original _id in response', function(done) {
|
|
const fixture = fixtures.deduplication.loopDuplicateSyncId;
|
|
|
|
// Insert first treatment
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.first])
|
|
.expect(200)
|
|
.end(function(err, res) {
|
|
should.not.exist(err);
|
|
const originalId = res.body[0]._id;
|
|
|
|
// Insert duplicate - should return original _id
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', api_secret_hash)
|
|
.send([fixture.duplicate])
|
|
.end(function(err, res) {
|
|
console.log(` Original _id: ${originalId}`);
|
|
console.log(` Duplicate response: ${JSON.stringify(res.body)}`);
|
|
|
|
if (Array.isArray(res.body) && res.body.length > 0 && res.body[0]._id) {
|
|
const returnedId = res.body[0]._id;
|
|
|
|
// CRITICAL: Returned _id should match original for deduplication
|
|
if (returnedId === originalId) {
|
|
console.log(' ✓ Deduplication returns original _id (ideal behavior)');
|
|
} else {
|
|
console.log(` ⚠ Deduplication returned different _id: ${returnedId} vs ${originalId}`);
|
|
}
|
|
}
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|