mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
Harden MongoDB driver compatibility
This commit is contained in:
@@ -26,16 +26,12 @@ describe('Clean MONGO after tests', function ( ) {
|
||||
});
|
||||
});
|
||||
|
||||
it('wipe treatment data', function (done) {
|
||||
self.ctx.treatments().deleteMany({ }, function ( ) {
|
||||
done();
|
||||
});
|
||||
it('wipe treatment data', async function () {
|
||||
await self.ctx.treatments().deleteMany({ });
|
||||
});
|
||||
|
||||
it('wipe entries data', function (done) {
|
||||
self.ctx.entries().deleteMany({ }, function ( ) {
|
||||
done();
|
||||
});
|
||||
it('wipe entries data', async function () {
|
||||
await self.ctx.entries().deleteMany({ });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -55,12 +55,14 @@ describe('v1 API Deduplication Behavior', function() {
|
||||
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);
|
||||
});
|
||||
self.ctx.entries().deleteMany({})
|
||||
.then(function() {
|
||||
// Also clear devicestatus to reduce database load
|
||||
self.ctx.devicestatus.remove({
|
||||
find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } }
|
||||
}, done);
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -62,12 +62,12 @@ describe('Entries REST api', function ( ) {
|
||||
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.archive( ).deleteMany({ }, done);
|
||||
afterEach(async function () {
|
||||
await self.archive( ).deleteMany({ });
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
self.archive( ).deleteMany({ }, done);
|
||||
after(async function () {
|
||||
await self.archive( ).deleteMany({ });
|
||||
});
|
||||
|
||||
// keep this test pinned at or near the top in order to validate all
|
||||
|
||||
@@ -43,12 +43,12 @@ describe('Entry sysTime+type dedup (Baseline)', function() {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
self.archive().deleteMany({}, done);
|
||||
afterEach(async function() {
|
||||
await self.archive().deleteMany({});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
self.archive().deleteMany({}, done);
|
||||
after(async function() {
|
||||
await self.archive().deleteMany({});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -98,14 +98,14 @@ describe('Entry sysTime+type dedup (Baseline)', function() {
|
||||
if (err2) return done(err2);
|
||||
|
||||
// Verify: only 1 entry exists with updated sgv 125
|
||||
self.archive().find({ date: timestamp }).toArray(function(err3, docs) {
|
||||
if (err3) return done(err3);
|
||||
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
docs[0].direction.should.equal('FortyFiveUp');
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
docs[0].direction.should.equal('FortyFiveUp');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -155,14 +155,14 @@ describe('Entry sysTime+type dedup (Baseline)', function() {
|
||||
if (err2) return done(err2);
|
||||
|
||||
// Verify: 2 entries exist (different types)
|
||||
self.archive().find({ date: timestamp }).toArray(function(err3, docs) {
|
||||
if (err3) return done(err3);
|
||||
|
||||
docs.should.have.lengthOf(2);
|
||||
var types = docs.map(d => d.type).sort();
|
||||
types.should.eql(['mbg', 'sgv']);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(2);
|
||||
var types = docs.map(d => d.type).sort();
|
||||
types.should.eql(['mbg', 'sgv']);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -213,12 +213,12 @@ describe('Entry sysTime+type dedup (Baseline)', function() {
|
||||
if (err2) return done(err2);
|
||||
|
||||
// Verify: 2 entries exist
|
||||
self.archive().find({ type: 'sgv', date: { $in: [timestamp1, timestamp2] } }).toArray(function(err3, docs) {
|
||||
if (err3) return done(err3);
|
||||
|
||||
docs.should.have.lengthOf(2);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ type: 'sgv', date: { $in: [timestamp1, timestamp2] } }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(2);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -249,12 +249,12 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
self.archive().deleteMany({}, done);
|
||||
afterEach(async function() {
|
||||
await self.archive().deleteMany({});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
self.archive().deleteMany({}, done);
|
||||
after(async function() {
|
||||
await self.archive().deleteMany({});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -286,14 +286,14 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
if (err) return done(err);
|
||||
|
||||
// Entry should be created (may have ObjectId _id, UUID in identifier)
|
||||
self.archive().find({ date: timestamp }).toArray(function(err2, docs) {
|
||||
if (err2) return done(err2);
|
||||
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(120);
|
||||
// Note: After fix, expect docs[0].identifier === uuid
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(120);
|
||||
// Note: After fix, expect docs[0].identifier === uuid
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -347,13 +347,13 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
if (err2) return done(err2);
|
||||
|
||||
// Verify: single entry, updated value
|
||||
self.archive().find({ date: timestamp }).toArray(function(err3, docs) {
|
||||
if (err3) return done(err3);
|
||||
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -412,13 +412,13 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
if (err2) return done(err2);
|
||||
|
||||
// Verify: single entry (dedup by sysTime+type, not UUID)
|
||||
self.archive().find({ date: timestamp }).toArray(function(err3, docs) {
|
||||
if (err3) return done(err3);
|
||||
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -469,14 +469,14 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
if (err) return done(err);
|
||||
|
||||
// Verify: all 3 entries created
|
||||
self.archive().find({ date: { $in: [timestamp1, timestamp2, timestamp3] } }).toArray(function(err2, docs) {
|
||||
if (err2) return done(err2);
|
||||
|
||||
docs.should.have.lengthOf(3);
|
||||
var sgvValues = docs.map(d => d.sgv).sort();
|
||||
sgvValues.should.eql([120, 125, 130]);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: { $in: [timestamp1, timestamp2, timestamp3] } }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(3);
|
||||
var sgvValues = docs.map(d => d.sgv).sort();
|
||||
sgvValues.should.eql([120, 125, 130]);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -500,9 +500,7 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
dateString: sysTime,
|
||||
sysTime: sysTime,
|
||||
device: 'Trio'
|
||||
}, function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
}).then(function() {
|
||||
// POST via API with same timestamp
|
||||
var entry = {
|
||||
_id: uuid,
|
||||
@@ -513,7 +511,7 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
dateString: sysTime,
|
||||
device: 'Trio'
|
||||
};
|
||||
|
||||
|
||||
request(self.app)
|
||||
.post('/entries/')
|
||||
.set('api-secret', self.known)
|
||||
@@ -521,17 +519,17 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
.expect(200)
|
||||
.end(function(err2, res) {
|
||||
if (err2) return done(err2);
|
||||
|
||||
|
||||
// Verify: single entry, updated value
|
||||
self.archive().find({ date: timestamp }).toArray(function(err3, docs) {
|
||||
if (err3) return done(err3);
|
||||
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].sgv.should.equal(125);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -563,15 +561,15 @@ describe('Entry UUID _id handling (GAP-SYNC-045)', function() {
|
||||
if (err) return done(err);
|
||||
|
||||
// Verify: entry has identifier field with UUID
|
||||
self.archive().find({ date: timestamp }).toArray(function(err2, docs) {
|
||||
if (err2) return done(err2);
|
||||
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].should.have.property('identifier', uuid);
|
||||
// _id should be ObjectId, not UUID
|
||||
docs[0]._id.should.not.equal(uuid);
|
||||
done();
|
||||
});
|
||||
self.archive().find({ date: timestamp }).toArray()
|
||||
.then(function(docs) {
|
||||
docs.should.have.lengthOf(1);
|
||||
docs[0].should.have.property('identifier', uuid);
|
||||
// _id should be ObjectId, not UUID
|
||||
docs[0]._id.should.not.equal(uuid);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should');
|
||||
|
||||
var objectIdValidation = require('../lib/api/shared/objectid-validation');
|
||||
|
||||
describe('API ObjectId validation helper', function () {
|
||||
it('accepts missing ids for create-tolerant routes', function () {
|
||||
should(objectIdValidation.isValidObjectId(undefined)).be.true();
|
||||
should(objectIdValidation.isValidObjectId(null)).be.true();
|
||||
});
|
||||
|
||||
it('accepts 24-character hex ids and rejects non-hex values', function () {
|
||||
should(objectIdValidation.isValidObjectId('507f1f77bcf86cd799439011')).be.true();
|
||||
should(objectIdValidation.isValidObjectId('550e8400-e29b-41d4-a716-446655440000')).be.false();
|
||||
should(objectIdValidation.isValidObjectId(123)).be.false();
|
||||
});
|
||||
|
||||
it('finds the first invalid id in a batch', function () {
|
||||
objectIdValidation.findInvalidId([
|
||||
{ _id: '507f1f77bcf86cd799439011' },
|
||||
{ _id: 'invalid-id' },
|
||||
{ _id: '507f191e810c19729de860ea' }
|
||||
]).should.eql({ index: 1, id: 'invalid-id' });
|
||||
});
|
||||
|
||||
it('returns null when all ids are valid or omitted', function () {
|
||||
should.not.exist(objectIdValidation.findInvalidId([
|
||||
{ _id: '507f1f77bcf86cd799439011' },
|
||||
{},
|
||||
{ _id: null }
|
||||
]));
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,22 @@ describe('Security of REST API V1', function() {
|
||||
|
||||
var known = 'b723e97aa97846eb92d5264f084b2823f57c4aa1';
|
||||
|
||||
function rolesCollection() {
|
||||
return self.ctx.store.collection(self.env.authentication_collections_prefix + 'roles');
|
||||
}
|
||||
|
||||
function subjectsCollection() {
|
||||
return self.ctx.store.collection(self.env.authentication_collections_prefix + 'subjects');
|
||||
}
|
||||
|
||||
async function getBearerToken(accessToken) {
|
||||
const res = await request(self.app)
|
||||
.get('/api/v2/authorization/request/' + accessToken)
|
||||
.expect(200);
|
||||
|
||||
return res.body.token;
|
||||
}
|
||||
|
||||
before(function(done) {
|
||||
var api = require('../lib/api/');
|
||||
delete process.env.API_SECRET;
|
||||
@@ -26,6 +42,7 @@ describe('Security of REST API V1', function() {
|
||||
self.app = require('express')();
|
||||
self.app.enable('api');
|
||||
require('../lib/server/bootevent')(self.env, language).boot(async function booted (ctx) {
|
||||
self.ctx = ctx;
|
||||
self.app.use('/api/v1', api(self.env, ctx));
|
||||
self.app.use('/api/v2/authorization', ctx.authorization.endpoints);
|
||||
let authResult = await authSubject(ctx.authorization.storage);
|
||||
@@ -159,4 +176,140 @@ describe('Security of REST API V1', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Authorization admin save endpoints', function () {
|
||||
beforeEach(async function () {
|
||||
await rolesCollection().deleteMany({ name: /^api-security-role/ });
|
||||
await subjectsCollection().deleteMany({ name: /^api-security-subject/ });
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await rolesCollection().deleteMany({ name: /^api-security-role/ });
|
||||
await subjectsCollection().deleteMany({ name: /^api-security-subject/ });
|
||||
});
|
||||
|
||||
it('PUT /api/v2/authorization/subjects updates an existing subject by _id', async function () {
|
||||
const insertResult = await subjectsCollection().insertOne({
|
||||
name: 'api-security-subject-update',
|
||||
roles: ['readable'],
|
||||
notes: 'original',
|
||||
created_at: '2024-10-26T20:32:49.173Z'
|
||||
});
|
||||
const token = await getBearerToken(self.token.adminAll);
|
||||
|
||||
await request(self.app)
|
||||
.put('/api/v2/authorization/subjects')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.send({
|
||||
_id: insertResult.insertedId.toString(),
|
||||
name: 'api-security-subject-update',
|
||||
roles: ['admin'],
|
||||
notes: 'updated',
|
||||
created_at: '2024-10-26T21:32:49.173Z'
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const docs = await subjectsCollection().find({ name: 'api-security-subject-update' }).toArray();
|
||||
docs.length.should.equal(1);
|
||||
docs[0].roles.should.deepEqual(['admin']);
|
||||
docs[0].notes.should.equal('updated');
|
||||
});
|
||||
|
||||
it('PUT /api/v2/authorization/roles updates an existing role by _id', async function () {
|
||||
const insertResult = await rolesCollection().insertOne({
|
||||
name: 'api-security-role-update',
|
||||
permissions: ['api:entries:read'],
|
||||
notes: 'original',
|
||||
created_at: '2024-10-26T20:32:49.173Z'
|
||||
});
|
||||
const token = await getBearerToken(self.token.adminAll);
|
||||
|
||||
await request(self.app)
|
||||
.put('/api/v2/authorization/roles')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.send({
|
||||
_id: insertResult.insertedId.toString(),
|
||||
name: 'api-security-role-update',
|
||||
permissions: ['api:entries:update'],
|
||||
notes: 'updated',
|
||||
created_at: '2024-10-26T21:32:49.173Z'
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const docs = await rolesCollection().find({ name: 'api-security-role-update' }).toArray();
|
||||
docs.length.should.equal(1);
|
||||
docs[0].permissions.should.deepEqual(['api:entries:update']);
|
||||
docs[0].notes.should.equal('updated');
|
||||
});
|
||||
|
||||
it('PUT /api/v2/authorization/subjects fails when _id is missing', async function () {
|
||||
const token = await getBearerToken(self.token.adminAll);
|
||||
|
||||
await request(self.app)
|
||||
.put('/api/v2/authorization/subjects')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.send({
|
||||
name: 'api-security-subject-missing-id',
|
||||
roles: ['readable'],
|
||||
notes: 'should fail'
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
const docs = await subjectsCollection().find({ name: 'api-security-subject-missing-id' }).toArray();
|
||||
docs.length.should.equal(0);
|
||||
});
|
||||
|
||||
it('PUT /api/v2/authorization/subjects fails when _id is invalid', async function () {
|
||||
const token = await getBearerToken(self.token.adminAll);
|
||||
|
||||
await request(self.app)
|
||||
.put('/api/v2/authorization/subjects')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.send({
|
||||
_id: 'not-a-valid-objectid',
|
||||
name: 'api-security-subject-invalid-id',
|
||||
roles: ['readable'],
|
||||
notes: 'should fail'
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
const docs = await subjectsCollection().find({ name: 'api-security-subject-invalid-id' }).toArray();
|
||||
docs.length.should.equal(0);
|
||||
});
|
||||
|
||||
it('PUT /api/v2/authorization/roles fails when _id is missing', async function () {
|
||||
const token = await getBearerToken(self.token.adminAll);
|
||||
|
||||
await request(self.app)
|
||||
.put('/api/v2/authorization/roles')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.send({
|
||||
name: 'api-security-role-missing-id',
|
||||
permissions: ['api:entries:read'],
|
||||
notes: 'should fail'
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
const docs = await rolesCollection().find({ name: 'api-security-role-missing-id' }).toArray();
|
||||
docs.length.should.equal(0);
|
||||
});
|
||||
|
||||
it('PUT /api/v2/authorization/roles fails when _id is invalid', async function () {
|
||||
const token = await getBearerToken(self.token.adminAll);
|
||||
|
||||
await request(self.app)
|
||||
.put('/api/v2/authorization/roles')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.send({
|
||||
_id: 'not-a-valid-objectid',
|
||||
name: 'api-security-role-invalid-id',
|
||||
permissions: ['api:entries:read'],
|
||||
notes: 'should fail'
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
const docs = await rolesCollection().find({ name: 'api-security-role-invalid-id' }).toArray();
|
||||
docs.length.should.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -352,16 +352,12 @@ describe('API Shape Handling - Single Object vs Array Input', function () {
|
||||
|
||||
describe('Entries API - /api/entries/', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
it('POST accepts single SGV entry object', function (done) {
|
||||
@@ -838,7 +834,95 @@ describe('API Shape Handling - Single Object vs Array Input', function () {
|
||||
p.should.have.property('_id');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Legacy PUT update semantics', function () {
|
||||
beforeEach(async function () {
|
||||
await self.ctx.food().deleteMany({});
|
||||
await self.ctx.activity().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await self.ctx.food().deleteMany({});
|
||||
await self.ctx.activity().deleteMany({});
|
||||
});
|
||||
|
||||
it('PUT /api/food/ updates an existing record by _id when created_at changes', function (done) {
|
||||
self.ctx.food.create({
|
||||
name: 'API shape food',
|
||||
category: 'Test',
|
||||
carbs: 20,
|
||||
protein: 10,
|
||||
fat: 5
|
||||
}, function (createErr, docs) {
|
||||
if (createErr) return done(createErr);
|
||||
|
||||
var created = docs[0];
|
||||
request(self.app)
|
||||
.put('/api/food/')
|
||||
.set('api-secret', known)
|
||||
.send({
|
||||
_id: created._id.toString(),
|
||||
name: 'API shape food',
|
||||
category: 'Test',
|
||||
carbs: 25,
|
||||
protein: 10,
|
||||
fat: 5,
|
||||
created_at: '2024-10-26T21:32:49.173Z'
|
||||
})
|
||||
.expect(200)
|
||||
.end(function (err) {
|
||||
if (err) return done(err);
|
||||
|
||||
self.ctx.food().find({ _id: created._id }).toArray()
|
||||
.then(function (updatedDocs) {
|
||||
updatedDocs.length.should.equal(1);
|
||||
updatedDocs[0].carbs.should.equal(25);
|
||||
updatedDocs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('PUT /api/activity/ updates an existing record by _id', function (done) {
|
||||
self.ctx.activity.create([{
|
||||
created_at: '2024-10-26T20:32:49.173Z',
|
||||
heartrate: 80,
|
||||
steps: 100,
|
||||
activitylevel: 'walking'
|
||||
}], function (createErr, docs) {
|
||||
if (createErr) return done(createErr);
|
||||
|
||||
var created = docs[0];
|
||||
request(self.app)
|
||||
.put('/api/activity/')
|
||||
.set('api-secret', known)
|
||||
.send({
|
||||
_id: created._id.toString(),
|
||||
created_at: '2024-10-26T21:32:49.173Z',
|
||||
heartrate: 95,
|
||||
steps: 250,
|
||||
activitylevel: 'running'
|
||||
})
|
||||
.expect(200)
|
||||
.end(function (err) {
|
||||
if (err) return done(err);
|
||||
|
||||
self.ctx.activity().find({ _id: created._id }).toArray()
|
||||
.then(function (updatedDocs) {
|
||||
updatedDocs.length.should.equal(1);
|
||||
updatedDocs[0].heartrate.should.equal(95);
|
||||
updatedDocs[0].steps.should.equal(250);
|
||||
updatedDocs[0].activitylevel.should.equal('running');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,12 +38,12 @@ describe('authed REST api', function ( ) {
|
||||
this.archive.create(creating, done);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.archive( ).deleteMany({ }, done);
|
||||
afterEach(async function () {
|
||||
await this.archive( ).deleteMany({ });
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
this.archive( ).deleteMany({ }, done);
|
||||
after(async function () {
|
||||
await this.archive( ).deleteMany({ });
|
||||
});
|
||||
|
||||
it('disallow unauthorized POST', function (done) {
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
require('should');
|
||||
|
||||
const find = require('../lib/api3/storage/mongoCollection/find');
|
||||
const { ObjectId } = require('mongodb');
|
||||
|
||||
describe('API3 mongoCollection findMany', function () {
|
||||
function createStubCollection (observed) {
|
||||
describe('API3 mongoCollection find helpers', function () {
|
||||
function createStubCursor (observed, docs) {
|
||||
return {
|
||||
find: function () {
|
||||
return this;
|
||||
},
|
||||
project: function () {
|
||||
return this;
|
||||
},
|
||||
sort: function () {
|
||||
return this;
|
||||
},
|
||||
@@ -21,11 +25,17 @@ describe('API3 mongoCollection findMany', function () {
|
||||
observed.skip = value;
|
||||
return this;
|
||||
},
|
||||
project: function () {
|
||||
return this;
|
||||
},
|
||||
toArray: function (callback) {
|
||||
callback(null, []);
|
||||
toArray: function () {
|
||||
observed.toArrayCalls = (observed.toArrayCalls || 0) + 1;
|
||||
return Promise.resolve(docs || []);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createStubCollection (observed, docs) {
|
||||
return {
|
||||
find: function () {
|
||||
return createStubCursor(observed, docs);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -44,6 +54,7 @@ describe('API3 mongoCollection findMany', function () {
|
||||
|
||||
observed.limit.should.equal(5);
|
||||
observed.skip.should.equal(2);
|
||||
observed.toArrayCalls.should.equal(1);
|
||||
result.should.eql([]);
|
||||
});
|
||||
|
||||
@@ -63,4 +74,30 @@ describe('API3 mongoCollection findMany', function () {
|
||||
observed.limit.should.equal(5);
|
||||
observed.skip.should.equal(2);
|
||||
});
|
||||
|
||||
it('normalizes findOne results when using promise-based cursors', async function () {
|
||||
const observed = {};
|
||||
const docId = new ObjectId();
|
||||
const col = createStubCollection(observed, [{ _id: docId, type: 'sgv' }]);
|
||||
|
||||
const result = await find.findOne(col, docId.toString(), {});
|
||||
|
||||
observed.toArrayCalls.should.equal(1);
|
||||
result.should.have.length(1);
|
||||
result[0].should.have.property('identifier', docId.toString());
|
||||
result[0].should.not.have.property('_id');
|
||||
});
|
||||
|
||||
it('supports promise-based findOneFilter without normalization when requested', async function () {
|
||||
const observed = {};
|
||||
const docId = new ObjectId();
|
||||
const col = createStubCollection(observed, [{ _id: docId, type: 'mbg' }]);
|
||||
|
||||
const result = await find.findOneFilter(col, { type: 'mbg' }, {}, { normalize: false });
|
||||
|
||||
observed.toArrayCalls.should.equal(1);
|
||||
result.should.have.length(1);
|
||||
result[0].should.have.property('_id');
|
||||
result[0]._id.toString().should.equal(docId.toString());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const { ObjectId } = require('mongodb');
|
||||
const modify = require('../lib/api3/storage/mongoCollection/modify');
|
||||
const MongoCollection = require('../lib/api3/storage/mongoCollection');
|
||||
|
||||
describe('API3 mongoCollection promise-based helpers', function () {
|
||||
describe('modify helpers', function () {
|
||||
it('insertOne uses promise-based collection methods and keeps existing behavior', async function () {
|
||||
const doc = { _id: new ObjectId(), type: 'sgv' };
|
||||
const insertedId = doc._id.toString();
|
||||
const col = {
|
||||
insertOne: function (receivedDoc) {
|
||||
arguments.length.should.equal(1);
|
||||
receivedDoc.should.equal(doc);
|
||||
return Promise.resolve({ insertedId: receivedDoc._id });
|
||||
}
|
||||
};
|
||||
|
||||
const identifier = await modify.insertOne(col, doc);
|
||||
|
||||
identifier.should.equal(insertedId);
|
||||
doc.should.not.have.property('_id');
|
||||
});
|
||||
|
||||
it('replaceOne uses promise-based upsert without a callback', async function () {
|
||||
const doc = { value: 42 };
|
||||
const col = {
|
||||
replaceOne: function (filter, receivedDoc, options) {
|
||||
arguments.length.should.equal(3);
|
||||
filter.should.eql({ $or: [{ identifier: 'record-1' }] });
|
||||
receivedDoc.should.equal(doc);
|
||||
options.should.eql({ upsert: true });
|
||||
return Promise.resolve({ matchedCount: 1 });
|
||||
}
|
||||
};
|
||||
|
||||
const matchedCount = await modify.replaceOne(col, 'record-1', doc);
|
||||
|
||||
matchedCount.should.equal(1);
|
||||
});
|
||||
|
||||
it('update and delete helpers use promise-based collection methods', async function () {
|
||||
const col = {
|
||||
updateOne: function (filter, update) {
|
||||
arguments.length.should.equal(2);
|
||||
filter.should.eql({ $or: [{ identifier: 'record-2' }] });
|
||||
update.should.eql({ $set: { value: 84 } });
|
||||
return Promise.resolve({ modifiedCount: 1 });
|
||||
},
|
||||
deleteOne: function (filter) {
|
||||
arguments.length.should.equal(1);
|
||||
filter.should.eql({ $or: [{ identifier: 'record-2' }] });
|
||||
return Promise.resolve({ deletedCount: 1 });
|
||||
},
|
||||
deleteMany: function (filter) {
|
||||
arguments.length.should.equal(1);
|
||||
filter.should.eql({ $or: [{ kind: 'sgv' }, { kind: 'mbg' }] });
|
||||
return Promise.resolve({ deletedCount: 2 });
|
||||
}
|
||||
};
|
||||
|
||||
const updated = await modify.updateOne(col, 'record-2', { value: 84 });
|
||||
const deletedOne = await modify.deleteOne(col, 'record-2');
|
||||
const deletedMany = await modify.deleteManyOr(col, [
|
||||
{ field: 'kind', operator: 'eq', value: 'sgv' },
|
||||
{ field: 'kind', operator: 'eq', value: 'mbg' }
|
||||
]);
|
||||
|
||||
updated.should.eql({ updated: 1 });
|
||||
deletedOne.should.eql({ deleted: 1 });
|
||||
deletedMany.should.eql({ deleted: 2 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('collection metadata helpers', function () {
|
||||
function createCursor (observed, docs) {
|
||||
return {
|
||||
sort: function (spec) {
|
||||
observed.sort = spec;
|
||||
return this;
|
||||
},
|
||||
limit: function (value) {
|
||||
observed.limit = value;
|
||||
return this;
|
||||
},
|
||||
project: function (projection) {
|
||||
observed.projection = projection;
|
||||
return this;
|
||||
},
|
||||
toArray: function () {
|
||||
observed.toArrayCalls = (observed.toArrayCalls || 0) + 1;
|
||||
return Promise.resolve(docs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createStorage (observed, docs) {
|
||||
const col = {
|
||||
find: function () {
|
||||
arguments.length.should.equal(0);
|
||||
observed.findCalls = (observed.findCalls || 0) + 1;
|
||||
return createCursor(observed, docs);
|
||||
}
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
store: {
|
||||
collection: function (name) {
|
||||
observed.collectionName = name;
|
||||
return col;
|
||||
},
|
||||
ensureIndexes: function (receivedCol, fields) {
|
||||
receivedCol.should.equal(col);
|
||||
observed.indexFields = fields;
|
||||
},
|
||||
db: {
|
||||
admin: function () {
|
||||
return {
|
||||
buildInfo: function () {
|
||||
arguments.length.should.equal(0);
|
||||
observed.buildInfoCalls = (observed.buildInfoCalls || 0) + 1;
|
||||
return Promise.resolve({ version: '5.9.2' });
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new MongoCollection(ctx, {}, 'entries');
|
||||
}
|
||||
|
||||
it('version uses promise-based buildInfo', async function () {
|
||||
const observed = {};
|
||||
const storage = createStorage(observed, []);
|
||||
|
||||
const version = await storage.version();
|
||||
|
||||
observed.collectionName.should.equal('entries');
|
||||
observed.buildInfoCalls.should.equal(1);
|
||||
version.should.eql({ storage: 'mongodb', version: '5.9.2' });
|
||||
});
|
||||
|
||||
it('getLastModified uses promise-based cursor methods', async function () {
|
||||
const observed = {};
|
||||
const storage = createStorage(observed, [{ srvModified: 1234 }]);
|
||||
|
||||
const lastModified = await storage.getLastModified('srvModified');
|
||||
|
||||
observed.findCalls.should.equal(1);
|
||||
observed.sort.should.eql({ srvModified: -1 });
|
||||
observed.limit.should.equal(1);
|
||||
observed.projection.should.eql({ srvModified: 1 });
|
||||
observed.toArrayCalls.should.equal(1);
|
||||
lastModified.should.eql({ srvModified: 1234 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@
|
||||
* On mongodb driver 3.x (v15.0.6), Object.keys(new ObjectID()) returned
|
||||
* ['_bsontype','id'], making _.isEmpty return false (correct).
|
||||
*
|
||||
* On mongodb driver 5.x / mongodb-legacy (dev), Object.keys(new ObjectId())
|
||||
* On mongodb driver 5.x, Object.keys(new ObjectId())
|
||||
* returns [], making _.isEmpty return true (incorrect – treats valid
|
||||
* ObjectId _id as empty).
|
||||
*
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
const { ObjectId } = require('mongodb-legacy');
|
||||
const { ObjectId } = require('mongodb');
|
||||
const should = require('should');
|
||||
|
||||
describe('Cache ObjectId compatibility', function () {
|
||||
|
||||
@@ -306,16 +306,12 @@ describe('Concurrent Write Tests - MongoDB 5.x Compatibility', function () {
|
||||
|
||||
describe('Simultaneous POST requests to entries', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
it('handles 5 simultaneous single entry POSTs', function (done) {
|
||||
@@ -422,9 +418,11 @@ describe('Concurrent Write Tests - MongoDB 5.x Compatibility', 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 () {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
self.ctx.entries().deleteMany({})
|
||||
.then(function () {
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -432,9 +430,11 @@ describe('Concurrent Write Tests - MongoDB 5.x Compatibility', function () {
|
||||
afterEach(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 () {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
self.ctx.entries().deleteMany({})
|
||||
.then(function () {
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const dataloaderInit = require('../lib/data/dataloader');
|
||||
const createDData = require('../lib/data/ddata');
|
||||
|
||||
describe('dataloader', function () {
|
||||
it('completes update when db.stats is promise-based', function (done) {
|
||||
const ddata = createDData();
|
||||
ddata.processTreatments = function () {};
|
||||
const ctx = {
|
||||
settings: {},
|
||||
language: {
|
||||
translate: function (value) { return value; }
|
||||
},
|
||||
cache: {
|
||||
isEmpty: function () { return true; },
|
||||
insertData: function (key, results) { return results; }
|
||||
},
|
||||
ddata: ddata,
|
||||
entries: {
|
||||
list: function (query, callback) { callback(null, []); }
|
||||
},
|
||||
treatments: {
|
||||
list: function (query, callback) { callback(null, []); }
|
||||
},
|
||||
profile: {
|
||||
last: function (callback) { callback(null, []); }
|
||||
},
|
||||
food: {
|
||||
list: function (callback) { callback(null, []); }
|
||||
},
|
||||
devicestatus: {
|
||||
list: function (query, callback) { callback(null, []); }
|
||||
},
|
||||
activity: {
|
||||
list: function (query, callback) { callback(null, []); }
|
||||
},
|
||||
store: {
|
||||
db: {
|
||||
stats: function () {
|
||||
return Promise.resolve({ dataSize: 123, indexSize: 456 });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const env = {
|
||||
settings: {
|
||||
isEnabled: function () { return false; },
|
||||
units: 'mg/dl'
|
||||
},
|
||||
extendedSettings: {}
|
||||
};
|
||||
const loader = dataloaderInit(env, ctx);
|
||||
|
||||
loader.update(ddata, function (err) {
|
||||
should.not.exist(err);
|
||||
ddata.dbstats.should.eql({
|
||||
dataSize: 123,
|
||||
indexSize: 456
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+16
-2
@@ -75,7 +75,14 @@ function guardedDeleteMany(collection, filter, callback) {
|
||||
}
|
||||
|
||||
// Safe to proceed
|
||||
return collection.deleteMany(filter, callback);
|
||||
const promise = collection.deleteMany(filter);
|
||||
if (callback) {
|
||||
promise.then(
|
||||
function onSuccess(result) { callback(null, result); },
|
||||
function onError(err) { callback(err); }
|
||||
);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +102,14 @@ function guardedDrop(collection, callback) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return collection.drop(callback);
|
||||
const promise = collection.drop();
|
||||
if (callback) {
|
||||
promise.then(
|
||||
function onSuccess(result) { callback(null, result); },
|
||||
function onError(err) { callback(err); }
|
||||
);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,10 +63,8 @@ describe('Issue #6923: Legacy UUID override edit/delete', function () {
|
||||
return self.ctx.store.collection(self.env.treatments_collection);
|
||||
}
|
||||
|
||||
beforeEach(function (done) {
|
||||
rawCollection().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await rawCollection().deleteMany({});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -75,16 +73,19 @@ describe('Issue #6923: Legacy UUID override edit/delete', function () {
|
||||
*/
|
||||
function insertLegacyDoc (callback) {
|
||||
var doc = Object.assign({}, LEGACY_OVERRIDE);
|
||||
rawCollection().insertOne(doc, function (err) {
|
||||
should.not.exist(err);
|
||||
rawCollection().findOne({ _id: LEGACY_UUID }, function (err, stored) {
|
||||
should.not.exist(err);
|
||||
rawCollection().insertOne(doc)
|
||||
.then(function () {
|
||||
return rawCollection().findOne({ _id: LEGACY_UUID });
|
||||
})
|
||||
.then(function (stored) {
|
||||
should.exist(stored, 'Legacy doc should exist after direct insert');
|
||||
stored._id.should.equal(LEGACY_UUID);
|
||||
should.not.exist(stored.identifier, 'Legacy doc must NOT have identifier field');
|
||||
callback(stored);
|
||||
})
|
||||
.catch(function (err) {
|
||||
should.not.exist(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('DELETE legacy UUID override via API', function () {
|
||||
@@ -130,10 +131,8 @@ describe('Issue #6923: Legacy UUID override edit/delete', function () {
|
||||
|
||||
// Check the database after the server has had time to process the upsert
|
||||
setTimeout(function () {
|
||||
rawCollection().find({ eventType: 'Temporary Override' }).toArray(function (err, docs) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
|
||||
rawCollection().find({ eventType: 'Temporary Override' }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1,
|
||||
'PUT should update the existing legacy override, not create a duplicate. '
|
||||
+ 'Found ' + docs.length + ' documents. '
|
||||
@@ -143,10 +142,8 @@ describe('Issue #6923: Legacy UUID override edit/delete', function () {
|
||||
);
|
||||
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(done);
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should');
|
||||
var mongodb = require('mongodb');
|
||||
|
||||
describe('mongo storage retry lifecycle', function () {
|
||||
var originalMongoClient = mongodb.MongoClient;
|
||||
var originalSetTimeout = global.setTimeout;
|
||||
|
||||
function setMongoClient(fakeMongoClient) {
|
||||
Object.defineProperty(mongodb, 'MongoClient', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: fakeMongoClient,
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(function () {
|
||||
setMongoClient(originalMongoClient);
|
||||
global.setTimeout = originalSetTimeout;
|
||||
delete require.cache[require.resolve('../lib/storage/mongo-storage')];
|
||||
});
|
||||
|
||||
it('closes failed retry clients and calls back only after a successful retry', function (done) {
|
||||
var createdClients = [];
|
||||
var connectAttempts = 0;
|
||||
var retryDelays = [];
|
||||
|
||||
function FakeMongoClient() {
|
||||
this.closed = 0;
|
||||
createdClients.push(this);
|
||||
}
|
||||
|
||||
FakeMongoClient.prototype.on = function () {};
|
||||
FakeMongoClient.prototype.connect = function () {
|
||||
connectAttempts += 1;
|
||||
|
||||
if (connectAttempts === 1) {
|
||||
var err = new Error('server selection failed');
|
||||
err.name = 'MongoServerSelectionError';
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
FakeMongoClient.prototype.db = function () {
|
||||
return {
|
||||
databaseName: 'testdb',
|
||||
command: function () {
|
||||
return Promise.resolve({ authInfo: { authenticatedUserRoles: [] } });
|
||||
},
|
||||
collection: function (name) {
|
||||
return { collectionName: name };
|
||||
}
|
||||
};
|
||||
};
|
||||
FakeMongoClient.prototype.close = function () {
|
||||
this.closed += 1;
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
setMongoClient(FakeMongoClient);
|
||||
global.setTimeout = function (fn, ms) {
|
||||
retryDelays.push(ms);
|
||||
Promise.resolve().then(fn);
|
||||
return 1;
|
||||
};
|
||||
|
||||
delete require.cache[require.resolve('../lib/storage/mongo-storage')];
|
||||
var store = require('../lib/storage/mongo-storage');
|
||||
var callbackCount = 0;
|
||||
|
||||
store({ storageURI: 'mongodb://example/testdb' }, function (err, db) {
|
||||
callbackCount += 1;
|
||||
|
||||
should.not.exist(err);
|
||||
should.exist(db);
|
||||
callbackCount.should.equal(1);
|
||||
connectAttempts.should.equal(2);
|
||||
createdClients.length.should.equal(2);
|
||||
createdClients[0].closed.should.equal(1);
|
||||
createdClients[1].closed.should.equal(0);
|
||||
retryDelays.should.eql([3000]);
|
||||
db.db.databaseName.should.equal('testdb');
|
||||
|
||||
done();
|
||||
}, true);
|
||||
});
|
||||
|
||||
it('closes the client and reports authentication failure once', function (done) {
|
||||
var createdClients = [];
|
||||
var retryDelays = [];
|
||||
|
||||
function FakeMongoClient() {
|
||||
this.closed = 0;
|
||||
createdClients.push(this);
|
||||
}
|
||||
|
||||
FakeMongoClient.prototype.on = function () {};
|
||||
FakeMongoClient.prototype.connect = function () {
|
||||
return Promise.reject(new Error('AuthenticationFailed: bad auth'));
|
||||
};
|
||||
FakeMongoClient.prototype.close = function () {
|
||||
this.closed += 1;
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
setMongoClient(FakeMongoClient);
|
||||
global.setTimeout = function (fn, ms) {
|
||||
retryDelays.push(ms);
|
||||
Promise.resolve().then(fn);
|
||||
return 1;
|
||||
};
|
||||
|
||||
delete require.cache[require.resolve('../lib/storage/mongo-storage')];
|
||||
var store = require('../lib/storage/mongo-storage');
|
||||
var callbackCount = 0;
|
||||
|
||||
store({ storageURI: 'mongodb://example/testdb' }, function (err, db) {
|
||||
callbackCount += 1;
|
||||
|
||||
should.exist(err);
|
||||
should.not.exist(db);
|
||||
err.message.should.equal('MongoDB authentication failed! Double check the URL has the right username and password in MONGODB_URI.');
|
||||
callbackCount.should.equal(1);
|
||||
createdClients.length.should.equal(1);
|
||||
createdClients[0].closed.should.equal(1);
|
||||
retryDelays.should.eql([]);
|
||||
|
||||
done();
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,42 @@ describe('mongo storage', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Uses the default database from the connection string via the public client API', function (done) {
|
||||
var store = require('../lib/storage/mongo-storage');
|
||||
store(env, function (err, db) {
|
||||
should.not.exist(err);
|
||||
should.exist(db.db);
|
||||
should.exist(db.client);
|
||||
|
||||
db.client.db().databaseName.should.equal(db.db.databaseName);
|
||||
db.db.databaseName.should.equal('testdb');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('ensureIndexes uses createIndex without the legacy background option', function (done) {
|
||||
var store = require('../lib/storage/mongo-storage');
|
||||
var calls = [];
|
||||
|
||||
store(env, function (err, db) {
|
||||
should.not.exist(err);
|
||||
|
||||
db.ensureIndexes({
|
||||
collectionName: 'entries',
|
||||
createIndex: function (field, options) {
|
||||
calls.push({ field: field, options: options });
|
||||
return Promise.resolve();
|
||||
}
|
||||
}, ['date']);
|
||||
|
||||
calls.length.should.equal(1);
|
||||
calls[0].field.should.equal('date');
|
||||
should.not.exist(calls[0].options);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('When no connection-string is given the storage-class should throw an error.', function (done) {
|
||||
delete env.storageURI;
|
||||
should.not.exist(env.storageURI);
|
||||
@@ -58,4 +94,3 @@ describe('mongo storage', function () {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ describe('Loop SGV Entry Upload Tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function(done) {
|
||||
self.ctx.entries().deleteMany({}, done);
|
||||
beforeEach(async function() {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
describe('TEST-SGV-001: Single SGV entry', function() {
|
||||
|
||||
@@ -173,16 +173,12 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
|
||||
describe('Entries Storage - lib/server/entries.js', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.ctx.entries().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await self.ctx.entries().deleteMany({});
|
||||
});
|
||||
|
||||
it('create() accepts single entry in array', function (done) {
|
||||
@@ -218,16 +214,12 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
|
||||
describe('Profile Storage - lib/server/profile.js', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.profile().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await self.ctx.profile().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.ctx.profile().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await self.ctx.profile().deleteMany({});
|
||||
});
|
||||
|
||||
it('create() accepts single profile object', function (done) {
|
||||
@@ -347,13 +339,14 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
self.ctx.profile.save(updated, function (saveErr) {
|
||||
should.not.exist(saveErr);
|
||||
|
||||
self.ctx.profile().find({ _id: savedId }).toArray(function (findErr, docs) {
|
||||
should.not.exist(findErr);
|
||||
docs.length.should.equal(1);
|
||||
docs[0].store.Default.dia.should.equal(4);
|
||||
docs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
});
|
||||
self.ctx.profile().find({ _id: savedId }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].store.Default.dia.should.equal(4);
|
||||
docs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -438,28 +431,25 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
should.not.exist(err);
|
||||
saved.created_at.should.equal('2020-01-01T00:00:00.000Z');
|
||||
|
||||
self.ctx.profile().find({ _id: saved._id }).toArray(function (findErr, docs) {
|
||||
should.not.exist(findErr);
|
||||
docs.length.should.equal(1);
|
||||
docs[0].created_at.should.equal('2020-01-01T00:00:00.000Z');
|
||||
done();
|
||||
});
|
||||
self.ctx.profile().find({ _id: saved._id }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].created_at.should.equal('2020-01-01T00:00:00.000Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Food Storage - lib/server/food.js', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.food().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await self.ctx.food().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.ctx.food().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await self.ctx.food().deleteMany({});
|
||||
});
|
||||
|
||||
it('create() accepts single food object', function (done) {
|
||||
@@ -480,20 +470,59 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('save() updates an existing food by _id when created_at changes', function (done) {
|
||||
self.ctx.food.create({
|
||||
name: 'Test Food',
|
||||
category: 'Test',
|
||||
carbs: 20,
|
||||
protein: 10,
|
||||
fat: 5
|
||||
}, function (err, createdDocs) {
|
||||
should.not.exist(err);
|
||||
should.exist(createdDocs);
|
||||
createdDocs.should.be.an.Array();
|
||||
createdDocs.length.should.equal(1);
|
||||
|
||||
var savedId = createdDocs[0]._id;
|
||||
var updated = {
|
||||
_id: savedId.toString(),
|
||||
name: 'Updated Food',
|
||||
category: 'Test',
|
||||
carbs: 25,
|
||||
protein: 10,
|
||||
fat: 5,
|
||||
created_at: '2024-10-26T21:32:49.173Z'
|
||||
};
|
||||
|
||||
self.ctx.food.save(updated, function (saveErr, savedDocs) {
|
||||
should.not.exist(saveErr);
|
||||
should.exist(savedDocs);
|
||||
savedDocs.should.be.an.Array();
|
||||
savedDocs.length.should.equal(1);
|
||||
|
||||
self.ctx.food().find({ _id: savedId }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].name.should.equal('Updated Food');
|
||||
docs[0].carbs.should.equal(25);
|
||||
docs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Activity Storage - lib/server/activity.js', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
self.ctx.activity().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
beforeEach(async function () {
|
||||
await self.ctx.activity().deleteMany({});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
self.ctx.activity().deleteMany({}, function () {
|
||||
done();
|
||||
});
|
||||
afterEach(async function () {
|
||||
await self.ctx.activity().deleteMany({});
|
||||
});
|
||||
|
||||
it('create() accepts array of activity objects (single object not supported)', function (done) {
|
||||
@@ -511,6 +540,124 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('save() updates an existing activity by _id', function (done) {
|
||||
self.ctx.activity.create([{
|
||||
created_at: '2024-10-26T20:32:49.173Z',
|
||||
heartrate: 80,
|
||||
steps: 100,
|
||||
activitylevel: 'walking'
|
||||
}], function (err, createdDocs) {
|
||||
should.not.exist(err);
|
||||
should.exist(createdDocs);
|
||||
createdDocs.length.should.equal(1);
|
||||
should.exist(createdDocs[0]._id);
|
||||
|
||||
var savedId = createdDocs[0]._id;
|
||||
var updated = {
|
||||
_id: savedId.toString(),
|
||||
created_at: '2024-10-26T21:32:49.173Z',
|
||||
heartrate: 95,
|
||||
steps: 250,
|
||||
activitylevel: 'running'
|
||||
};
|
||||
|
||||
self.ctx.activity.save(updated, function (saveErr, savedDoc) {
|
||||
should.not.exist(saveErr);
|
||||
should.exist(savedDoc);
|
||||
savedDoc._id.toString().should.equal(savedId.toString());
|
||||
|
||||
self.ctx.activity().find({ _id: savedId }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].heartrate.should.equal(95);
|
||||
docs[0].steps.should.equal(250);
|
||||
docs[0].activitylevel.should.equal('running');
|
||||
docs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Authorization Storage - lib/authorization/storage.js', function () {
|
||||
function rolesCollection() {
|
||||
return self.ctx.store.collection(self.env.authentication_collections_prefix + 'roles');
|
||||
}
|
||||
|
||||
function subjectsCollection() {
|
||||
return self.ctx.store.collection(self.env.authentication_collections_prefix + 'subjects');
|
||||
}
|
||||
|
||||
beforeEach(async function () {
|
||||
await rolesCollection().deleteMany({ name: 'mongo-save-role' });
|
||||
await subjectsCollection().deleteMany({ name: 'mongo-save-subject' });
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await rolesCollection().deleteMany({ name: 'mongo-save-role' });
|
||||
await subjectsCollection().deleteMany({ name: 'mongo-save-subject' });
|
||||
});
|
||||
|
||||
it('saveRole() updates an existing role without duplicating it', function (done) {
|
||||
rolesCollection().insertOne({
|
||||
name: 'mongo-save-role',
|
||||
permissions: ['api:entries:read'],
|
||||
notes: 'original',
|
||||
created_at: '2024-10-26T20:32:49.173Z'
|
||||
}).then(function (result) {
|
||||
self.ctx.authorization.storage.saveRole({
|
||||
_id: result.insertedId.toString(),
|
||||
name: 'mongo-save-role',
|
||||
permissions: ['api:entries:update'],
|
||||
notes: 'updated',
|
||||
created_at: '2024-10-26T21:32:49.173Z'
|
||||
}, function (saveErr) {
|
||||
should.not.exist(saveErr);
|
||||
|
||||
rolesCollection().find({ name: 'mongo-save-role' }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].permissions.should.deepEqual(['api:entries:update']);
|
||||
docs[0].notes.should.equal('updated');
|
||||
docs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('saveSubject() updates an existing subject without duplicating it', function (done) {
|
||||
subjectsCollection().insertOne({
|
||||
name: 'mongo-save-subject',
|
||||
roles: ['readable'],
|
||||
notes: 'original',
|
||||
created_at: '2024-10-26T20:32:49.173Z'
|
||||
}).then(function (result) {
|
||||
self.ctx.authorization.storage.saveSubject({
|
||||
_id: result.insertedId.toString(),
|
||||
name: 'mongo-save-subject',
|
||||
roles: ['admin'],
|
||||
notes: 'updated',
|
||||
created_at: '2024-10-26T21:32:49.173Z'
|
||||
}, function (saveErr) {
|
||||
should.not.exist(saveErr);
|
||||
|
||||
subjectsCollection().find({ name: 'mongo-save-subject' }).toArray()
|
||||
.then(function (docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].roles.should.deepEqual(['admin']);
|
||||
docs[0].notes.should.equal('updated');
|
||||
docs[0].created_at.should.equal('2024-10-26T21:32:49.173Z');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -533,71 +680,61 @@ describe('MongoDB insertOne vs insertMany Behavior', function () {
|
||||
|
||||
describe('Direct MongoDB operations - testing insertOne with array data', function () {
|
||||
|
||||
it('insertOne with object inserts correctly', function (done) {
|
||||
it('insertOne with object inserts correctly', async function () {
|
||||
var testCollection = self.ctx.store.collection('test_shape_handling');
|
||||
|
||||
testCollection.deleteMany({}, function () {
|
||||
testCollection.insertOne({ type: 'test', value: 42 }, function (err, result) {
|
||||
should.not.exist(err);
|
||||
should.exist(result);
|
||||
result.insertedId.should.be.ok();
|
||||
|
||||
testCollection.find({}).toArray(function (err, docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].value.should.equal(42);
|
||||
testCollection.deleteMany({}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await testCollection.deleteMany({});
|
||||
var result = await testCollection.insertOne({ type: 'test', value: 42 });
|
||||
should.exist(result);
|
||||
result.insertedId.should.be.ok();
|
||||
|
||||
var docs = await testCollection.find({}).toArray();
|
||||
docs.length.should.equal(1);
|
||||
docs[0].value.should.equal(42);
|
||||
await testCollection.deleteMany({});
|
||||
});
|
||||
|
||||
it('insertOne with array creates single document containing array (NOT multiple docs)', function (done) {
|
||||
it('insertOne with array creates single document containing array (NOT multiple docs)', async function () {
|
||||
var testCollection = self.ctx.store.collection('test_shape_handling');
|
||||
|
||||
testCollection.deleteMany({}, function () {
|
||||
var arrayData = [
|
||||
{ type: 'test', value: 1 },
|
||||
{ type: 'test', value: 2 },
|
||||
{ type: 'test', value: 3 }
|
||||
];
|
||||
|
||||
testCollection.insertOne(arrayData, function (err, result) {
|
||||
if (err) {
|
||||
console.log('insertOne with array error:', err.message);
|
||||
done();
|
||||
} else {
|
||||
testCollection.find({}).toArray(function (err, docs) {
|
||||
console.log('Documents after insertOne with array:', JSON.stringify(docs, null, 2));
|
||||
console.log('Number of documents:', docs.length);
|
||||
|
||||
testCollection.deleteMany({}, done);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await testCollection.deleteMany({});
|
||||
var arrayData = [
|
||||
{ type: 'test', value: 1 },
|
||||
{ type: 'test', value: 2 },
|
||||
{ type: 'test', value: 3 }
|
||||
];
|
||||
|
||||
try {
|
||||
await testCollection.insertOne(arrayData);
|
||||
} catch (err) {
|
||||
console.log('insertOne with array error:', err.message);
|
||||
await testCollection.deleteMany({});
|
||||
return;
|
||||
}
|
||||
|
||||
var docs = await testCollection.find({}).toArray();
|
||||
console.log('Documents after insertOne with array:', JSON.stringify(docs, null, 2));
|
||||
console.log('Number of documents:', docs.length);
|
||||
await testCollection.deleteMany({});
|
||||
});
|
||||
|
||||
it('insertMany with array creates multiple documents', function (done) {
|
||||
it('insertMany with array creates multiple documents', async function () {
|
||||
var testCollection = self.ctx.store.collection('test_shape_handling');
|
||||
|
||||
testCollection.deleteMany({}, function () {
|
||||
var arrayData = [
|
||||
{ type: 'test', value: 1 },
|
||||
{ type: 'test', value: 2 },
|
||||
{ type: 'test', value: 3 }
|
||||
];
|
||||
|
||||
testCollection.insertMany(arrayData, function (err, result) {
|
||||
should.not.exist(err);
|
||||
should.exist(result);
|
||||
result.insertedCount.should.equal(3);
|
||||
|
||||
testCollection.find({}).toArray(function (err, docs) {
|
||||
docs.length.should.equal(3);
|
||||
testCollection.deleteMany({}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await testCollection.deleteMany({});
|
||||
var arrayData = [
|
||||
{ type: 'test', value: 1 },
|
||||
{ type: 'test', value: 2 },
|
||||
{ type: 'test', value: 3 }
|
||||
];
|
||||
|
||||
var result = await testCollection.insertMany(arrayData);
|
||||
should.exist(result);
|
||||
result.insertedCount.should.equal(3);
|
||||
|
||||
var docs = await testCollection.find({}).toArray();
|
||||
docs.length.should.equal(3);
|
||||
await testCollection.deleteMany({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
var request = require('supertest');
|
||||
var should = require('should');
|
||||
var ObjectID = require('mongodb').ObjectId;
|
||||
var language = require('../lib/language')();
|
||||
var api = require('../lib/api/');
|
||||
|
||||
@@ -243,7 +244,6 @@ describe('UUID_HANDLING=true', function() {
|
||||
});
|
||||
|
||||
it('UUID-ON-003: ObjectId still works normally', function(done) {
|
||||
var ObjectID = require('mongodb').ObjectId;
|
||||
var testId = new ObjectID();
|
||||
|
||||
// Insert treatment with ObjectId
|
||||
@@ -491,7 +491,6 @@ describe('UUID Edge Cases', function() {
|
||||
});
|
||||
|
||||
it('UUID-EDGE-007: Valid ObjectId still works normally', function(done) {
|
||||
var ObjectID = require('mongodb').ObjectId;
|
||||
var testId = new ObjectID();
|
||||
|
||||
self.ctx.treatments.create([{
|
||||
|
||||
@@ -79,6 +79,10 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
return self.ctx.store.collection(self.env.treatments_collection);
|
||||
}
|
||||
|
||||
function foodCollection() {
|
||||
return self.ctx.food();
|
||||
}
|
||||
|
||||
describe('dbAdd with treatments collection', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
@@ -278,8 +282,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
eventType: 'Note',
|
||||
created_at: createdAt,
|
||||
notes: 'legacy original'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
}).then(function () {
|
||||
|
||||
socket.emit('dbUpdate', {
|
||||
collection: 'treatments',
|
||||
@@ -293,7 +296,9 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
treatmentsCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
@@ -303,7 +308,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
operationName: 'verify websocket dbUpdate with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -328,8 +333,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
eventType: 'Note',
|
||||
created_at: createdAt,
|
||||
notes: 'remove me'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
}).then(function () {
|
||||
|
||||
socket.emit('dbUpdateUnset', {
|
||||
collection: 'treatments',
|
||||
@@ -343,7 +347,9 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
treatmentsCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
@@ -353,7 +359,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
operationName: 'verify websocket dbUpdateUnset with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -408,8 +414,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
eventType: 'Note',
|
||||
created_at: createdAt,
|
||||
notes: 'delete me'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
}).then(function () {
|
||||
|
||||
socket.emit('dbRemove', {
|
||||
collection: 'treatments',
|
||||
@@ -420,7 +425,9 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
treatmentsCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.not.exist(doc);
|
||||
@@ -429,7 +436,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
operationName: 'verify websocket dbRemove with custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -455,8 +462,7 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
eventType: 'Note',
|
||||
created_at: originalCreatedAt,
|
||||
notes: 'existing legacy note'
|
||||
}, function (insertErr) {
|
||||
if (insertErr) return done(insertErr);
|
||||
}).then(function () {
|
||||
|
||||
socket.emit('dbAdd', {
|
||||
collection: 'treatments',
|
||||
@@ -473,7 +479,9 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
treatmentsCollection().findOne({ _id: legacyId }, cb);
|
||||
treatmentsCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
@@ -483,8 +491,132 @@ describe('WebSocket Shape Handling - dbAdd Single vs Array Input', function () {
|
||||
operationName: 'verify websocket dbAdd dedupe with custom string _id'
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generic collection raw write watchpoints', function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
await foodCollection().deleteMany({});
|
||||
});
|
||||
|
||||
it('dbAdd preserves custom string _id values for generic collections', function (done) {
|
||||
connectAndAuthorize(function (err, socket, authResult) {
|
||||
if (err) return done(err);
|
||||
|
||||
authResult.write.should.equal(true);
|
||||
|
||||
var legacyId = 'legacy-food-id-add';
|
||||
socket.emit('dbAdd', {
|
||||
collection: 'food',
|
||||
data: {
|
||||
_id: legacyId,
|
||||
name: 'ws food',
|
||||
carbs: 15
|
||||
}
|
||||
}, 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) {
|
||||
foodCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
doc.name.should.equal('ws food');
|
||||
doc.carbs.should.equal(15);
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbAdd generic collection custom string _id'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('dbUpdate supports custom string _id values for generic collections', function (done) {
|
||||
connectAndAuthorize(function (err, socket, authResult) {
|
||||
if (err) return done(err);
|
||||
|
||||
authResult.write.should.equal(true);
|
||||
|
||||
var legacyId = 'legacy-food-id-update';
|
||||
foodCollection().insertOne({
|
||||
_id: legacyId,
|
||||
name: 'original food',
|
||||
carbs: 10,
|
||||
protein: 2
|
||||
}).then(function () {
|
||||
socket.emit('dbUpdate', {
|
||||
collection: 'food',
|
||||
_id: legacyId,
|
||||
data: {
|
||||
carbs: 18,
|
||||
protein: 4
|
||||
}
|
||||
}, function (updateResult) {
|
||||
should.exist(updateResult);
|
||||
updateResult.result.should.equal('success');
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
foodCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.exist(doc);
|
||||
doc.carbs.should.equal(18);
|
||||
doc.protein.should.equal(4);
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbUpdate generic collection custom string _id'
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('dbRemove supports custom string _id values for generic collections', function (done) {
|
||||
connectAndAuthorize(function (err, socket, authResult) {
|
||||
if (err) return done(err);
|
||||
|
||||
authResult.write.should.equal(true);
|
||||
|
||||
var legacyId = 'legacy-food-id-remove';
|
||||
foodCollection().insertOne({
|
||||
_id: legacyId,
|
||||
name: 'remove food',
|
||||
carbs: 9
|
||||
}).then(function () {
|
||||
socket.emit('dbRemove', {
|
||||
collection: 'food',
|
||||
_id: legacyId
|
||||
}, function (removeResult) {
|
||||
should.exist(removeResult);
|
||||
removeResult.result.should.equal('success');
|
||||
|
||||
waitForConditionWithWarning({
|
||||
condition: function (cb) {
|
||||
foodCollection().findOne({ _id: legacyId })
|
||||
.then(function (doc) { cb(null, doc); })
|
||||
.catch(cb);
|
||||
},
|
||||
assertion: function (doc) {
|
||||
should.not.exist(doc);
|
||||
},
|
||||
done: done,
|
||||
operationName: 'verify websocket dbRemove generic collection custom string _id'
|
||||
});
|
||||
});
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user