From c9be3f4eb7c7f9d1fc5d3cc84915d0e37eeab18f Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Mon, 11 May 2020 13:05:54 +0200 Subject: [PATCH] PUT method for condition --- api/api.yaml | 2 +- api/condition.yaml | 13 ++- api/measurement.yaml | 32 +++++- src/routes/condition.spec.ts | 157 +++++++++++++++++++++++++++++- src/routes/condition.ts | 36 ++++++- src/routes/material.spec.ts | 2 +- src/routes/sample.spec.ts | 2 +- src/routes/template.spec.ts | 2 +- src/routes/validate/condition.ts | 4 +- src/routes/validate/parameters.ts | 20 ++-- src/test/db.json | 11 +++ 11 files changed, 253 insertions(+), 28 deletions(-) diff --git a/api/api.yaml b/api/api.yaml index 44756ae..ed387a3 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -27,7 +27,7 @@ info:
  • no whitespace
  • at least 8 characters
  • - +# TODO: Link to new documentation page servers: diff --git a/api/condition.yaml b/api/condition.yaml index 32f410b..696aa4d 100644 --- a/api/condition.yaml +++ b/api/condition.yaml @@ -22,7 +22,7 @@ 500: $ref: 'api.yaml#/components/responses/500' put: - summary: TODO change condition + summary: change condition description: 'Auth: basic, levels: write, maintain, dev, admin
    Only maintain and admin are allowed to reference samples created by another user' tags: - /condition @@ -33,7 +33,14 @@ content: application/json: schema: - $ref: 'api.yaml#/components/schemas/Condition' + allOf: + - $ref: 'api.yaml#/components/schemas/_Id' + properties: + number: + type: string + example: B1 + parameters: + type: object responses: 200: description: condition details @@ -52,7 +59,7 @@ 500: $ref: 'api.yaml#/components/responses/500' delete: - summary: TODO delete condition + summary: delete condition description: 'Auth: basic, levels: write, maintain, dev, admin' tags: - /condition diff --git a/api/measurement.yaml b/api/measurement.yaml index 0f86047..84e6237 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -22,7 +22,7 @@ 500: $ref: 'api.yaml#/components/responses/500' put: - summary: TODO add/change measurement + summary: TODO change measurement description: 'Auth: basic, levels: write, maintain, dev, admin' tags: - /measurement @@ -69,5 +69,35 @@ $ref: 'api.yaml#/components/responses/403' 404: $ref: 'api.yaml#/components/responses/404' + 500: + $ref: 'api.yaml#/components/responses/500' + +/measurement/new: + post: + summary: TODO add measurement + description: 'Auth: basic, levels: write, maintain, dev, admin' + tags: + - /measurement + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: 'api.yaml#/components/schemas/Measurement' + responses: + 200: + description: measurement details + content: + application/json: + schema: + $ref: 'api.yaml#/components/schemas/Measurement' + 400: + $ref: 'api.yaml#/components/responses/400' + 401: + $ref: 'api.yaml#/components/responses/401' + 403: + $ref: 'api.yaml#/components/responses/403' 500: $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/src/routes/condition.spec.ts b/src/routes/condition.spec.ts index 60e7d78..5884b2e 100644 --- a/src/routes/condition.spec.ts +++ b/src/routes/condition.spec.ts @@ -53,6 +53,157 @@ describe('/condition', () => { }); }); + describe('PUT /condition{id}', () => { + it('returns the right condition', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {}, + res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}} + }); + }); + it('keeps unchanged properties', done => { + TestHelper.request(server, done, { + method: 'put', + 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}} + }); + }); + it('changes the given properties', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {parameters: {material: 'hot air', weeks: 10}} + }).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: 'hot air', weeks: 10}}); + ConditionModel.findById('700000000000000000000001').lean().exec((err, data: any) => { + if (err) return done(err); + 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('parameters'); + should(data.parameters).have.property('material', 'hot air'); + should(data.parameters).have.property('weeks', 10); + done(); + }); + }); + }); + it('allows changing only one parameter', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {parameters: {weeks: 8}}, + res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 8}} + }); + }); + it('rejects not specified parameters', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 400, + req: {parameters: {xx: 13}}, + res: {status: 'Invalid body format', details: '"xx" is not allowed'} + }); + }); + it('rejects a parameter not in the value range', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 400, + req: {parameters: {material: 'xxx'}}, + res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'} + }); + }); + it('rejects a parameter below minimum range', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 400, + req: {parameters: {weeks: -10}}, + res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'} + }); + }); + it('rejects a parameter above maximum range', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 400, + req: {parameters: {weeks: 11}}, + res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'} + }); + }); + it('rejects a new treatment_template', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 400, + req: {treatment_template: '200000000000000000000002'}, + res: {status: 'Invalid body format', details: '"treatment_template" is not allowed'} + }); + }); + it('rejects editing a condition for a write user who did not create this condition', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000003', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {parameters: {weeks: 8}} + }); + }); + it('accepts editing a condition of another user for a maintain/admin user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {parameters: {material: 'hot air', weeks: 10}}, + res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}} + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {key: 'janedoe'}, + httpStatus: 401, + req: {parameters: {material: 'hot air', weeks: 10}} + }); + }); + it('rejects requests form a read user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + auth: {basic: 'user'}, + httpStatus: 403, + req: {parameters: {material: 'hot air', weeks: 10}} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/condition/700000000000000000000001', + httpStatus: 401, + req: {parameters: {material: 'hot air', weeks: 10}} + }); + }); + }); // TODO: how to deal with template changes? Template versioning? + // TODO: rewrite delete methods -> set status for every database collection + describe('DELETE /condition/{id}', () => { it('deletes the condition', done => { TestHelper.request(server, done, { @@ -132,7 +283,7 @@ describe('/condition', () => { }); }); - describe('POST /condition/new', () => { + describe('POST /condition/new', () => { // TODO: sample number generation it('returns the right condition', done => { TestHelper.request(server, done, { method: 'post', @@ -186,7 +337,7 @@ describe('/condition', () => { res: {status: 'Invalid body format', details: '"sample_id" with value "4000000000h0000000000002" fails to match the required pattern: /[0-9a-f]{24}/'} }); }); - it('rejects a missing sample id', done => { + it('rejects a sample id not available', done => { TestHelper.request(server, done, { method: 'post', url: '/condition/new', @@ -206,7 +357,7 @@ describe('/condition', () => { res: {status: 'Invalid body format', details: '"treatment_template" with value "200000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'} }); }); - it('rejects a sample treatment_template which does not exist', done => { + it('rejects a treatment_template which does not exist', done => { TestHelper.request(server, done, { method: 'post', url: '/condition/new', diff --git a/src/routes/condition.ts b/src/routes/condition.ts index f5fa085..687ea2a 100644 --- a/src/routes/condition.ts +++ b/src/routes/condition.ts @@ -26,6 +26,35 @@ router.get('/condition/' + IdValidate.parameter(), (req, res, next) => { }); }); +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; + if (data instanceof Error) { + return; + } + if (!data) { + res.status(404).json({status: 'Not found'}); + } + 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); + } + if (!await treatmentCheck(condition, 'change', res, next)) return; + + console.log(condition); + ConditionModel.findByIdAndUpdate(req.params.id, condition, {new: true}).lean().exec((err, data) => { + if (err) return next(err); + res.json(ConditionValidate.output(data)); + }); +}); + router.delete('/condition/' + IdValidate.parameter(), (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; @@ -50,7 +79,7 @@ router.post('/condition/new', async (req, res, next) => { if (!await sampleIdCheck(condition, req, res, next)) return; if (!await numberCheck(condition, res, next)) return; - if (!await treatmentCheck(condition, res, next)) return; + if (!await treatmentCheck(condition, 'new', res, next)) return; new ConditionModel(condition).save((err, data) => { if (err) return next(err); @@ -82,7 +111,7 @@ async function numberCheck (condition, res, next) { // validate number, returns return true; } -async function treatmentCheck (condition, res, next) { +async function treatmentCheck (condition, param, res, next) { const treatmentData = await TreatmentTemplateModel.findById(condition.treatment_template).lean().exec().catch(err => {next(err); return false;}) as any; if (!treatmentData) { // sample_id not found res.status(400).json({status: 'Treatment template not available'}); @@ -90,7 +119,8 @@ async function treatmentCheck (condition, res, next) { } // validate parameters - const {error, value: ignore} = ParametersValidate.input(condition.parameters, treatmentData.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/material.spec.ts b/src/routes/material.spec.ts index dbc646b..1e7e7ff 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -153,7 +153,7 @@ describe('/material', () => { should(res.body).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}]}); MaterialModel.findById('100000000000000000000001').lean().exec((err, data:any) => { if (err) return done(err); - data._id = data._id.toString({_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}]}); + 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} ); diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index aa01a39..28acff9 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -3,7 +3,7 @@ import SampleModel from '../models/sample'; import NoteModel from '../models/note'; import NoteFieldModel from '../models/note_field'; import TestHelper from "../test/helper"; - +// TODO: generate sample number describe('/sample', () => { let server; diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts index eea3ea4..d3f973a 100644 --- a/src/routes/template.spec.ts +++ b/src/routes/template.spec.ts @@ -3,7 +3,7 @@ import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import TestHelper from "../test/helper"; - +// TODO: remove DELETE methods, only updates possible describe('/template', () => { let server; before(done => TestHelper.before(done)); diff --git a/src/routes/validate/condition.ts b/src/routes/validate/condition.ts index 4c4673f..10d90f5 100644 --- a/src/routes/validate/condition.ts +++ b/src/routes/validate/condition.ts @@ -32,10 +32,8 @@ export default class ConditionValidate { } else if (param === 'change') { return Joi.object({ - sample_id: this.condition.sample_id, number: this.condition.number, - parameters: this.condition.parameters, - treatment_template: this.condition.treatment_template + parameters: this.condition.parameters }).validate(data); } else { diff --git a/src/routes/validate/parameters.ts b/src/routes/validate/parameters.ts index d14c6e2..ab1149b 100644 --- a/src/routes/validate/parameters.ts +++ b/src/routes/validate/parameters.ts @@ -1,35 +1,33 @@ import Joi from '@hapi/joi'; export default class ParametersValidate { - static input (data, parameters) { + static input (data, parameters, param) { // data to validate, parameters from template, param: 'new', 'change' let joiObject = {}; parameters.forEach(parameter => { if (parameter.range.hasOwnProperty('values')) { joiObject[parameter.name] = Joi.alternatives() .try(Joi.string(), Joi.number(), Joi.boolean()) - .valid(...parameter.range.values) - .required(); + .valid(...parameter.range.values); } else if (parameter.range.hasOwnProperty('min') && parameter.range.hasOwnProperty('max')) { joiObject[parameter.name] = Joi.number() .min(parameter.range.min) - .max(parameter.range.max) - .required(); + .max(parameter.range.max); } else if (parameter.range.hasOwnProperty('min')) { joiObject[parameter.name] = Joi.number() - .min(parameter.range.min) - .required(); + .min(parameter.range.min); } else if (parameter.range.hasOwnProperty('max')) { joiObject[parameter.name] = Joi.number() - .max(parameter.range.max) - .required(); + .max(parameter.range.max); } else { joiObject[parameter.name] = Joi.alternatives() - .try(Joi.string(), Joi.number(), Joi.boolean()) - .required(); + .try(Joi.string(), Joi.number(), Joi.boolean()); + } + if (param === 'new') { + joiObject[parameter.name] = joiObject[parameter.name].required() } }); return Joi.object(joiObject).validate(data); diff --git a/src/test/db.json b/src/test/db.json index 2545a71..64079ef 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -216,6 +216,17 @@ }, "treatment_template": {"$oid":"200000000000000000000001"}, "__v": 0 + }, + { + "_id": {"$oid":"700000000000000000000004"}, + "sample_id": {"$oid":"400000000000000000000001"}, + "number": "B3", + "parameters": { + "material": "hot air", + "weeks": 5 + }, + "treatment_template": {"$oid":"200000000000000000000001"}, + "__v": 0 } ], "treatment_templates": [