mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
fix(profile): use replaceOne upsert to prevent duplicate key error on save (#8455)
Regression from 286fa07 (switch profile api to new mongo driver) caused
E11000 duplicate key errors when saving an existing profile because
insertOne rejects documents whose _id already exists.
Changes:
- Replace insertOne with replaceOne({ _id }, obj, { upsert: true })
- Add try/catch for ObjectID construction to handle invalid/missing _id
- Use hasOwnProperty check for created_at to preserve explicit values
Tests added:
- save() updates existing profile by _id (from PR #8455)
- save() generates _id when none provided
- save() generates _id when invalid _id provided
- save() preserves explicit created_at without overwriting
Cherry-picked from AndyLow91/cgm-remote-monitor@55a70139 with
additional regression tests.
Closes nightscout/cgm-remote-monitor#8455
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
Ben West
co-authored by
Copilot
parent
246e46adb3
commit
15a5fb24d3
@@ -36,11 +36,16 @@ function storage (collection, ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function save (obj, fn) {
|
function save (obj, fn) {
|
||||||
obj._id = new ObjectID(obj._id);
|
try {
|
||||||
if (!obj.created_at) {
|
obj._id = new ObjectID(obj._id);
|
||||||
|
} catch (err) {
|
||||||
|
obj._id = new ObjectID();
|
||||||
|
}
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(obj, 'created_at')) {
|
||||||
obj.created_at = (new Date( )).toISOString( );
|
obj.created_at = (new Date( )).toISOString( );
|
||||||
}
|
}
|
||||||
api().insertOne(obj, function (err) {
|
// Match existing profiles by _id only. The profile editor rewrites created_at on save.
|
||||||
|
api().replaceOne({ _id: obj._id }, obj, { upsert: true }, function (err) {
|
||||||
//id should be added for new docs
|
//id should be added for new docs
|
||||||
fn(err, obj);
|
fn(err, obj);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -299,6 +299,153 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('save() updates an existing profile by _id', function (done) {
|
||||||
|
var original = {
|
||||||
|
defaultProfile: 'Default',
|
||||||
|
store: {
|
||||||
|
Default: {
|
||||||
|
dia: 3,
|
||||||
|
carbratio: [{ time: '00:00', value: 30 }],
|
||||||
|
sens: [{ time: '00:00', value: 100 }],
|
||||||
|
basal: [{ time: '00:00', value: 0.5 }],
|
||||||
|
target_low: [{ time: '00:00', value: 80 }],
|
||||||
|
target_high: [{ time: '00:00', value: 120 }],
|
||||||
|
units: 'mg/dl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDate: '2024-10-19T23:00:00.000Z',
|
||||||
|
created_at: '2024-10-26T20:32:49.173Z',
|
||||||
|
units: 'mg/dl'
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ctx.profile.save(original, function (err, savedProfile) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(savedProfile);
|
||||||
|
should.exist(savedProfile._id);
|
||||||
|
|
||||||
|
var savedId = savedProfile._id;
|
||||||
|
var updated = {
|
||||||
|
_id: savedId.toString(),
|
||||||
|
defaultProfile: 'Default',
|
||||||
|
store: {
|
||||||
|
Default: {
|
||||||
|
dia: 4,
|
||||||
|
carbratio: [{ time: '00:00', value: 30 }],
|
||||||
|
sens: [{ time: '00:00', value: 100 }],
|
||||||
|
basal: [{ time: '00:00', value: 0.5 }],
|
||||||
|
target_low: [{ time: '00:00', value: 80 }],
|
||||||
|
target_high: [{ time: '00:00', value: 120 }],
|
||||||
|
units: 'mg/dl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDate: '2024-10-19T23:00:00.000Z',
|
||||||
|
created_at: '2024-10-26T21:32:49.173Z',
|
||||||
|
units: 'mg/dl'
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('save() generates _id when none provided', function (done) {
|
||||||
|
var profile = {
|
||||||
|
defaultProfile: 'Default',
|
||||||
|
store: {
|
||||||
|
Default: {
|
||||||
|
dia: 3,
|
||||||
|
carbratio: [{ time: '00:00', value: 30 }],
|
||||||
|
sens: [{ time: '00:00', value: 100 }],
|
||||||
|
basal: [{ time: '00:00', value: 0.5 }],
|
||||||
|
target_low: [{ time: '00:00', value: 80 }],
|
||||||
|
target_high: [{ time: '00:00', value: 120 }],
|
||||||
|
units: 'mg/dl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDate: '2024-10-19T23:00:00.000Z',
|
||||||
|
units: 'mg/dl'
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ctx.profile.save(profile, function (err, saved) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(saved);
|
||||||
|
should.exist(saved._id);
|
||||||
|
saved._id.constructor.name.should.equal('ObjectId');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('save() generates _id when invalid _id provided', function (done) {
|
||||||
|
var profile = {
|
||||||
|
_id: 'not-a-valid-objectid',
|
||||||
|
defaultProfile: 'Default',
|
||||||
|
store: {
|
||||||
|
Default: {
|
||||||
|
dia: 3,
|
||||||
|
carbratio: [{ time: '00:00', value: 30 }],
|
||||||
|
sens: [{ time: '00:00', value: 100 }],
|
||||||
|
basal: [{ time: '00:00', value: 0.5 }],
|
||||||
|
target_low: [{ time: '00:00', value: 80 }],
|
||||||
|
target_high: [{ time: '00:00', value: 120 }],
|
||||||
|
units: 'mg/dl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDate: '2024-10-19T23:00:00.000Z',
|
||||||
|
units: 'mg/dl'
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ctx.profile.save(profile, function (err, saved) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(saved);
|
||||||
|
should.exist(saved._id);
|
||||||
|
saved._id.constructor.name.should.equal('ObjectId');
|
||||||
|
// Should not be the invalid string
|
||||||
|
saved._id.toString().should.not.equal('not-a-valid-objectid');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('save() preserves explicit created_at and does not overwrite it', function (done) {
|
||||||
|
var profile = {
|
||||||
|
defaultProfile: 'Default',
|
||||||
|
store: {
|
||||||
|
Default: {
|
||||||
|
dia: 3,
|
||||||
|
carbratio: [{ time: '00:00', value: 30 }],
|
||||||
|
sens: [{ time: '00:00', value: 100 }],
|
||||||
|
basal: [{ time: '00:00', value: 0.5 }],
|
||||||
|
target_low: [{ time: '00:00', value: 80 }],
|
||||||
|
target_high: [{ time: '00:00', value: 120 }],
|
||||||
|
units: 'mg/dl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDate: '2024-10-19T23:00:00.000Z',
|
||||||
|
created_at: '2020-01-01T00:00:00.000Z',
|
||||||
|
units: 'mg/dl'
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ctx.profile.save(profile, function (err, saved) {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Food Storage - lib/server/food.js', function () {
|
describe('Food Storage - lib/server/food.js', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user