mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
Refactor tests/api.shape-handling.test.js to use `before()` for server boot instead of `beforeEach()`, reducing boot overhead and improving test stability. Update docs/test-specs/flaky-tests.md and replit.md to reflect the fix and progress tracker. Replit-Commit-Author: Agent Replit-Commit-Session-Id: dd54303b-52a7-4c5b-9110-6cd8643ee3bb Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: fa6ecd04-4886-43f8-b6ed-5ca9e21eb746 Replit-Helium-Checkpoint-Created: true
519 lines
17 KiB
JavaScript
519 lines
17 KiB
JavaScript
'use strict';
|
|
|
|
var request = require('supertest');
|
|
var should = require('should');
|
|
var language = require('../lib/language')();
|
|
|
|
describe('API Shape Handling - Single Object vs Array Input', function () {
|
|
this.timeout(15000);
|
|
var self = this;
|
|
var known = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
|
|
|
|
var api = require('../lib/api/');
|
|
|
|
// Use before() instead of beforeEach() for server boot - boots once for all tests
|
|
// Data cleanup happens in nested beforeEach() hooks to maintain test isolation
|
|
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'];
|
|
this.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.ddata = require('../lib/data/ddata')();
|
|
self.app.use('/api', api(self.env, ctx));
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('Treatments API - /api/treatments/', function () {
|
|
|
|
beforeEach(function (done) {
|
|
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts single object', function (done) {
|
|
var now = new Date().toISOString();
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send({ eventType: 'Note', created_at: now, notes: 'single object test' })
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.be.greaterThanOrEqual(1);
|
|
res.body[0].notes.should.equal('single object test');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts array with single element', function (done) {
|
|
var now = new Date().toISOString();
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send([{ eventType: 'Note', created_at: now, notes: 'array single element test' }])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.be.greaterThanOrEqual(1);
|
|
res.body[0].notes.should.equal('array single element test');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts array with multiple elements', function (done) {
|
|
var now = new Date().toISOString();
|
|
var later = new Date(Date.now() + 1000).toISOString();
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send([
|
|
{ eventType: 'Note', created_at: now, notes: 'first treatment' },
|
|
{ eventType: 'BG Check', created_at: later, glucose: 120, glucoseType: 'Finger', units: 'mg/dl' }
|
|
])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(2);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST handles large batch array', function (done) {
|
|
var treatments = [];
|
|
var baseTime = Date.now();
|
|
for (var i = 0; i < 10; i++) {
|
|
treatments.push({
|
|
eventType: 'Note',
|
|
created_at: new Date(baseTime + i * 1000).toISOString(),
|
|
notes: 'batch item ' + i
|
|
});
|
|
}
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send(treatments)
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(10);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Devicestatus API - /api/devicestatus/', function () {
|
|
|
|
beforeEach(function (done) {
|
|
self.ctx.devicestatus.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts single object', function (done) {
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send({
|
|
device: 'test-device',
|
|
created_at: new Date().toISOString(),
|
|
uploaderBattery: 85
|
|
})
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(1);
|
|
res.body[0].uploaderBattery.should.equal(85);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts array with single element', function (done) {
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send([{
|
|
device: 'test-device-array',
|
|
created_at: new Date().toISOString(),
|
|
uploaderBattery: 90
|
|
}])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(1);
|
|
res.body[0].uploaderBattery.should.equal(90);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts array with multiple elements', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send([
|
|
{ device: 'device-1', created_at: new Date(now).toISOString(), uploaderBattery: 80 },
|
|
{ device: 'device-2', created_at: new Date(now + 1000).toISOString(), uploaderBattery: 75 },
|
|
{ device: 'device-3', created_at: new Date(now + 2000).toISOString(), uploaderBattery: 70 }
|
|
])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(3);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST handles large batch array', function (done) {
|
|
var statuses = [];
|
|
var baseTime = Date.now();
|
|
for (var i = 0; i < 10; i++) {
|
|
statuses.push({
|
|
device: 'batch-device-' + i,
|
|
created_at: new Date(baseTime + i * 1000).toISOString(),
|
|
uploaderBattery: 50 + i
|
|
});
|
|
}
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send(statuses)
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(10);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Response Shape Consistency', function () {
|
|
|
|
beforeEach(function (done) {
|
|
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
|
self.ctx.devicestatus.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('treatments single object input returns array response', function (done) {
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send({ eventType: 'Note', created_at: new Date().toISOString(), notes: 'response shape test' })
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('treatments array input returns array response', function (done) {
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send([{ eventType: 'Note', created_at: new Date().toISOString(), notes: 'response shape test array' }])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('devicestatus single object input returns array response', function (done) {
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send({ device: 'test', created_at: new Date().toISOString() })
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('devicestatus array input returns array response', function (done) {
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send([{ device: 'test', created_at: new Date().toISOString() }])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases - Empty and Malformed Input', function () {
|
|
|
|
it('treatments POST with empty object', function (done) {
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send({})
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('treatments POST with empty array', function (done) {
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send([])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('devicestatus POST with empty object', function (done) {
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send({})
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('devicestatus POST with empty array', function (done) {
|
|
request(self.app)
|
|
.post('/api/devicestatus/')
|
|
.set('api-secret', known)
|
|
.send([])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(0);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Mixed Array Content Types', function () {
|
|
|
|
beforeEach(function (done) {
|
|
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('treatments array with different eventTypes', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/treatments/')
|
|
.set('api-secret', known)
|
|
.send([
|
|
{ eventType: 'Note', created_at: new Date(now).toISOString(), notes: 'a note' },
|
|
{ eventType: 'BG Check', created_at: new Date(now + 1000).toISOString(), glucose: 100, glucoseType: 'Finger', units: 'mg/dl' },
|
|
{ eventType: 'Meal Bolus', created_at: new Date(now + 2000).toISOString(), carbs: 30, insulin: 2 },
|
|
{ eventType: 'Correction Bolus', created_at: new Date(now + 3000).toISOString(), insulin: 1.5 }
|
|
])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(4);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Entries API - /api/entries/', function () {
|
|
|
|
beforeEach(function (done) {
|
|
self.ctx.entries().deleteMany({}, function () {
|
|
done();
|
|
});
|
|
});
|
|
|
|
afterEach(function (done) {
|
|
self.ctx.entries().deleteMany({}, function () {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts single SGV entry object', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send({ type: 'sgv', sgv: 120, date: now, dateString: new Date(now).toISOString() })
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.be.greaterThanOrEqual(1);
|
|
res.body[0].sgv.should.equal(120);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts array with single SGV entry', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send([{ type: 'sgv', sgv: 115, date: now, dateString: new Date(now).toISOString() }])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.be.greaterThanOrEqual(1);
|
|
res.body[0].sgv.should.equal(115);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts array with multiple SGV entries', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send([
|
|
{ type: 'sgv', sgv: 100, date: now, dateString: new Date(now).toISOString() },
|
|
{ type: 'sgv', sgv: 110, date: now + 300000, dateString: new Date(now + 300000).toISOString() },
|
|
{ type: 'sgv', sgv: 120, date: now + 600000, dateString: new Date(now + 600000).toISOString() }
|
|
])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(3);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts single MBG entry object', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send({ type: 'mbg', mbg: 95, date: now, dateString: new Date(now).toISOString() })
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.be.greaterThanOrEqual(1);
|
|
res.body[0].mbg.should.equal(95);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST accepts mixed entry types in array', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send([
|
|
{ type: 'sgv', sgv: 110, date: now, dateString: new Date(now).toISOString() },
|
|
{ type: 'mbg', mbg: 100, date: now + 60000, dateString: new Date(now + 60000).toISOString() }
|
|
])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(2);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST handles large batch of entries', function (done) {
|
|
var entries = [];
|
|
var baseTime = Date.now();
|
|
for (var i = 0; i < 10; i++) {
|
|
entries.push({
|
|
type: 'sgv',
|
|
sgv: 100 + i,
|
|
date: baseTime + i * 300000,
|
|
dateString: new Date(baseTime + i * 300000).toISOString()
|
|
});
|
|
}
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send(entries)
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(10);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('POST with empty array returns empty array', function (done) {
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send([])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
res.body.length.should.equal(0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('single entry input returns array response', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send({ type: 'sgv', sgv: 105, date: now, dateString: new Date(now).toISOString() })
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('array input returns array response', function (done) {
|
|
var now = Date.now();
|
|
request(self.app)
|
|
.post('/api/entries/')
|
|
.set('api-secret', known)
|
|
.send([{ type: 'sgv', sgv: 108, date: now, dateString: new Date(now).toISOString() }])
|
|
.expect(200)
|
|
.end(function (err, res) {
|
|
if (err) return done(err);
|
|
res.body.should.be.instanceof(Array);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|