From 649a0b166e8d58d397a7d7c257e882bc7f310138 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Wed, 13 May 2020 17:28:18 +0200 Subject: [PATCH 1/4] adapted api doc --- api/schemas.yaml | 17 +++ api/template.yaml | 142 ++++++------------ package-lock.json | 55 +++++++ package.json | 1 + src/api.ts | 11 +- src/routes/material.spec.ts | 2 +- src/routes/material.ts | 2 +- src/routes/template.spec.ts | 285 +++++++++++++++--------------------- src/routes/template.ts | 28 ++-- src/test/db.json | 7 + 10 files changed, 269 insertions(+), 281 deletions(-) diff --git a/api/schemas.yaml b/api/schemas.yaml index 84722a5..3f5098c 100644 --- a/api/schemas.yaml +++ b/api/schemas.yaml @@ -145,6 +145,11 @@ Template: properties: name: type: string + example: humidity + version: + type: number + readOnly: true + example: 1 parameters: type: array items: @@ -152,8 +157,20 @@ Template: properties: name: type: string + example: kf range: type: object + example: + min: 0 + max: 2 + +TreatmentTemplate: + allOf: + - $ref: 'api.yaml#/components/schemas/Template' + properties: + number_prefix: + type: string + example: B Email: properties: diff --git a/api/template.yaml b/api/template.yaml index 5b362fb..37f374a 100644 --- a/api/template.yaml +++ b/api/template.yaml @@ -14,23 +14,15 @@ schema: type: array items: - $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: heat aging - parameters: - - name: method - range: - values: - - copper - - hot air + $ref: 'api.yaml#/components/schemas/TreatmentTemplate' 401: $ref: 'api.yaml#/components/responses/401' 500: $ref: 'api.yaml#/components/responses/500' -/template/treatment/{name}: + +/template/treatment/{id}: parameters: - - $ref: 'api.yaml#/components/parameters/Name' + - $ref: 'api.yaml#/components/parameters/Id' get: summary: treatment method details description: 'Auth: basic, levels: read, write, maintain, admin' @@ -44,17 +36,7 @@ content: application/json: schema: - allOf: - - $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: heat aging - parameters: - - name: method - range: - values: - - copper - - hot air + $ref: 'api.yaml#/components/schemas/TreatmentTemplate' 401: $ref: 'api.yaml#/components/responses/401' 404: @@ -62,8 +44,9 @@ 500: $ref: 'api.yaml#/components/responses/500' put: - summary: add/change treatment method + summary: change treatment method description: 'Auth: basic, levels: maintain, admin' + x-doc: With a change a new version is set, resulting in a new template with a new id tags: - /template security: @@ -73,33 +56,14 @@ content: application/json: schema: - allOf: - - $ref: 'api.yaml#/components/schemas/Template' - example: - name: heat aging - parameters: - - name: method - range: - values: - - copper - - hot air + $ref: 'api.yaml#/components/schemas/TreatmentTemplate' responses: 200: description: treatment details content: application/json: schema: - allOf: - - $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: heat aging - parameters: - - name: method - range: - values: - - copper - - hot air + $ref: 'api.yaml#/components/schemas/TreatmentTemplate' 400: $ref: 'api.yaml#/components/responses/400' 401: @@ -110,26 +74,37 @@ $ref: 'api.yaml#/components/responses/404' 500: $ref: 'api.yaml#/components/responses/500' - delete: - summary: delete treatment method + +/template/treatment/new: + post: + summary: add treatment method description: 'Auth: basic, levels: maintain, admin' tags: - /template security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: 'api.yaml#/components/schemas/TreatmentTemplate' responses: 200: - $ref: 'api.yaml#/components/responses/Ok' + description: treatment details + content: + application/json: + schema: + $ref: 'api.yaml#/components/schemas/TreatmentTemplate' 400: $ref: 'api.yaml#/components/responses/400' 401: $ref: 'api.yaml#/components/responses/401' 403: $ref: 'api.yaml#/components/responses/403' - 404: - $ref: 'api.yaml#/components/responses/404' 500: $ref: 'api.yaml#/components/responses/500' + /template/measurements: get: summary: all available measurement methods @@ -147,21 +122,13 @@ type: array items: $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: humidity - parameters: - - name: kf - range: - min: 0 - max: 2 401: $ref: 'api.yaml#/components/responses/401' 500: $ref: 'api.yaml#/components/responses/500' -/template/measurement/{name}: +/template/measurement/{id}: parameters: - - $ref: 'api.yaml#/components/parameters/Name' + - $ref: 'api.yaml#/components/parameters/Id' get: summary: measurement method details description: 'Auth: basic, levels: read, write, maintain, admin' @@ -175,16 +142,7 @@ content: application/json: schema: - allOf: - - $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: humidity - parameters: - - name: kf - range: - min: 0 - max: 2 + $ref: 'api.yaml#/components/schemas/Template' 400: $ref: 'api.yaml#/components/responses/400' 401: @@ -194,7 +152,7 @@ 500: $ref: 'api.yaml#/components/responses/500' put: - summary: add/change measurement method + summary: change measurement method description: 'Auth: basic, levels: maintain, admin' tags: - /template @@ -205,32 +163,14 @@ content: application/json: schema: - allOf: - - $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: humidity - parameters: - - name: kf - range: - min: 0 - max: 2 + $ref: 'api.yaml#/components/schemas/Template' responses: 200: description: measurement details content: application/json: schema: - allOf: - - $ref: 'api.yaml#/components/schemas/Template' - example: - _id: 5ea0450ed851c30a90e70894 - name: humidity - parameters: - - name: kf - range: - min: 0 - max: 2 + $ref: 'api.yaml#/components/schemas/Template' 400: $ref: 'api.yaml#/components/responses/400' 401: @@ -241,23 +181,33 @@ $ref: 'api.yaml#/components/responses/404' 500: $ref: 'api.yaml#/components/responses/500' - delete: - summary: delete measurement method + +/template/measurement/new: + post: + summary: add measurement method description: 'Auth: basic, levels: maintain, admin' tags: - /template security: - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: 'api.yaml#/components/schemas/Template' responses: 200: - $ref: 'api.yaml#/components/responses/Ok' + description: measurement details + content: + application/json: + schema: + $ref: 'api.yaml#/components/schemas/Template' 400: $ref: 'api.yaml#/components/responses/400' 401: $ref: 'api.yaml#/components/responses/401' 403: $ref: 'api.yaml#/components/responses/403' - 404: - $ref: 'api.yaml#/components/responses/404' 500: $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4629b37..4c3c77d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,30 @@ "js-yaml": "^3.13.1" } }, + "@apidevtools/openapi-schemas": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.0.3.tgz", + "integrity": "sha512-QoPaxGXfgqgGpK1p21FJ400z56hV681a8DOcZt3J5z0WIHgFeaIZ4+6bX5ATqmOoCpRCsH4ITEwKaOyFMz7wOA==" + }, + "@apidevtools/swagger-methods": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.1.tgz", + "integrity": "sha512-1Vlm18XYW6Yg7uHunroXeunWz5FShPFAdxBbPy8H6niB2Elz9QQsCoYHMbcc11EL1pTxaIr9HXz2An/mHXlX1Q==" + }, + "@apidevtools/swagger-parser": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-9.0.1.tgz", + "integrity": "sha512-Irqybg4dQrcHhZcxJc/UM4vO7Ksoj1Id5e+K94XUOzllqX1n47HEA50EKiXTCQbykxuJ4cYGIivjx/MRSTC5OA==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^8.0.0", + "@apidevtools/openapi-schemas": "^2.0.2", + "@apidevtools/swagger-methods": "^3.0.0", + "@jsdevtools/ono": "^7.1.0", + "call-me-maybe": "^1.0.1", + "openapi-types": "^1.3.5", + "z-schema": "^4.2.2" + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -1353,6 +1377,16 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -1743,6 +1777,11 @@ "wrappy": "1" } }, + "openapi-types": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-1.3.5.tgz", + "integrity": "sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg==" + }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -2505,6 +2544,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "validator": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz", + "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2709,6 +2753,17 @@ "lodash": "^4.17.15", "yargs": "^13.3.0" } + }, + "z-schema": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz", + "integrity": "sha512-zkvK/9TC6p38IwcrbnT3ul9in1UX4cm1y/VZSs4GHKIiDCrlafc+YQBgQBUdDXLAoZHf2qvQ7gJJOo6yT1LH6A==", + "requires": { + "commander": "^2.7.1", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^12.0.0" + } } } } diff --git a/package.json b/package.json index e58c0a0..4ec763a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "license": "ISC", "dependencies": { "@apidevtools/json-schema-ref-parser": "^8.0.0", + "@apidevtools/swagger-parser": "^9.0.1", "@hapi/joi": "^17.1.1", "@types/bcrypt": "^3.0.0", "@types/body-parser": "^1.19.0", diff --git a/src/api.ts b/src/api.ts index 77a60ca..f85393f 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,5 +1,6 @@ import swagger from 'swagger-ui-express'; import jsonRefParser, {JSONSchema} from '@apidevtools/json-schema-ref-parser'; +import oasParser from '@apidevtools/swagger-parser'; // modifies the normal swagger-ui-express package @@ -19,7 +20,15 @@ export default class api { apiDoc = doc; apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e)); // bundle routes apiDoc = this.resolveXDoc(apiDoc); - swagger.setup(apiDoc); + oasParser.validate(apiDoc, (err, api) => { + if (err) { + console.error(err); + } + else { + console.info('API ok, version ' + api.info.version); + swagger.setup(apiDoc); + } + }); }); return swagger.setup(apiDoc, {customCssUrl: '/static/styles/swagger.css'}) } diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index e7767de..0faf04e 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -1,7 +1,7 @@ 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 diff --git a/src/routes/material.ts b/src/routes/material.ts index 7601796..a912f5e 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -8,7 +8,7 @@ import IdValidate from './validate/id'; import res400 from './validate/res400'; import mongoose from 'mongoose'; -// TODO: remove f() for await + const router = express.Router(); diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts index d3f973a..3c05991 100644 --- a/src/routes/template.spec.ts +++ b/src/routes/template.spec.ts @@ -3,7 +3,8 @@ import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import TestHelper from "../test/helper"; -// TODO: remove DELETE methods, only updates possible +// TODO: convert name to id, criteria for new name, criteria for new version, criteria for prefix + describe('/template', () => { let server; before(done => TestHelper.before(done)); @@ -26,6 +27,8 @@ describe('/template', () => { should(treatment).have.only.keys('_id', 'name', 'parameters'); should(treatment).have.property('_id').be.type('string'); should(treatment).have.property('name').be.type('string'); + should(treatment).have.property('version').be.type('number'); + should(treatment).have.property('number_prefix').be.type('string'); should(treatment.parameters).matchEach(number => { should(number).have.only.keys('name', 'range'); should(number).have.property('name').be.type('string'); @@ -52,28 +55,28 @@ describe('/template', () => { }); }); - describe('GET /template/treatment/{name}', () => { + describe('GET /template/treatment/{id}', () => { it('returns the right treatment template', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} }); }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {key: 'janedoe'}, httpStatus: 401 }); }); - it('rejects an unknown name', done => { + it('rejects an unknown id', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/treatment/xxx', + url: '/template/treatment/000000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 404 }); @@ -81,7 +84,7 @@ describe('/template', () => { it('rejects unauthorized requests', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', httpStatus: 401 }); }); @@ -91,38 +94,50 @@ describe('/template', () => { it('returns the right treatment template', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {}, - res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} }); }); it('keeps unchanged properties', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} + }); + }); + it('keeps only one unchanged property', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {name: 'heat treatment'}, + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} }); }); it('changes the given properties', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}); + should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1}}]}); TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => { if (err) return done(err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v'); + should(data[0]).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); should(data[0]).have.property('name', 'heat aging'); + should(data[0]).have.property('version', 2); + should(data[0]).have.property('number_prefix', 'A'); should(data[0]).have.property('parameters').have.lengthOf(1); should(data[0].parameters[0]).have.property('name', 'time'); should(data[0].parameters[0]).have.property('range'); @@ -131,50 +146,122 @@ describe('/template', () => { }); }); }); + it('allows changing only one property'); // TODO: adapt PUT to other PUTs and do POST, everything for measurement too it('supports values ranges', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'time', range: {values: [1, 2, 5]}}]} + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {values: [1, 2, 5]}}]} }); }); it('supports min max ranges', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'time', range: {min: 1, max: 11}}]} + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1, max: 11}}]} }); }); it('supports empty ranges', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20treatment', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {parameters: [{name: 'time', range: {}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', parameters: [{name: 'time', range: {}}]} + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {}}]} }); }); - it('adds a new template for an unknown name', done => { + it('rejects an invalid id', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/treatment/heat%20aging', + url: '/template/treatment/2000000000h0000000000001', + auth: {basic: 'admin'}, + httpStatus: 404, + req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} + }); + }); + it('rejects an unknown id', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/000000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 404, + req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} + }); + }); + it('rejects already existing number prefixes', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {number_prefix: 'B', parameters: [{name: 'time', range: {min: 1}}]}, + res: {status: 'Number prefix already taken'} + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {key: 'admin'}, + httpStatus: 401, + req: {} + }); + }); + it('rejects requests from a write user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + httpStatus: 401, + req: {} + }); + }); + }); + + describe('POST /template/treatment/new', () => { + it('returns the right treatment template', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {}, + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} + }); + }); + it('stores the template', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} - }).end(err => { + }).end((err, res) => { if (err) return done(err); + should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1}}]}); TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => { if (err) return done(err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v'); + should(data[0]).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); should(data[0]).have.property('name', 'heat aging'); + should(data[0]).have.property('version', 2); + should(data[0]).have.property('number_prefix', 'A'); + should(data[0]).have.property('parameters').have.lengthOf(1); should(data[0].parameters[0]).have.property('name', 'time'); should(data[0].parameters[0]).have.property('range'); should(data[0].parameters[0].range).have.property('min', 1); @@ -182,7 +269,7 @@ describe('/template', () => { }); }); }); - it('rejects a missing name for a new name', done => { + it('rejects a missing name', done => { TestHelper.request(server, done, { method: 'put', url: '/template/treatment/heat%20aging', @@ -192,7 +279,7 @@ describe('/template', () => { res: {status: 'Invalid body format', details: '"name" is required'} }); }); - it('rejects missing parameters for a new name', done => { + it('rejects missing parameters', done => { TestHelper.request(server, done, { method: 'put', url: '/template/treatment/heat%20aging', @@ -202,7 +289,7 @@ describe('/template', () => { res: {status: 'Invalid body format', details: '"parameters" is required'} }); }); - it('rejects a missing parameter name for a new name', done => { + it('rejects a missing parameter name', done => { TestHelper.request(server, done, { method: 'put', url: '/template/treatment/heat%20aging', @@ -212,7 +299,7 @@ describe('/template', () => { res: {status: 'Invalid body format', details: '"parameters[0].name" is required'} }); }); - it('rejects a missing parameter range for a new name', done => { + it('rejects a missing parameter range', done => { TestHelper.request(server, done, { method: 'put', url: '/template/treatment/heat%20aging', @@ -222,7 +309,7 @@ describe('/template', () => { res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} }); }); - it('rejects a an invalid parameter range property for a new name', done => { + it('rejects a an invalid parameter range property', done => { TestHelper.request(server, done, { method: 'put', url: '/template/treatment/heat%20aging', @@ -232,16 +319,6 @@ describe('/template', () => { res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'} }); }); - it('rejects already existing names', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20treatment', - auth: {basic: 'admin'}, - httpStatus: 400, - req: {name: 'heat treatment 2', parameters: [{name: 'time', range: {min: 1}}]}, - res: {status: 'Template name already taken'} - }); - }); it('rejects wrong properties', done => { TestHelper.request(server, done, { method: 'put', @@ -252,83 +329,6 @@ describe('/template', () => { res: {status: 'Invalid body format', details: '"name" is required'} }); }); - it('rejects an API key', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20treatment', - auth: {key: 'admin'}, - httpStatus: 401, - req: {} - }); - }); - it('rejects requests from a write user', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20treatment', - auth: {basic: 'janedoe'}, - httpStatus: 403, - req: {} - }); - }); - it('rejects unauthorized requests', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20treatment', - httpStatus: 401, - req: {} - }); - }); - }); - - describe('DELETE /template/treatment/{name}', () => { - it('deletes the template', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/treatment/heat%20treatment', - auth: {basic: 'admin'}, - httpStatus: 200 - }).end((err, res) => { - if (err) return done(err); - should(res.body).be.eql({status: 'OK'}); - TemplateTreatmentModel.find({name: 'heat treatment'}).lean().exec((err, data:any) => { - if (err) return done(err); - should(data).have.lengthOf(0); - done(); - }); - }); - }); - it('rejects deleting a template still in use'); - it('rejects an API key', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/treatment/heat%20treatment', - auth: {key: 'admin'}, - httpStatus: 401 - }); - }); - it('rejects requests from a write user', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/treatment/heat%20treatment', - auth: {basic: 'janedoe'}, - httpStatus: 403 - }) - }); - it('returns 404 for an unknown name', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/treatment/xxx', - auth: {basic: 'admin'}, - httpStatus: 404 - }) - }); - it('rejects unauthorized requests', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/treatment/heat%20treatment', - httpStatus: 401 - }) - }); }); }); @@ -603,56 +603,5 @@ describe('/template', () => { }); }); }); - - describe('DELETE /template/measurement/{name}', () => { - it('deletes the template', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/measurement/spectrum', - auth: {basic: 'admin'}, - httpStatus: 200 - }).end((err, res) => { - if (err) return done(err); - should(res.body).be.eql({status: 'OK'}); - TemplateMeasurementModel.find({name: 'spectrum'}).lean().exec((err, data:any) => { - if (err) return done(err); - should(data).have.lengthOf(0); - done(); - }); - }); - }); - it('rejects deleting a template still in use'); - it('rejects an API key', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/measurement/spectrum', - auth: {key: 'admin'}, - httpStatus: 401 - }); - }); - it('rejects requests from a write user', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/measurement/spectrum', - auth: {basic: 'janedoe'}, - httpStatus: 403 - }) - }); - it('returns 404 for an unknown name', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/measurement/xxx', - auth: {basic: 'admin'}, - httpStatus: 404 - }) - }); - it('rejects unauthorized requests', done => { - TestHelper.request(server, done, { - method: 'delete', - url: '/template/measurement/spectrum', - httpStatus: 401 - }) - }); - }); }); }); \ No newline at end of file diff --git a/src/routes/template.ts b/src/routes/template.ts index 5f1477c..55088f9 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -71,20 +71,20 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next }); }); -router.delete('/template/:collection(measurement|treatment)/:name', (req, res, next) => { - if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; - - (req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel) - .findOneAndDelete({name: req.params.name}).lean().exec((err, data) => { - if (err) return next(err); - if (data) { - res.json({status: 'OK'}) - } - else { - res.status(404).json({status: 'Not found'}); - } - }); -}); +// router.delete('/template/:collection(measurement|treatment)/:name', (req, res, next) => { +// if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; +// +// (req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel) +// .findOneAndDelete({name: req.params.name}).lean().exec((err, data) => { +// if (err) return next(err); +// if (data) { +// res.json({status: 'OK'}) +// } +// else { +// res.status(404).json({status: 'Not found'}); +// } +// }); +// }); module.exports = router; \ No newline at end of file diff --git a/src/test/db.json b/src/test/db.json index 6ac2156..619fb75 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -308,6 +308,8 @@ { "_id": {"$oid":"200000000000000000000001"}, "name": "heat treatment", + "version": 1, + "number_prefix": "A", "parameters": [ { "name": "material", @@ -331,6 +333,8 @@ { "_id": {"$oid":"200000000000000000000002"}, "name": "heat treatment 2", + "version": 2, + "number_prefix": "B", "parameters": [ { "name": "material", @@ -344,6 +348,7 @@ { "_id": {"$oid":"300000000000000000000001"}, "name": "spectrum", + "version": 1, "parameters": [ { "name": "dpt", @@ -357,6 +362,7 @@ { "_id": {"$oid":"300000000000000000000002"}, "name": "kf", + "version": 2, "parameters": [ { "name": "weight %", @@ -378,6 +384,7 @@ { "_id": {"$oid":"300000000000000000000003"}, "name": "mt 3", + "version": 1, "parameters": [ { "name": "val1", From 81a7663f6c1fb5de3bcd76f27d23045e21beaf6b Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 14 May 2020 12:31:57 +0200 Subject: [PATCH 2/4] GET finished --- src/api.ts | 2 +- src/models/measurement_template.ts | 1 + src/models/treatment_template.ts | 2 + src/routes/template.spec.ts | 502 ++++++++++++++++++++--------- src/routes/template.ts | 34 +- src/routes/validate/template.ts | 69 +++- 6 files changed, 432 insertions(+), 178 deletions(-) diff --git a/src/api.ts b/src/api.ts index f85393f..228f166 100644 --- a/src/api.ts +++ b/src/api.ts @@ -25,7 +25,7 @@ export default class api { console.error(err); } else { - console.info('API ok, version ' + api.info.version); + console.info(process.env.NODE_ENV === 'test' ? '' : 'API ok, version ' + api.info.version); swagger.setup(apiDoc); } }); diff --git a/src/models/measurement_template.ts b/src/models/measurement_template.ts index c55cbc7..1b0f6e9 100644 --- a/src/models/measurement_template.ts +++ b/src/models/measurement_template.ts @@ -2,6 +2,7 @@ import mongoose from 'mongoose'; const MeasurementTemplateSchema = new mongoose.Schema({ name: {type: String, index: {unique: true}}, + version: Number, parameters: [{ name: String, range: mongoose.Schema.Types.Mixed diff --git a/src/models/treatment_template.ts b/src/models/treatment_template.ts index 3b61164..8fc4af8 100644 --- a/src/models/treatment_template.ts +++ b/src/models/treatment_template.ts @@ -2,6 +2,8 @@ import mongoose from 'mongoose'; const TreatmentTemplateSchema = new mongoose.Schema({ name: {type: String, index: {unique: true}}, + version: Number, + number_prefix: String, parameters: [{ name: String, range: mongoose.Schema.Types.Mixed diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts index 3c05991..e89e238 100644 --- a/src/routes/template.spec.ts +++ b/src/routes/template.spec.ts @@ -24,7 +24,7 @@ describe('/template', () => { const json = require('../test/db.json'); should(res.body).have.lengthOf(json.collections.treatment_templates.length); should(res.body).matchEach(treatment => { - should(treatment).have.only.keys('_id', 'name', 'parameters'); + should(treatment).have.only.keys('_id', 'name', 'version', 'parameters', 'number_prefix'); should(treatment).have.property('_id').be.type('string'); should(treatment).have.property('name').be.type('string'); should(treatment).have.property('version').be.type('number'); @@ -131,22 +131,44 @@ describe('/template', () => { }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1}}]}); - TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => { + TemplateTreatmentModel.findById('200000000000000000000001').lean().exec((err, data:any) => { if (err) return done(err); - should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); - should(data[0]).have.property('name', 'heat aging'); - should(data[0]).have.property('version', 2); - should(data[0]).have.property('number_prefix', 'A'); - should(data[0]).have.property('parameters').have.lengthOf(1); - should(data[0].parameters[0]).have.property('name', 'time'); - should(data[0].parameters[0]).have.property('range'); - should(data[0].parameters[0].range).have.property('min', 1); + should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); + should(data).have.property('name', 'heat aging'); + should(data).have.property('version', 2); + should(data).have.property('number_prefix', 'A'); + should(data).have.property('parameters').have.lengthOf(1); + should(data.parameters[0]).have.property('name', 'time'); + should(data.parameters[0]).have.property('range'); + should(data.parameters[0].range).have.property('min', 1); done(); }); }); }); - it('allows changing only one property'); // TODO: adapt PUT to other PUTs and do POST, everything for measurement too + it('allows changing only one property', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {name: 'heat aging'} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}); + TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => { + if (err) return done(err); + should(data).have.lengthOf(1); + should(data[0]).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); + should(data[0]).have.property('name', 'heat aging'); + should(data[0]).have.property('version', 2); + should(data[0]).have.property('number_prefix', 'A'); + should(data[0]).have.property('parameters').have.lengthOf(1); + should(data[0].parameters[0]).have.property('name', 'material'); + should(data[0].parameters[1]).have.property('name', 'weeks'); + done(); + }); + }); + }); it('supports values ranges', done => { TestHelper.request(server, done, { method: 'put', @@ -167,6 +189,16 @@ describe('/template', () => { res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1, max: 11}}]} }); }); + it('supports array type ranges', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {parameters: [{name: 'time', range: {type: 'array'}}]}, + res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]} + }); + }); it('supports empty ranges', done => { TestHelper.request(server, done, { method: 'put', @@ -177,6 +209,16 @@ describe('/template', () => { res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {}}]} }); }); + it('rejects not specified parameters', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/treatment/200000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]}, + res: {} + }); + }) it('rejects an invalid id', done => { TestHelper.request(server, done, { method: 'put', @@ -236,63 +278,81 @@ describe('/template', () => { describe('POST /template/treatment/new', () => { it('returns the right treatment template', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/200000000000000000000001', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 200, - req: {}, - res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]} + req: {name: 'heat treatment3', number_prefix: 'C', parameters: [{name: 'material', range: {values: ['copper']}}]} + }).end((err, res) => { + if (err) return done(err); + should(res.body).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters'); + should(res.body).have.property('name', 'heat treatment3'); + should(res.body).have.property('version', 1); + should(res.body).have.property('number_prefix', 'C'); + should(res.body).have.property('parameters').have.lengthOf(1); + should(res.body.parameters[0]).have.property('name', 'material'); + should(res.body.parameters[0]).have.property('range'); + should(res.body.parameters[0].range).have.property('values'); + should(res.body.parameters[0].range.values[0]).be.eql('copper'); }); }); it('stores the template', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/200000000000000000000001', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 200, - req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]} }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1}}]}); - TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => { + TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => { if (err) return done(err); - should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); - should(data[0]).have.property('name', 'heat aging'); - should(data[0]).have.property('version', 2); - should(data[0]).have.property('number_prefix', 'A'); - should(data[0]).have.property('parameters').have.lengthOf(1); - should(data[0].parameters[0]).have.property('name', 'time'); - should(data[0].parameters[0]).have.property('range'); - should(data[0].parameters[0].range).have.property('min', 1); + should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); + should(data).have.property('name', 'heat aging'); + should(data).have.property('version', 1); + should(data).have.property('number_prefix', 'C'); + should(data).have.property('parameters').have.lengthOf(1); + should(data.parameters[0]).have.property('name', 'time'); + should(data.parameters[0]).have.property('range'); + should(data.parameters[0].range).have.property('min', 1); done(); }); }); }); it('rejects a missing name', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20aging', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {parameters: [{name: 'time', range: {min: 1}}]}, + req: {number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}, res: {status: 'Invalid body format', details: '"name" is required'} }); }); + it('rejects a missing number prefix', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/treatment/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}, + res: {status: 'Invalid body format', details: '"number_prefix" is required'} + }); + }); it('rejects missing parameters', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20aging', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {name: 'heat aging'}, + req: {name: 'heat aging', number_prefix: 'C'}, res: {status: 'Invalid body format', details: '"parameters" is required'} }); }); it('rejects a missing parameter name', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20aging', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, req: {name: 'heat aging', parameters: [{range: {min: 1}}]}, @@ -301,18 +361,18 @@ describe('/template', () => { }); it('rejects a missing parameter range', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20aging', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, req: {name: 'heat aging', parameters: [{name: 'time'}]}, res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} }); }); - it('rejects a an invalid parameter range property', done => { + it('rejects an invalid parameter range property', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20aging', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, req: {name: 'heat aging', parameters: [{name: 'time', range: {xx: 1}}]}, @@ -321,12 +381,48 @@ describe('/template', () => { }); it('rejects wrong properties', done => { TestHelper.request(server, done, { - method: 'put', - url: '/template/treatment/heat%20aging', + method: 'post', + url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {parameters: [{name: 'time'}], xx: 33}, - res: {status: 'Invalid body format', details: '"name" is required'} + req: {name: 'heat aging', parameters: [{name: 'time', range: {}}], xx: 33}, + res: {} + }); + }); + it('rejects already existing number prefixes', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/treatment/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'heat aging', number_prefix: 'B', parameters: [{name: 'time', range: {min: 1}}]}, + res: {status: 'Number prefix already taken'} + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/treatment/new', + auth: {key: 'admin'}, + httpStatus: 401, + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]} + }); + }); + it('rejects requests from a write user', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/treatment/new', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/treatment/new', + httpStatus: 401, + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]} }); }); }); @@ -345,9 +441,10 @@ describe('/template', () => { const json = require('../test/db.json'); should(res.body).have.lengthOf(json.collections.measurement_templates.length); should(res.body).matchEach(measurement => { - should(measurement).have.only.keys('_id', 'name', 'parameters'); + should(measurement).have.only.keys('_id', 'name', 'version', 'parameters'); should(measurement).have.property('_id').be.type('string'); should(measurement).have.property('name').be.type('string'); + should(measurement).have.property('version').be.type('number'); should(measurement.parameters).matchEach(number => { should(number).have.only.keys('name', 'range'); should(number).have.property('name').be.type('string'); @@ -374,28 +471,28 @@ describe('/template', () => { }); }); - describe('GET /template/measurement/{name}', () => { + describe('GET /template/measurement/id', () => { it('returns the right measurement template', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {}}]} }); }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {key: 'janedoe'}, httpStatus: 401 }); }); - it('rejects an unknown name', done => { + it('rejects an unknown id', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/measurement/xxx', + url: '/template/measurement/000000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 404 }); @@ -403,7 +500,7 @@ describe('/template', () => { it('rejects unauthorized requests', done => { TestHelper.request(server, done, { method: 'get', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', httpStatus: 401 }); }); @@ -413,38 +510,49 @@ describe('/template', () => { it('returns the right measurement template', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {}, - res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {}}]} }); }); it('keeps unchanged properties', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {name: 'spectrum', parameters: [{name: 'dpt', range: {}}]}, - res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {type: 'array'}}]} + }); + }); + it('keeps only one unchanged property', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/measurement/300000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {name: 'spectrum'}, + res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {type: 'array'}}]} }); }); it('changes the given properties', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}, }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({_id: '300000000000000000000001', name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}); - TemplateMeasurementModel.find({name: 'IR spectrum'}).lean().exec((err, data:any) => { + TemplateMeasurementModel.findById('300000000000000000000001').lean().exec((err, data:any) => { if (err) return done(err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v'); + should(data[0]).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); should(data[0]).have.property('name', 'IR spectrum'); + should(data[0]).have.property('version', 2); should(data[0]).have.property('parameters').have.lengthOf(1); should(data[0].parameters[0]).have.property('name', 'data point table'); should(data[0].parameters[0]).have.property('range'); @@ -454,132 +562,102 @@ describe('/template', () => { }); }); }); + it('allows changing only one property', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/measurement/300000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {name: 'IR spectrum'}, + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '300000000000000000000001', name: 'IR spectrum', parameters: [{name: 'dpt', range: {type: 'array'}}]}); + TemplateMeasurementModel.findById('300000000000000000000001').lean().exec((err, data:any) => { + if (err) return done(err); + should(data).have.lengthOf(1); + should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); + should(data).have.property('name', 'IR spectrum'); + should(data).have.property('version', 2); + should(data).have.property('parameters').have.lengthOf(1); + should(data.parameters[0]).have.property('name', 'dpt'); + should(data.parameters[0]).have.property('range'); + should(data.parameters[0].range).have.property('type', 'array'); + done(); + }); + }); + }); it('supports values ranges', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]}, - res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]} }); }); it('supports min max ranges', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, req: {parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]}, - res: {_id: '300000000000000000000001', name: 'spectrum', parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]} + }); + }); + it('supports min max ranges', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/template/measurement/300000000000000000000001', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {parameters: [{name: 'dpt', range: {type: 'array'}}]}, + res: {_id: '300000000000000000000001', name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {type: 'array'}}]} }); }); it('supports empty ranges', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/kf', + url: '/template/measurement/300000000000000000000002', auth: {basic: 'admin'}, httpStatus: 200, req: {parameters: [{name: 'weight %', range: {}}]}, - res: {_id: '300000000000000000000002', name: 'kf', parameters: [{name: 'weight %', range: {}}]} + res: {_id: '300000000000000000000002', name: 'kf', version: 2, parameters: [{name: 'weight %', range: {}}]} }); }); - it('adds a new template for an unknown name', done => { + it('rejects not specified parameters', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/vz', - auth: {basic: 'admin'}, - httpStatus: 200, - req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]} - }).end(err => { - if (err) return done(err); - TemplateMeasurementModel.find({name: 'vz'}).lean().exec((err, data:any) => { - if (err) return done(err); - should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v'); - should(data[0]).have.property('name', 'vz'); - should(data[0]).have.property('parameters').have.lengthOf(1); - should(data[0].parameters[0]).have.property('name', 'vz'); - should(data[0].parameters[0]).have.property('range'); - should(data[0].parameters[0].range).have.property('min', 1); - done(); - }); - }); - }); - it('rejects a missing name for a new name', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/measurement/spectrum2', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 400, - req: {parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}, - res: {status: 'Invalid body format', details: '"name" is required'} + req: {parameters: [{name: 'dpt'}], range: {xx: 33}}, + res: {} }); }); - it('rejects missing parameters for a new name', done => { + it('rejects an invalid id', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum2', + url: '/template/measurement/3000000000h0000000000001', auth: {basic: 'admin'}, - httpStatus: 400, - req: {name: 'IR spectrum'}, - res: {status: 'Invalid body format', details: '"parameters" is required'} + httpStatus: 404, + req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}, }); }); - it('rejects a missing parameter name for a new name', done => { + it('rejects an unknown id', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum2', + url: '/template/measurement/000000000000000000000001', auth: {basic: 'admin'}, - httpStatus: 400, - req: {name: 'IR spectrum', parameters: [{range: {min: 0, max: 1000}}]}, - res: {status: 'Invalid body format', details: '"parameters[0].name" is required'} - }); - }); - it('rejects a missing parameter range for a new name', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/measurement/spectrum2', - auth: {basic: 'admin'}, - httpStatus: 400, - req: {name: 'IR spectrum', parameters: [{name: 'data point table'}]}, - res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} - }); - }); - it('rejects a an invalid parameter range property for a new name', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/measurement/spectrum2', - auth: {basic: 'admin'}, - httpStatus: 400, - req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {xx: 0}}]}, - res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'} - }); - }); - it('rejects already existing names', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/measurement/spectrum', - auth: {basic: 'admin'}, - httpStatus: 400, - req: {name: 'kf', parameters: [{name: 'dpt', range: {min: 1}}]}, - res: {status: 'Template name already taken'} - }); - }); - it('rejects wrong properties', done => { - TestHelper.request(server, done, { - method: 'put', - url: '/template/measurement/spectrum', - auth: {basic: 'admin'}, - httpStatus: 400, - req: {parameters: [{name: 'dpt'}], xx: 33}, - res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} + httpStatus: 404, + req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}, }); }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {key: 'admin'}, httpStatus: 401, req: {} @@ -588,7 +666,7 @@ describe('/template', () => { it('rejects requests from a write user', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 403, req: {} @@ -597,11 +675,141 @@ describe('/template', () => { it('rejects unauthorized requests', done => { TestHelper.request(server, done, { method: 'put', - url: '/template/measurement/spectrum', + url: '/template/measurement/300000000000000000000001', httpStatus: 401, req: {} }); }); }); + + describe('POST /template/measurement/new', () => { + it('returns the right measurement template', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]} + }).end((err, res) => { + if (err) return done(err); + should(res.body).have.only.keys('_id', 'name', 'version', 'parameters'); + should(res.body).have.property('name', 'vz'); + should(res.body).have.property('version', 1); + should(res.body).have.property('parameters').have.lengthOf(1); + should(res.body.parameters[0]).have.property('name', 'vz'); + should(res.body.parameters[0]).have.property('range'); + should(res.body.parameters[0].range).have.property('min', 1); + }); + }); + it('stores the template', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]} + }).end(err => { + if (err) return done(err); + TemplateMeasurementModel.find({name: 'vz'}).lean().exec((err, data:any) => { + if (err) return done(err); + should(data).have.lengthOf(1); + should(data[0]).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); + should(data[0]).have.property('name', 'vz'); + should(data[0]).have.property('vaersion', 1); + should(data[0]).have.property('parameters').have.lengthOf(1); + should(data[0].parameters[0]).have.property('name', 'vz'); + should(data[0].parameters[0]).have.property('range'); + should(data[0].parameters[0].range).have.property('min', 1); + done(); + }); + }); + }); + it('rejects a missing name', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}, + res: {status: 'Invalid body format', details: '"name" is required'} + }); + }); + it('rejects missing parameters', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'IR spectrum'}, + res: {status: 'Invalid body format', details: '"parameters" is required'} + }); + }); + it('rejects a missing parameter name', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'IR spectrum', parameters: [{range: {min: 0, max: 1000}}]}, + res: {status: 'Invalid body format', details: '"parameters[0].name" is required'} + }); + }); + it('rejects a missing parameter range', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'IR spectrum', parameters: [{name: 'data point table'}]}, + res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} + }); + }); + it('rejects a an invalid parameter range property', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {xx: 0}}]}, + res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'} + }); + }); + it('rejects wrong properties', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {}}], xx: 35}, + res: {} + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {key: 'admin'}, + httpStatus: 401, + req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]} + }); + }); + it('rejects requests from a write user', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/measurement/new', + httpStatus: 401, + req: {name: 'vz', parameters: [{name: 'vz', range: {min: 1}}]} + }); + }); + }); }); }); \ No newline at end of file diff --git a/src/routes/template.ts b/src/routes/template.ts index 55088f9..6c3212b 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -5,6 +5,7 @@ import TemplateValidate from './validate/template'; import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import res400 from './validate/res400'; +import IdValidate from './validate/id'; // TODO: remove f() for await @@ -13,21 +14,20 @@ const router = express.Router(); router.get('/template/:collection(measurements|treatments)', (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return; - (req.params.collection === 'treatments' ? TemplateTreatmentModel : TemplateMeasurementModel) - .find({}).lean().exec((err, data) => { + req.params.collection = req.params.collection.replace(/s$/g, ''); + model(req).find({}).lean().exec((err, data) => { if (err) next (err); - res.json(_.compact(data.map(e => TemplateValidate.output(e)))); // validate all and filter null values from validation errors + res.json(_.compact(data.map(e => TemplateValidate.output(e, req.params.collection)))); // validate all and filter null values from validation errors }); }); -router.get('/template/:collection(measurement|treatment)/:name', (req, res, next) => { +router.get('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return; - (req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel) - .findOne({name: req.params.name}).lean().exec((err, data) => { + model(req).findById(req.params.id).lean().exec((err, data) => { if (err) next (err); if (data) { - res.json(TemplateValidate.output(data)); + res.json(TemplateValidate.output(data, req.params.collection)); } else { res.status(404).json({status: 'Not found'}); @@ -35,19 +35,18 @@ router.get('/template/:collection(measurement|treatment)/:name', (req, res, next }); }); -router.put('/template/:collection(measurement|treatment)/:name', (req, res, next) => { +router.put('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), (req, res, next) => { if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; - const collectionModel = req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel; - collectionModel.findOne({name: req.params.name}).lean().exec((err, data) => { + model(req).findOne({name: req.params.name}).lean().exec((err, data) => { if (err) next (err); const templateState = data? 'change': 'new'; - const {error, value: template} = TemplateValidate.input(req.body, templateState); + const {error, value: template} = TemplateValidate.input(req.body, templateState, req.params.collection); if (error) return res400(error, res); if (template.hasOwnProperty('name') && template.name !== req.params.name) { - collectionModel.find({name: template.name}).lean().exec((err, data) => { + model(req).find({name: template.name}).lean().exec((err, data) => { if (err) next (err); if (data.length > 0) { res.status(400).json({status: 'Template name already taken'}); @@ -63,9 +62,9 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next } function f() { // to resolve async - collectionModel.findOneAndUpdate({name: req.params.name}, template, {new: true, upsert: true}).lean().exec((err, data) => { + model(req).findOneAndUpdate({name: req.params.name}, template, {new: true, upsert: true}).lean().exec((err, data) => { if (err) return next(err); - res.json(TemplateValidate.output(data)); + res.json(TemplateValidate.output(data, req.params.collection)); }); } }); @@ -87,4 +86,9 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next // }); -module.exports = router; \ No newline at end of file +module.exports = router; + + +function model (req) { + return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel; +} \ No newline at end of file diff --git a/src/routes/validate/template.ts b/src/routes/validate/template.ts index a279dce..440c515 100644 --- a/src/routes/validate/template.ts +++ b/src/routes/validate/template.ts @@ -6,6 +6,13 @@ export default class TemplateValidate { name: joi.string() .max(128), + version: joi.number() + .min(1), + + number_prefix: joi.string() + .min(1) + .max(16), + parameters: joi.array() .min(1) .items( @@ -29,31 +36,63 @@ export default class TemplateValidate { ) }; - static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated) + static input (data, param, template) { // validate data, param: new(everything required)/change(available attributes are validated) if (param === 'new') { - return joi.object({ - name: this.template.name.required(), - parameters: this.template.parameters.required() - }).validate(data); + if (template === 'treatment') { + return joi.object({ + name: this.template.name.required(), + number_prefix: this.template.number_prefix.required(), + parameters: this.template.parameters.required() + }).validate(data); + } + else { + return joi.object({ + name: this.template.name.required(), + parameters: this.template.parameters.required() + }).validate(data); + } } else if (param === 'change') { - return joi.object({ - name: this.template.name, - parameters: this.template.parameters - }).validate(data); + if (template === 'treatment') { + return joi.object({ + name: this.template.name, + number_prefix: this.template.number_prefix, + parameters: this.template.parameters + }).validate(data); + } + else { + return joi.object({ + name: this.template.name, + parameters: this.template.parameters + }).validate(data); + } } else { return{error: 'No parameter specified!', value: {}}; } } - static output (data) { // validate output from database for needed properties, strip everything else + static output (data, template) { // validate output from database for needed properties, strip everything else data = IdValidate.stringify(data); - const {value, error} = joi.object({ - _id: IdValidate.get(), - name: this.template.name, - parameters: this.template.parameters - }).validate(data, {stripUnknown: true}); + let joiObject; + if (template === 'treatment') { + joiObject = { + _id: IdValidate.get(), + name: this.template.name, + version: this.template.version, + number_prefix: this.template.number_prefix, + parameters: this.template.parameters + }; + } + else { + joiObject = { + _id: IdValidate.get(), + name: this.template.name, + version: this.template.version, + parameters: this.template.parameters + }; + } + const {value, error} = joi.object(joiObject).validate(data, {stripUnknown: true}); return error !== undefined? null : value; } } \ No newline at end of file From 8315fc0d3ba63a07bec516b0733dd381089e4bc6 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 14 May 2020 15:36:47 +0200 Subject: [PATCH 3/4] PUT finished --- src/models/measurement_template.ts | 2 +- src/models/treatment_template.ts | 2 +- src/routes/condition.ts | 4 +- src/routes/material.ts | 4 +- src/routes/measurement.ts | 4 +- src/routes/sample.spec.ts | 26 +++++++ src/routes/sample.ts | 44 ++++++----- src/routes/template.spec.ts | 117 +++++++++++++++++------------ src/routes/template.ts | 58 +++++++------- src/routes/validate/template.ts | 8 +- src/routes/validate/user.ts | 2 +- 11 files changed, 162 insertions(+), 109 deletions(-) diff --git a/src/models/measurement_template.ts b/src/models/measurement_template.ts index 1b0f6e9..080f42b 100644 --- a/src/models/measurement_template.ts +++ b/src/models/measurement_template.ts @@ -1,7 +1,7 @@ import mongoose from 'mongoose'; const MeasurementTemplateSchema = new mongoose.Schema({ - name: {type: String, index: {unique: true}}, + name: String, version: Number, parameters: [{ name: String, diff --git a/src/models/treatment_template.ts b/src/models/treatment_template.ts index 8fc4af8..154ae79 100644 --- a/src/models/treatment_template.ts +++ b/src/models/treatment_template.ts @@ -1,7 +1,7 @@ import mongoose from 'mongoose'; const TreatmentTemplateSchema = new mongoose.Schema({ - name: {type: String, index: {unique: true}}, + name: String, version: Number, number_prefix: String, parameters: [{ diff --git a/src/routes/condition.ts b/src/routes/condition.ts index 8acc2f2..89ddce0 100644 --- a/src/routes/condition.ts +++ b/src/routes/condition.ts @@ -34,9 +34,7 @@ router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => { 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 instanceof Error) return; if (!data) { res.status(404).json({status: 'Not found'}); } diff --git a/src/routes/material.ts b/src/routes/material.ts index a912f5e..1c33591 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -104,9 +104,7 @@ 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 instanceof Error) return false; if (materialData) { // could not find material_id res.status(400).json({status: 'Material name already taken'}); return false; diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts index 4aa89c1..bb69b3f 100644 --- a/src/routes/measurement.ts +++ b/src/routes/measurement.ts @@ -32,9 +32,7 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => { if (error) return res400(error, res); const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; - if (data instanceof Error) { - return; - } + if (data instanceof Error) return; if (!data) { res.status(404).json({status: 'Not found'}); } diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index 99f6344..42c8435 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -124,6 +124,32 @@ describe('/sample', () => { }); }); }); + it('keeps unchanged notes', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/400000000000000000000002', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {notes: {comment: 'Stoff gesperrt', sample_references: []}} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '400000000000000000000002', number: '21', type: 'granulate', color: 'natural', batch: '1560237365', material_id: '100000000000000000000001', note_id: '500000000000000000000001', user_id: '000000000000000000000002'}); + SampleModel.findById('400000000000000000000002').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', '21'); + should(data).have.property('color', 'natural'); + should(data).have.property('type', 'granulate'); + should(data).have.property('batch', '1560237365'); + should(data.material_id.toString()).be.eql('100000000000000000000001'); + should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', 10); + should(data.note_id.toString()).be.eql('500000000000000000000001'); + done(); + }); + }); + }); it('changes the given properties', done => { TestHelper.request(server, done, { method: 'put', diff --git a/src/routes/sample.ts b/src/routes/sample.ts index a6e9ced..6acb7d2 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -46,25 +46,31 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => { if (!await materialCheck(sample, res, next, sampleData.material_id)) return; } - if (sample.hasOwnProperty('notes') && sampleData.note_id !== null) { // deal with old notes data - NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { - if (err) return console.error(err); - if (data.hasOwnProperty('custom_fields')) { // update note_fields - customFieldsChange(Object.keys(data.custom_fields), -1); + if (sample.hasOwnProperty('notes')) { + let newNotes = true; + if (sampleData.note_id !== null) { // old notes data exists + const data = await NoteModel.findById(sampleData.note_id).lean().exec().catch(err => {next(err);}) as any; + if (data instanceof Error) return; + newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes); + if (newNotes) { + if (data.hasOwnProperty('custom_fields')) { // update note_fields + customFieldsChange(Object.keys(data.custom_fields), -1); + } + NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => { // delete old notes + if (err) return console.error(err); + }); } - NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => { // delete old notes - if (err) return console.error(err); - }) - }); - } - if (sample.hasOwnProperty('notes') && Object.keys(sample.notes).length > 0) { // save new notes - if (!await sampleRefCheck(sample, res, next)) return; - if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) { // new custom_fields - customFieldsChange(Object.keys(sample.notes.custom_fields), 1); } - let data = await new NoteModel(sample.notes).save().catch(err => { return next(err)}); // save new notes - delete sample.notes; - sample.note_id = data._id; + + if (_.keys(sample.notes).length > 0 && newNotes) { // save new notes + if (!await sampleRefCheck(sample, res, next)) return; + if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) { // new custom_fields + customFieldsChange(Object.keys(sample.notes.custom_fields), 1); + } + let data = await new NoteModel(sample.notes).save().catch(err => { return next(err)}); // save new notes + delete sample.notes; + sample.note_id = data._id; + } } // check for changes @@ -160,9 +166,7 @@ async function numberCheck (sample, res, next) { // validate number, returns fa async function materialCheck (sample, res, next, id = sample.material_id) { // validate material_id and color, returns false if invalid const materialData = await MaterialModel.findById(id).lean().exec().catch(err => {next(err); return false;}) as any; - if (materialData instanceof Error) { - return false; - } + if (materialData instanceof Error) return false; if (!materialData) { // could not find material_id res.status(400).json({status: 'Material not available'}); return false; diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts index e89e238..c42430b 100644 --- a/src/routes/template.spec.ts +++ b/src/routes/template.spec.ts @@ -1,4 +1,5 @@ import should from 'should/as-function'; +import _ from 'lodash'; import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import TestHelper from "../test/helper"; @@ -130,8 +131,7 @@ describe('/template', () => { req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1}}]}); - TemplateTreatmentModel.findById('200000000000000000000001').lean().exec((err, data:any) => { + TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => { if (err) return done(err); should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); should(data).have.property('name', 'heat aging'); @@ -154,17 +154,15 @@ describe('/template', () => { req: {name: 'heat aging'} }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '200000000000000000000001', name: 'heat aging', version: 2, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}); - TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => { + TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => { if (err) return done(err); - should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); - should(data[0]).have.property('name', 'heat aging'); - should(data[0]).have.property('version', 2); - should(data[0]).have.property('number_prefix', 'A'); - should(data[0]).have.property('parameters').have.lengthOf(1); - should(data[0].parameters[0]).have.property('name', 'material'); - should(data[0].parameters[1]).have.property('name', 'weeks'); + should(data).have.only.keys('_id', 'name', 'version', 'number_prefix', 'parameters', '__v'); + should(data).have.property('name', 'heat aging'); + should(data).have.property('version', 2); + should(data).have.property('number_prefix', 'A'); + should(data).have.property('parameters').have.lengthOf(2); + should(data.parameters[0]).have.property('name', 'material'); + should(data.parameters[1]).have.property('name', 'weeks'); done(); }); }); @@ -175,8 +173,11 @@ describe('/template', () => { url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {values: [1, 2, 5]}}]} + req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {values: [1, 2, 5]}}]}); + done(); }); }); it('supports min max ranges', done => { @@ -185,8 +186,11 @@ describe('/template', () => { url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1, max: 11}}]} + req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {min: 1, max: 11}}]}); + done(); }); }); it('supports array type ranges', done => { @@ -195,8 +199,12 @@ describe('/template', () => { url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'time', range: {type: 'array'}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]} + req: {parameters: [{name: 'time', range: {type: 'array'}}]} + }).end((err, res) => { + console.log(res.body); + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]}); + done(); }); }); it('supports empty ranges', done => { @@ -205,8 +213,11 @@ describe('/template', () => { url: '/template/treatment/200000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'time', range: {}}]}, - res: {_id: '200000000000000000000001', name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {}}]} + req: {parameters: [{name: 'time', range: {}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {}}]}); + done(); }); }); it('rejects not specified parameters', done => { @@ -216,7 +227,7 @@ describe('/template', () => { auth: {basic: 'admin'}, httpStatus: 400, req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]}, - res: {} + res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'} }); }) it('rejects an invalid id', done => { @@ -478,7 +489,7 @@ describe('/template', () => { url: '/template/measurement/300000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: { type: 'array'}}]} }); }); it('rejects an API key', done => { @@ -514,7 +525,7 @@ describe('/template', () => { auth: {basic: 'admin'}, httpStatus: 200, req: {}, - res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {}}]} + res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: { type: 'array'}}]} }); }); it('keeps unchanged properties', done => { @@ -523,7 +534,7 @@ describe('/template', () => { url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {name: 'spectrum', parameters: [{name: 'dpt', range: {}}]}, + req: {name: 'spectrum', parameters: [{name: 'dpt', range: { type: 'array'}}]}, res: {_id: '300000000000000000000001', name: 'spectrum', version: 1, parameters: [{name: 'dpt', range: {type: 'array'}}]} }); }); @@ -546,18 +557,17 @@ describe('/template', () => { req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}, }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '300000000000000000000001', name: 'IR spectrum', parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}); - TemplateMeasurementModel.findById('300000000000000000000001').lean().exec((err, data:any) => { + should(_.omit(res.body, '_id')).be.eql({name: 'IR spectrum', version: 2, parameters: [{name: 'data point table', range: {min: 0, max: 1000}}]}); + TemplateMeasurementModel.findById(res.body._id).lean().exec((err, data:any) => { if (err) return done(err); - should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); - should(data[0]).have.property('name', 'IR spectrum'); - should(data[0]).have.property('version', 2); - should(data[0]).have.property('parameters').have.lengthOf(1); - should(data[0].parameters[0]).have.property('name', 'data point table'); - should(data[0].parameters[0]).have.property('range'); - should(data[0].parameters[0].range).have.property('min', 0); - should(data[0].parameters[0].range).have.property('max', 1000); + should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); + should(data).have.property('name', 'IR spectrum'); + should(data).have.property('version', 2); + should(data).have.property('parameters').have.lengthOf(1); + should(data.parameters[0]).have.property('name', 'data point table'); + should(data.parameters[0]).have.property('range'); + should(data.parameters[0].range).have.property('min', 0); + should(data.parameters[0].range).have.property('max', 1000); done(); }); }); @@ -571,10 +581,9 @@ describe('/template', () => { req: {name: 'IR spectrum'}, }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '300000000000000000000001', name: 'IR spectrum', parameters: [{name: 'dpt', range: {type: 'array'}}]}); - TemplateMeasurementModel.findById('300000000000000000000001').lean().exec((err, data:any) => { + should(_.omit(res.body, '_id')).be.eql({name: 'IR spectrum', version: 2, parameters: [{name: 'dpt', range: {type: 'array'}}]}); + TemplateMeasurementModel.findById(res.body._id).lean().exec((err, data:any) => { if (err) return done(err); - should(data).have.lengthOf(1); should(data).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); should(data).have.property('name', 'IR spectrum'); should(data).have.property('version', 2); @@ -592,8 +601,11 @@ describe('/template', () => { url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]}, - res: {_id: '300000000000000000000001', name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]} + req: {parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {values: [1, 2, 5]}}]}); + done(); }); }); it('supports min max ranges', done => { @@ -602,18 +614,24 @@ describe('/template', () => { url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]}, - res: {_id: '300000000000000000000001', name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]} + req: {parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {min: 0, max: 1000}}]}); + done(); }); }); - it('supports min max ranges', done => { + it('supports array type ranges', done => { TestHelper.request(server, done, { method: 'put', url: '/template/measurement/300000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'dpt', range: {type: 'array'}}]}, - res: {_id: '300000000000000000000001', name: 'spectrum', version: 2, parameters: [{name: 'dpt', range: {type: 'array'}}]} + req: {parameters: [{name: 'dpt2', range: {type: 'array'}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'spectrum', version: 2, parameters: [{name: 'dpt2', range: {type: 'array'}}]}); + done(); }); }); it('supports empty ranges', done => { @@ -622,8 +640,11 @@ describe('/template', () => { url: '/template/measurement/300000000000000000000002', auth: {basic: 'admin'}, httpStatus: 200, - req: {parameters: [{name: 'weight %', range: {}}]}, - res: {_id: '300000000000000000000002', name: 'kf', version: 2, parameters: [{name: 'weight %', range: {}}]} + req: {parameters: [{name: 'weight %', range: {}}]} + }).end((err, res) => { + if (err) return done(err); + should(_.omit(res.body, '_id')).be.eql({name: 'kf', version: 3, parameters: [{name: 'weight %', range: {}}]}); + done(); }); }); it('rejects not specified parameters', done => { @@ -633,7 +654,7 @@ describe('/template', () => { auth: {basic: 'admin'}, httpStatus: 400, req: {parameters: [{name: 'dpt'}], range: {xx: 33}}, - res: {} + res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} }); }); it('rejects an invalid id', done => { diff --git a/src/routes/template.ts b/src/routes/template.ts index 6c3212b..dac7a01 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -35,39 +35,32 @@ router.get('/template/:collection(measurement|treatment)/' + IdValidate.paramete }); }); -router.put('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), (req, res, next) => { +router.put('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), async (req, res, next) => { if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; + const {error, value: template} = TemplateValidate.input(req.body, 'change', req.params.collection); + if (error) return res400(error, res); - model(req).findOne({name: req.params.name}).lean().exec((err, data) => { - if (err) next (err); - const templateState = data? 'change': 'new'; - const {error, value: template} = TemplateValidate.input(req.body, templateState, req.params.collection); - if (error) return res400(error, res); + const templateData = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; + if (templateData instanceof Error) return; + if (!templateData) { + res.status(404).json({status: 'Not found'}); + } - if (template.hasOwnProperty('name') && template.name !== req.params.name) { - model(req).find({name: template.name}).lean().exec((err, data) => { - if (err) next (err); - if (data.length > 0) { - res.status(400).json({status: 'Template name already taken'}); - return; - } - else { - f(); - } - }); - } - else { - f(); - } + if (_.has(template, 'number_prefix') && template.number_prefix !== templateData.number_prefix) { // got new number_prefix + if (!await numberPrefixCheck(template, req, res, next)) return; + } - function f() { // to resolve async - model(req).findOneAndUpdate({name: req.params.name}, template, {new: true, upsert: true}).lean().exec((err, data) => { - if (err) return next(err); - res.json(TemplateValidate.output(data, req.params.collection)); - }); - } - }); + if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed + template.version = templateData.version + 1; + await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => { + if (err) next (err); + res.json(TemplateValidate.output(data.toObject(), req.params.collection)); + }); + } + else { + res.json(TemplateValidate.output(templateData, req.params.collection)); + } }); // router.delete('/template/:collection(measurement|treatment)/:name', (req, res, next) => { @@ -89,6 +82,15 @@ router.put('/template/:collection(measurement|treatment)/' + IdValidate.paramete module.exports = router; +async function numberPrefixCheck (template, req, res, next) { + const data = await model(req).findOne({number_prefix: template.number_prefix}).lean().exec().catch(err => {next(err); return false;}) as any; + if (data) { + res.status(400).json({status: 'Number prefix already taken'}); + return false + } + return true; +} + function model (req) { return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel; } \ No newline at end of file diff --git a/src/routes/validate/template.ts b/src/routes/validate/template.ts index 440c515..7cb461d 100644 --- a/src/routes/validate/template.ts +++ b/src/routes/validate/template.ts @@ -27,10 +27,16 @@ export default class TemplateValidate { min: joi.number(), - max: joi.number() + max: joi.number(), + + type: joi.string() + .valid('array') }) .oxor('values', 'min') .oxor('values', 'max') + .oxor('type', 'values') + .oxor('type', 'min') + .oxor('type', 'max') .required() }) ) diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts index 024d1a9..4472aa8 100644 --- a/src/routes/validate/user.ts +++ b/src/routes/validate/user.ts @@ -5,7 +5,7 @@ import IdValidate from './id'; export default class UserValidate { // validate input for user private static user = { - name: Joi.string() + name: Joi.string() // TODO: check allowed characters .alphanum() .lowercase() .max(128), From ab8c74a6410b29bca1b66369ac39b0a33fbfd94f Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 14 May 2020 16:42:47 +0200 Subject: [PATCH 4/4] POST finished --- src/routes/template.spec.ts | 28 ++++++++++++++++++++-------- src/routes/template.ts | 30 ++++++++++++++++-------------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts index c42430b..d9673b7 100644 --- a/src/routes/template.spec.ts +++ b/src/routes/template.spec.ts @@ -4,7 +4,7 @@ import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import TestHelper from "../test/helper"; -// TODO: convert name to id, criteria for new name, criteria for new version, criteria for prefix + describe('/template', () => { let server; @@ -305,6 +305,7 @@ describe('/template', () => { should(res.body.parameters[0]).have.property('range'); should(res.body.parameters[0].range).have.property('values'); should(res.body.parameters[0].range.values[0]).be.eql('copper'); + done(); }); }); it('stores the template', done => { @@ -366,17 +367,27 @@ describe('/template', () => { url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {name: 'heat aging', parameters: [{range: {min: 1}}]}, + req: {name: 'heat aging', number_prefix: 'C', parameters: [{range: {min: 1}}]}, res: {status: 'Invalid body format', details: '"parameters[0].name" is required'} }); }); + it('rejects a missing number prefix', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/template/treatment/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}, + res: {status: 'Invalid body format', details: '"number_prefix" is required'} + }); + }); it('rejects a missing parameter range', done => { TestHelper.request(server, done, { method: 'post', url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {name: 'heat aging', parameters: [{name: 'time'}]}, + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time'}]}, res: {status: 'Invalid body format', details: '"parameters[0].range" is required'} }); }); @@ -386,7 +397,7 @@ describe('/template', () => { url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {name: 'heat aging', parameters: [{name: 'time', range: {xx: 1}}]}, + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {xx: 1}}]}, res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'} }); }); @@ -396,8 +407,8 @@ describe('/template', () => { url: '/template/treatment/new', auth: {basic: 'admin'}, httpStatus: 400, - req: {name: 'heat aging', parameters: [{name: 'time', range: {}}], xx: 33}, - res: {} + req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {}}], xx: 33}, + res: {status: 'Invalid body format', details: '"xx" is not allowed'} }); }); it('rejects already existing number prefixes', done => { @@ -720,6 +731,7 @@ describe('/template', () => { should(res.body.parameters[0]).have.property('name', 'vz'); should(res.body.parameters[0]).have.property('range'); should(res.body.parameters[0].range).have.property('min', 1); + done(); }); }); it('stores the template', done => { @@ -736,7 +748,7 @@ describe('/template', () => { should(data).have.lengthOf(1); should(data[0]).have.only.keys('_id', 'name', 'version', 'parameters', '__v'); should(data[0]).have.property('name', 'vz'); - should(data[0]).have.property('vaersion', 1); + should(data[0]).have.property('version', 1); should(data[0]).have.property('parameters').have.lengthOf(1); should(data[0].parameters[0]).have.property('name', 'vz'); should(data[0].parameters[0]).have.property('range'); @@ -802,7 +814,7 @@ describe('/template', () => { auth: {basic: 'admin'}, httpStatus: 400, req: {name: 'IR spectrum', parameters: [{name: 'data point table', range: {}}], xx: 35}, - res: {} + res: {status: 'Invalid body format', details: '"xx" is not allowed'} }); }); it('rejects an API key', done => { diff --git a/src/routes/template.ts b/src/routes/template.ts index dac7a01..3997944 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -63,20 +63,22 @@ router.put('/template/:collection(measurement|treatment)/' + IdValidate.paramete } }); -// router.delete('/template/:collection(measurement|treatment)/:name', (req, res, next) => { -// if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; -// -// (req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel) -// .findOneAndDelete({name: req.params.name}).lean().exec((err, data) => { -// if (err) return next(err); -// if (data) { -// res.json({status: 'OK'}) -// } -// else { -// res.status(404).json({status: 'Not found'}); -// } -// }); -// }); +router.post('/template/:collection(measurement|treatment)/new', async (req, res, next) => { + if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; + + const {error, value: template} = TemplateValidate.input(req.body, 'new', req.params.collection); + if (error) return res400(error, res); + + if (_.has(template, 'number_prefix')) { // got number_prefix + if (!await numberPrefixCheck(template, req, res, next)) return; + } + + template.version = 1; + await new (model(req))(template).save((err, data) => { + if (err) next (err); + res.json(TemplateValidate.output(data.toObject(), req.params.collection)); + }); +}); module.exports = router;