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) {
|
||||
obj._id = new ObjectID(obj._id);
|
||||
if (!obj.created_at) {
|
||||
try {
|
||||
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( );
|
||||
}
|
||||
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
|
||||
fn(err, obj);
|
||||
});
|
||||
|
||||
@@ -299,6 +299,153 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
|
||||
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 () {
|
||||
|
||||
Reference in New Issue
Block a user