From f77d39af3478afdae181ef1ef38ef6d4026e3e1c Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Wed, 13 May 2020 09:56:44 +0200 Subject: [PATCH 1/3] adjusted condition --- api/measurement.yaml | 2 +- src/models/condition.ts | 3 +- src/models/measurement.ts | 4 +-- src/routes/condition.spec.ts | 51 ++++++++++++++++++++++++++++------ src/routes/condition.ts | 12 +++++--- src/routes/measurement.spec.ts | 18 ++++++++++++ src/routes/measurement.ts | 10 +++---- src/test/db.json | 4 +++ 8 files changed, 82 insertions(+), 22 deletions(-) diff --git a/api/measurement.yaml b/api/measurement.yaml index 4386a15..d9fcd40 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -2,7 +2,7 @@ parameters: - $ref: 'api.yaml#/components/parameters/Id' get: - summary: TODO measurement values by id + summary: measurement values by id description: 'Auth: all, levels: read, write, maintain, dev, admin' tags: - /measurement diff --git a/src/models/condition.ts b/src/models/condition.ts index 1e24daf..e0f79da 100644 --- a/src/models/condition.ts +++ b/src/models/condition.ts @@ -6,7 +6,8 @@ const ConditionSchema = new mongoose.Schema({ sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel}, number: String, parameters: mongoose.Schema.Types.Mixed, - treatment_template: {type: mongoose.Schema.Types.ObjectId, ref: TreatmentTemplateModel} + treatment_template: {type: mongoose.Schema.Types.ObjectId, ref: TreatmentTemplateModel}, + status: Number }); export default mongoose.model('condition', ConditionSchema); \ No newline at end of file diff --git a/src/models/measurement.ts b/src/models/measurement.ts index 401103b..ac0ef20 100644 --- a/src/models/measurement.ts +++ b/src/models/measurement.ts @@ -5,8 +5,8 @@ import MeasurementTemplateModel from './measurement_template'; const MeasurementSchema = new mongoose.Schema({ condition_id: {type: mongoose.Schema.Types.ObjectId, ref: ConditionModel}, values: mongoose.Schema.Types.Mixed, - status: Number, - measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel} + measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel}, + status: Number }); export default mongoose.model('measurement', MeasurementSchema); \ No newline at end of file diff --git a/src/routes/condition.spec.ts b/src/routes/condition.spec.ts index ec71ac3..2967108 100644 --- a/src/routes/condition.spec.ts +++ b/src/routes/condition.spec.ts @@ -1,7 +1,7 @@ import should from 'should/as-function'; import ConditionModel from '../models/condition'; import TestHelper from "../test/helper"; -// TODO: status + describe('/condition', () => { let server; @@ -70,8 +70,32 @@ describe('/condition', () => { url: '/condition/700000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - req: {parameters: {material: 'copper', weeks: 3}}, - res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}} + req: {parameters: {material: 'copper', weeks: 3}} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}}); + ConditionModel.findById('700000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); + }); + }); + it('keeps only one unchanged parameter', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {parameters: {material: 'copper'}} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}}); + ConditionModel.findById('700000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); }); }); it('changes the given properties', done => { @@ -86,9 +110,11 @@ describe('/condition', () => { should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}}); ConditionModel.findById('700000000000000000000001').lean().exec((err, data: any) => { if (err) return done(err); + should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v'); should(data.sample_id.toString()).be.eql('400000000000000000000001'); should(data).have.property('number', 'B1'); should(data.treatment_template.toString()).be.eql('200000000000000000000001'); + should(data).have.property('status', 0); should(data).have.property('parameters'); should(data.parameters).have.property('material', 'hot air'); should(data.parameters).have.property('weeks', 10); @@ -205,7 +231,7 @@ describe('/condition', () => { // TODO: rewrite delete methods -> set status for every database collection describe('DELETE /condition/{id}', () => { - it('deletes the condition', done => { + it('sets the status to deleted', done => { TestHelper.request(server, done, { method: 'delete', url: '/condition/700000000000000000000002', @@ -214,14 +240,21 @@ describe('/condition', () => { }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); - ConditionModel.findById('700000000000000000000002').lean().exec((err, data) => { + ConditionModel.findById('700000000000000000000002').lean().exec((err, data: any) => { if (err) return done(err); - should(data).be.null(); + should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v'); + should(data.sample_id.toString()).be.eql('400000000000000000000002'); + should(data).have.property('number', 'B1'); + should(data.treatment_template.toString()).be.eql('200000000000000000000001'); + should(data).have.property('status', -1); + should(data).have.property('parameters'); + should(data.parameters).have.property('material', 'copper'); + should(data.parameters).have.property('weeks', 3); done(); }); }); }); - it('rejects a deleting a condition referenced by measurements'); + it('rejects a deleting a condition referenced by measurements'); // TODO it('rejects an invalid id', done => { TestHelper.request(server, done, { method: 'delete', @@ -315,11 +348,11 @@ describe('/condition', () => { if (err) return done(err); ConditionModel.findById(res.body._id).lean().exec((err, data: any) => { if (err) return done(err); - should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', '__v'); - should(data).have.property('_id'); + should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v'); should(data.sample_id.toString()).be.eql('400000000000000000000002'); should(data).have.property('number', 'B2'); should(data.treatment_template.toString()).be.eql('200000000000000000000001'); + should(data).have.property('status', 0); should(data).have.property('parameters'); should(data.parameters).have.property('material', 'hot air'); should(data.parameters).have.property('weeks', 10); diff --git a/src/routes/condition.ts b/src/routes/condition.ts index a5639e6..8acc2f2 100644 --- a/src/routes/condition.ts +++ b/src/routes/condition.ts @@ -1,5 +1,6 @@ import express from 'express'; import mongoose from 'mongoose'; +import _ from 'lodash'; import ConditionValidate from './validate/condition'; import ParametersValidate from './validate/parameters'; @@ -30,7 +31,6 @@ router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; const {error, value: condition} = ConditionValidate.input(req.body, 'change'); - console.log(error); if (error) return res400(error, res); const data = await ConditionModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; @@ -40,11 +40,15 @@ router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => { if (!data) { res.status(404).json({status: 'Not found'}); } + // add properties needed for sampleIdCheck condition.treatment_template = data.treatment_template; condition.sample_id = data.sample_id; if (!await sampleIdCheck(condition, req, res, next)) return; if (condition.parameters) { - condition.parameters = Object.assign(data.parameters, condition.parameters); + condition.parameters = _.assign({}, data.parameters, condition.parameters); + if (!_.isEqual(condition.parameters, data.parameters)) { + condition.status = 0; + } } if (!await treatmentCheck(condition, 'change', res, next)) return; @@ -63,7 +67,7 @@ router.delete('/condition/' + IdValidate.parameter(), (req, res, next) => { res.status(404).json({status: 'Not found'}); } if (!await sampleIdCheck(data, req, res, next)) return; - await ConditionModel.findByIdAndDelete(req.params.id).lean().exec(async err => { + await ConditionModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { if (err) return next(err); res.json({status: 'OK'}); }); @@ -80,6 +84,7 @@ router.post('/condition/new', async (req, res, next) => { if (!await numberCheck(condition, res, next)) return; if (!await treatmentCheck(condition, 'new', res, next)) return; + condition.status = 0; await new ConditionModel(condition).save((err, data) => { if (err) return next(err); res.json(ConditionValidate.output(data.toObject())); @@ -119,7 +124,6 @@ async function treatmentCheck (condition, param, res, next) { // validate parameters const {error, value: ignore} = ParametersValidate.input(condition.parameters, treatmentData.parameters, param); - console.log(error); if (error) {res400(error, res); return false;} return true; } \ No newline at end of file diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index bba7ca8..7a604d2 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -74,6 +74,24 @@ describe('/measurement', () => { if (err) return done(err); should(res.body).be.eql({_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}); MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); + }); + }); + it('keeps only one unchanged value', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/800000000000000000000002', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {values: {'weight %': 0.5}} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.5, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'}); + MeasurementModel.findById('800000000000000000000002').lean().exec((err, data: any) => { + if (err) return done(err); should(data).have.property('status', 10); done(); }); diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts index 85bea0e..4aa89c1 100644 --- a/src/routes/measurement.ts +++ b/src/routes/measurement.ts @@ -41,12 +41,12 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => { // add properties needed for conditionIdCheck measurement.measurement_template = data.measurement_template; measurement.condition_id = data.condition_id; - if (measurement.hasOwnProperty('values') && !_.isEqual(measurement.values, data.values)) { - measurement.status = 0; - } if (!await conditionIdCheck(measurement, req, res, next)) return; if (measurement.values) { - measurement.values = Object.assign(data.values, measurement.values); + measurement.values = _.assign({}, data.values, measurement.values); + if (!_.isEqual(measurement.values, data.values)) { + measurement.status = 0; + } } if (!await templateCheck(measurement, 'change', res, next)) return; await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).lean().exec((err, data) => { @@ -64,7 +64,7 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => { res.status(404).json({status: 'Not found'}); } if (!await conditionIdCheck(data, req, res, next)) return; - await MeasurementModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(async err => { + await MeasurementModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { if (err) return next(err); res.json({status: 'OK'}); }); diff --git a/src/test/db.json b/src/test/db.json index 01b06b3..621b385 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -193,6 +193,7 @@ "weeks": 3 }, "treatment_template": {"$oid":"200000000000000000000001"}, + "status": 10, "__v": 0 }, { @@ -204,6 +205,7 @@ "weeks": 3 }, "treatment_template": {"$oid":"200000000000000000000001"}, + "status": 10, "__v": 0 }, { @@ -215,6 +217,7 @@ "weeks": 3 }, "treatment_template": {"$oid":"200000000000000000000001"}, + "status": 10, "__v": 0 }, { @@ -226,6 +229,7 @@ "weeks": 5 }, "treatment_template": {"$oid":"200000000000000000000001"}, + "status": 10, "__v": 0 } ], From 478660573dc689373c0d836fe9dc62a380fddf2a Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Wed, 13 May 2020 12:06:28 +0200 Subject: [PATCH 2/3] adjusted sample --- api/condition.yaml | 4 ++ api/measurement.yaml | 2 + api/sample.yaml | 6 +++ src/models/sample.ts | 4 +- src/routes/sample.spec.ts | 82 ++++++++++++++++++++++++++++++--------- src/routes/sample.ts | 19 +++++---- src/test/db.json | 20 ++++++++-- 7 files changed, 103 insertions(+), 34 deletions(-) diff --git a/api/condition.yaml b/api/condition.yaml index 696aa4d..f924707 100644 --- a/api/condition.yaml +++ b/api/condition.yaml @@ -4,6 +4,7 @@ get: summary: condition by id description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /condition responses: @@ -24,6 +25,7 @@ put: summary: change condition description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to reference samples created by another user' + x-doc: status is reset to 0 on any changes tags: - /condition security: @@ -61,6 +63,7 @@ delete: summary: delete condition description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: sets status to -1 tags: - /condition security: @@ -83,6 +86,7 @@ post: summary: add condition description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to reference samples created by another user' + x-doc: 'Adds status: 0 automatically' tags: - /condition security: diff --git a/api/measurement.yaml b/api/measurement.yaml index d9fcd40..2882883 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -4,6 +4,7 @@ get: summary: measurement values by id description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /measurement responses: @@ -57,6 +58,7 @@ delete: summary: delete measurement description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: sets status to -1 tags: - /measurement security: diff --git a/api/sample.yaml b/api/sample.yaml index 4d2817b..9539053 100644 --- a/api/sample.yaml +++ b/api/sample.yaml @@ -2,6 +2,7 @@ get: summary: all samples in overview description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: returns only samples with status 10 # TODO: methods /samples/new|deleted tags: - /sample responses: @@ -23,6 +24,7 @@ get: summary: TODO sample details description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /sample responses: @@ -43,6 +45,7 @@ put: summary: change sample description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to edit samples created by another user' + x-doc: status is reset to 0 on any changes tags: - /sample security: @@ -73,6 +76,7 @@ delete: summary: delete sample description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to edit samples created by another user' + x-doc: sets status to -1, notes and references to this sample are also kept, only note_fields are updated accordingly tags: - /sample security: @@ -95,6 +99,7 @@ post: summary: add sample description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: 'Adds status: 0 automatically' tags: - /sample security: @@ -125,6 +130,7 @@ get: summary: list all existing field names for custom notes fields description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: integrity has to be ensured # TODO: implement mechanism to regularly check note_fields tags: - /sample responses: diff --git a/src/models/sample.ts b/src/models/sample.ts index 81dcc28..9e5353b 100644 --- a/src/models/sample.ts +++ b/src/models/sample.ts @@ -9,10 +9,10 @@ const SampleSchema = new mongoose.Schema({ type: String, color: String, batch: String, - validated: Boolean, material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel}, note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel}, - user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel} + user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel}, + status: Number }); export default mongoose.model('sample', SampleSchema); \ No newline at end of file diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index d74703d..99f6344 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -5,7 +5,6 @@ import NoteFieldModel from '../models/note_field'; import TestHelper from "../test/helper"; // TODO: generate sample number // TODO: think again which parameters are required at POST -// TODO: status describe('/sample', () => { let server; @@ -23,7 +22,7 @@ describe('/sample', () => { }).end((err, res) => { if (err) return done(err); const json = require('../test/db.json'); - should(res.body).have.lengthOf(json.collections.samples.length); + should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length); should(res.body).matchEach(material => { should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id'); should(material).have.property('_id').be.type('string'); @@ -47,7 +46,7 @@ describe('/sample', () => { }).end((err, res) => { if (err) return done(err); const json = require('../test/db.json'); - should(res.body).have.lengthOf(json.collections.samples.length); + should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length); should(res.body).matchEach(material => { should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id'); should(material).have.property('_id').be.type('string'); @@ -88,8 +87,41 @@ describe('/sample', () => { url: '/sample/400000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - req: {number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}}, - res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'} + req: {number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}); + SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { + if (err) return done (err); + should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); + should(data).have.property('_id'); + should(data).have.property('number', '1'); + should(data).have.property('color', 'black'); + should(data).have.property('type', 'granulate'); + should(data).have.property('batch', ''); + should(data.material_id.toString()).be.eql('100000000000000000000004'); + should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', 10); + should(data).have.property('note_id', null); + done(); + }); + }); + }); + it('keeps only one unchanged parameter', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/400000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {type: 'granulate'} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}); + SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { + if (err) return done (err); + should(data).have.property('status', 10); + done(); + }); }); }); it('changes the given properties', done => { @@ -103,15 +135,15 @@ describe('/sample', () => { if (err) return done (err); SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { if (err) return done (err); - should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'validated', 'material_id', 'note_id', 'user_id', '__v'); + should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); should(data).have.property('_id'); should(data).have.property('number', '10'); should(data).have.property('color', 'signalviolet'); should(data).have.property('type', 'part'); should(data).have.property('batch', '114531'); - should(data).have.property('validated').be.type('boolean'); should(data.material_id.toString()).be.eql('100000000000000000000002'); should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', 0); should(data).have.property('note_id'); NoteModel.findById(data.note_id).lean().exec((err, data: any) => { if (err) return done (err); @@ -123,7 +155,7 @@ describe('/sample', () => { should(data.sample_references[0]).have.property('relation', 'part to this sample'); done(); }); - }) + }); }); }); it('adjusts the note_fields correctly', done => { @@ -315,7 +347,7 @@ describe('/sample', () => { }); describe('DELETE /sample/{id}', () => { - it('deletes the sample', done => { + it('sets the status to deleted', done => { TestHelper.request(server, done, { method: 'delete', url: '/sample/400000000000000000000001', @@ -324,14 +356,23 @@ describe('/sample', () => { }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); - SampleModel.findById('400000000000000000000001').lean().exec((err, data) => { + SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { if (err) return done(err); - should(data).be.null(); + should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); + should(data).have.property('_id'); + should(data).have.property('number', '1'); + should(data).have.property('color', 'black'); + should(data).have.property('type', 'granulate'); + should(data).have.property('batch', ''); + should(data.material_id.toString()).be.eql('100000000000000000000004'); + should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', -1); + should(data).have.property('note_id', null); done(); }); }); }); - it('deletes the notes of the sample', done => { + it('keeps the notes of the sample', done => { TestHelper.request(server, done, { method: 'delete', url: '/sample/400000000000000000000002', @@ -342,7 +383,9 @@ describe('/sample', () => { should(res.body).be.eql({status: 'OK'}); NoteModel.findById('500000000000000000000001').lean().exec((err, data) => { if (err) return done(err); - should(data).be.null(); + should(data).have.only.keys('_id', 'comment', 'sample_references', '__v'); + should(data).have.property('comment', 'Stoff gesperrt'); + should(data).have.property('sample_references').with.lengthOf(0); done(); }); }); @@ -367,7 +410,7 @@ describe('/sample', () => { }); }); }); - it('resets references to this sample', done => { + it('keeps references to this sample', done => { TestHelper.request(server, done, { method: 'delete', url: '/sample/400000000000000000000003', @@ -377,10 +420,12 @@ describe('/sample', () => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); setTimeout(() => { // background action takes some time before we can check - NoteModel.findById('500000000000000000000003').lean().exec((err, data) => { + NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => { if (err) return done(err); console.log(data); - should(data).have.property('sample_references').with.lengthOf(0); + should(data).have.property('sample_references').with.lengthOf(1); + should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003'); + should(data.sample_references[0]).have.property('relation', 'part to sample'); done(); }); }, 100); @@ -398,7 +443,7 @@ describe('/sample', () => { should(res.body).be.eql({status: 'OK'}); SampleModel.findById('400000000000000000000001').lean().exec((err, data) => { if (err) return done(err); - should(data).be.null(); + should(data).have.property('status', -1); done(); }); }); @@ -486,7 +531,7 @@ describe('/sample', () => { SampleModel.find({number: 'Rng172'}).lean().exec((err, data: any) => { if (err) return done (err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', '__v'); + should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); should(data[0]).have.property('_id'); should(data[0]).have.property('number', 'Rng172'); should(data[0]).have.property('color', 'black'); @@ -494,6 +539,7 @@ describe('/sample', () => { should(data[0]).have.property('batch', '1560237365'); should(data[0].material_id.toString()).be.eql('100000000000000000000001'); should(data[0].user_id.toString()).be.eql('000000000000000000000002'); + should(data[0]).have.property('status', 0); should(data[0]).have.property('note_id'); NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => { if (err) return done (err); diff --git a/src/routes/sample.ts b/src/routes/sample.ts index abc5747..a6e9ced 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -16,7 +16,7 @@ const router = express.Router(); router.get('/samples', (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; - SampleModel.find({}).lean().exec((err, data) => { + SampleModel.find({status: 10}).lean().exec((err, data) => { if (err) return next(err); res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors }) @@ -66,6 +66,11 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => { delete sample.notes; sample.note_id = data._id; } + + // check for changes + if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) { + sample.status = 0; + } SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => { if (err) return next(err); res.json(SampleValidate.output(data)); @@ -85,22 +90,15 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => { // only maintain and admin are allowed to edit other user's data if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return; - SampleModel.findByIdAndDelete(req.params.id).lean().exec(err => { // delete sample + SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { // set sample status if (err) return next(err); if (sampleData.note_id !== null) { - NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec((err, data: any) => { // delete notes + NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields if (err) return next(err); - console.log(data); if (data.hasOwnProperty('custom_fields')) { // update note_fields customFieldsChange(Object.keys(data.custom_fields), -1); } res.json({status: 'OK'}); - NoteModel.updateMany({'sample_references.id': req.params.id}, {$unset: {'sample_references.$': null}}).lean().exec(err => { // remove sample_references - if (err) console.error(err); - NoteModel.collection.updateMany({sample_references: null}, {$pull: {sample_references: null}}, err => { // only works with native MongoDB driver somehow - if (err) console.error(err); - }); - }); }); } else { @@ -124,6 +122,7 @@ router.post('/sample/new', async (req, res, next) => { customFieldsChange(Object.keys(sample.notes.custom_fields), 1); } + sample.status = 0; new NoteModel(sample.notes).save((err, data) => { if (err) return next(err); delete sample.notes; diff --git a/src/test/db.json b/src/test/db.json index 621b385..a5a347a 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -7,10 +7,10 @@ "type": "granulate", "color": "black", "batch": "", - "validated": true, "material_id": {"$oid":"100000000000000000000004"}, "note_id": null, "user_id": {"$oid":"000000000000000000000002"}, + "status": 10, "__v": 0 }, { @@ -19,10 +19,10 @@ "type": "granulate", "color": "natural", "batch": "1560237365", - "validated": true, "material_id": {"$oid":"100000000000000000000001"}, "note_id": {"$oid":"500000000000000000000001"}, "user_id": {"$oid":"000000000000000000000002"}, + "status": 10, "__v": 0 }, { @@ -31,10 +31,10 @@ "type": "part", "color": "black", "batch": "1704-005", - "validated": false, "material_id": {"$oid":"100000000000000000000005"}, "note_id": {"$oid":"500000000000000000000002"}, "user_id": {"$oid":"000000000000000000000003"}, + "status": 0, "__v": 0 }, { @@ -43,10 +43,22 @@ "type": "granulate", "color": "black", "batch": "1653000308", - "validated": false, "material_id": {"$oid":"100000000000000000000005"}, "note_id": {"$oid":"500000000000000000000003"}, "user_id": {"$oid":"000000000000000000000003"}, + "status": 0, + "__v": 0 + }, + { + "_id": {"$oid":"400000000000000000000005"}, + "number": "33", + "type": "granulate", + "color": "black", + "batch": "1653000308", + "material_id": {"$oid":"100000000000000000000005"}, + "note_id": {"$oid":"500000000000000000000003"}, + "user_id": {"$oid":"000000000000000000000003"}, + "status": -1, "__v": 0 } ], From 806b77eecf2e0f24e65a604b2c6f6d736e5e2903 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Wed, 13 May 2020 14:18:15 +0200 Subject: [PATCH 3/3] adjusted material --- api/api.yaml | 1 + api/material.yaml | 5 +++ src/models/material.ts | 3 +- src/routes/material.spec.ts | 50 +++++++++++++++++++----- src/routes/material.ts | 76 ++++++++++++++++++------------------- src/routes/template.ts | 1 + src/test/db.json | 22 +++++++++++ 7 files changed, 109 insertions(+), 49 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 0c17f4d..f890477 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -34,6 +34,7 @@ info:
  • 0: newly added/changed
  • 10: validated
  • + Bitbucket repository # TODO: Link to new documentation page diff --git a/api/material.yaml b/api/material.yaml index 5e8bc13..6a86b38 100644 --- a/api/material.yaml +++ b/api/material.yaml @@ -2,6 +2,7 @@ get: summary: lists all materials description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: returns only materials with status 10 # TODO: methods /materials/new|deleted tags: - /material responses: @@ -24,6 +25,7 @@ get: summary: get material details description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /material responses: @@ -42,6 +44,7 @@ put: summary: change material description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: status is reset to 0 on any changes tags: - /material security: @@ -72,6 +75,7 @@ delete: summary: delete material description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: sets status to -1 tags: - /material security: @@ -94,6 +98,7 @@ post: summary: add material description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: 'Adds status: 0 automatically' tags: - /material security: diff --git a/src/models/material.ts b/src/models/material.ts index 530f8f0..a5378e0 100644 --- a/src/models/material.ts +++ b/src/models/material.ts @@ -10,7 +10,8 @@ const MaterialSchema = new mongoose.Schema({ numbers: [{ color: String, number: Number - }] + }], + status: Number }); export default mongoose.model('material', MaterialSchema); \ No newline at end of file diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index 59bdd4a..e7767de 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -2,6 +2,8 @@ import should from 'should/as-function'; import MaterialModel from '../models/material'; import TestHelper from "../test/helper"; // TODO: status +// TODO: numbers with color only (no number) +// TODO: deal with numbers with leading zeros describe('/material', () => { let server; @@ -19,7 +21,8 @@ describe('/material', () => { }).end((err, res) => { if (err) return done(err); const json = require('../test/db.json'); - should(res.body).have.lengthOf(json.collections.materials.length); + console.log(res.body); + should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length); should(res.body).matchEach(material => { should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers'); should(material).have.property('_id').be.type('string'); @@ -47,7 +50,7 @@ describe('/material', () => { }).end((err, res) => { if (err) return done(err); const json = require('../test/db.json'); - should(res.body).have.lengthOf(json.collections.materials.length); + should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length); should(res.body).matchEach(material => { should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers'); should(material).have.property('_id').be.type('string'); @@ -136,8 +139,32 @@ describe('/material', () => { url: '/material/100000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]}, - res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]} + req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]}); + MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); + }); + }); + it('keeps only one unchanged property', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/100000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {name: 'Stanyl TW 200 F8'} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]}); + MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); }); }); it('changes the given properties', done => { @@ -155,8 +182,7 @@ describe('/material', () => { if (err) return done(err); data._id = data._id.toString(); data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}}); - should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], __v: 0} - ); + should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], status: 0, __v: 0}); done(); }); }); @@ -268,7 +294,7 @@ describe('/material', () => { }); describe('DELETE /material/{id}', () => { - it('deletes the material', done => { + it('sets the status to deleted', done => { TestHelper.request(server, done, { method: 'delete', url: '/material/100000000000000000000002', @@ -277,9 +303,12 @@ describe('/material', () => { }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); - MaterialModel.findById('100000000000000000000002').lean().exec((err, data) => { + MaterialModel.findById('100000000000000000000002').lean().exec((err, data: any) => { if (err) return done(err); - should(data).be.null(); + data._id = data._id.toString(); + data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}}); + should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], status: -1, __v: 0} + ); done(); }); }); @@ -372,7 +401,7 @@ describe('/material', () => { MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => { if (err) return done (err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', '__v'); + should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v'); should(data[0]).have.property('_id'); should(data[0]).have.property('name', 'Crastin CE 2510'); should(data[0]).have.property('supplier', 'Du Pont'); @@ -380,6 +409,7 @@ describe('/material', () => { should(data[0]).have.property('mineral', '0'); should(data[0]).have.property('glass_fiber', '30'); should(data[0]).have.property('carbon_fiber', '0'); + should(data[0]).have.property('status', 0); should(data[0].numbers).have.lengthOf(0); done(); }); diff --git a/src/routes/material.ts b/src/routes/material.ts index fdb0c47..7601796 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -8,13 +8,14 @@ import IdValidate from './validate/id'; import res400 from './validate/res400'; import mongoose from 'mongoose'; +// TODO: remove f() for await const router = express.Router(); router.get('/materials', (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; - MaterialModel.find({}).lean().exec((err, data) => { + MaterialModel.find({status: 10}).lean().exec((err, data) => { if (err) return next(err); res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors }); @@ -40,33 +41,24 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => { const {error, value: material} = MaterialValidate.input(req.body, 'change'); if (error) return res400(error, res); - if (material.hasOwnProperty('name')) { - MaterialModel.find({name: material.name}).lean().exec((err, data) => { - if (err) return next(err); - if (data.length > 0 && data[0]._id != req.params.id) { - res.status(400).json({status: 'Material name already taken'}); - return; - } - else { - f(); - } - }); - } - else { - f(); - } + MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => { + if (!materialData) { + return res.status(404).json({status: 'Not found'}); + } + if (material.hasOwnProperty('name') && material.name !== materialData.name) { + if (!await nameCheck(material, res, next)) return; + } - function f() { // to resolve async - MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => { + // check for changes + if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) { + material.status = 0; + } + + await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => { if (err) return next(err); - if (data) { - res.json(MaterialValidate.output(data)); - } - else { - res.status(404).json({status: 'Not found'}); - } + res.json(MaterialValidate.output(data)); }); - } + }); }); router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { @@ -78,7 +70,7 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { if (data.length) { return res.status(400).json({status: 'Material still in use'}); } - MaterialModel.findByIdAndDelete(req.params.id).lean().exec((err, data) => { + MaterialModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec((err, data) => { if (err) return next(err); if (data) { res.json({status: 'OK'}); @@ -90,26 +82,34 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { }); }); -router.post('/material/new', (req, res, next) => { +router.post('/material/new', async (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; // validate input const {error, value: material} = MaterialValidate.input(req.body, 'new'); if (error) return res400(error, res); - MaterialModel.find({name: material.name}).lean().exec((err, data) => { - if (err) return next(err); - if (data.length > 0) { - res.status(400).json({status: 'Material name already taken'}); - return; - } + if (!await nameCheck(material, res, next)) return; - new MaterialModel(material).save((err, data) => { - if (err) return next(err); - res.json(MaterialValidate.output(data.toObject())); - }); + material.status = 0; + await new MaterialModel(material).save((err, data) => { + if (err) return next(err); + res.json(MaterialValidate.output(data.toObject())); }); }); -module.exports = router; \ No newline at end of file +module.exports = router; + + +async function nameCheck (material, res, next) { // check if name was already taken + const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => {next(err); return false;}) as any; + if (materialData instanceof Error) { + return false; + } + if (materialData) { // could not find material_id + res.status(400).json({status: 'Material name already taken'}); + return false; + } + return true; +} \ No newline at end of file diff --git a/src/routes/template.ts b/src/routes/template.ts index 2c0277c..5f1477c 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -6,6 +6,7 @@ import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import res400 from './validate/res400'; +// TODO: remove f() for await const router = express.Router(); diff --git a/src/test/db.json b/src/test/db.json index a5a347a..6ac2156 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -128,6 +128,7 @@ "number": 5514263422 } ], + "status": 10, "__v": 0 }, { @@ -148,6 +149,7 @@ "number": 5514612901 } ], + "status": 10, "__v": 0 }, { @@ -160,6 +162,7 @@ "carbon_fiber": 0, "numbers": [ ], + "status": 10, "__v": 0 }, { @@ -176,6 +179,7 @@ "number": 5513933405 } ], + "status": 10, "__v": 0 }, { @@ -192,6 +196,24 @@ "number": 5514262406 } ], + "status": 10, + "__v": 0 + }, + { + "_id": {"$oid":"100000000000000000000006"}, + "name": "PK-HM natural (4773)", + "supplier": "Akro-Plastic", + "group": "PK", + "mineral": 0, + "glass_fiber": 0, + "carbon_fiber": 0, + "numbers": [ + { + "color": "natural", + "number": 10000000 + } + ], + "status": -1, "__v": 0 } ],