From 54168e45008692cd9102f3da40be7b205318bee0 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 12:40:37 +0200 Subject: [PATCH 1/7] adjusted PUT /sample/{id} --- api/sample.yaml | 2 +- src/routes/material.spec.ts | 2 ++ src/routes/sample.spec.ts | 17 ++++++++++------- src/routes/sample.ts | 3 +++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/api/sample.yaml b/api/sample.yaml index 9e830ff..cea8de7 100644 --- a/api/sample.yaml +++ b/api/sample.yaml @@ -69,7 +69,7 @@ put: summary: change sample description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to edit samples created by another user' - x-doc: status is reset to 0 on any changes + x-doc: status is reset to 0 on any changes, deleted samples cannot be changed # TODO tags: - /sample security: diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index ae8d305..6c70e07 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -7,6 +7,8 @@ import globals from '../globals'; // TODO: color name must be unique to get color number // TODO: separate supplier/ material name into own collections +// TODO: restore material + describe('/material', () => { let server; before(done => TestHelper.before(done)); diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index cfeeb7c..f0bbe88 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -10,9 +10,9 @@ import globals from '../globals'; // TODO: write script for data import // TODO: delete everything (measurements, condition) with sample // TODO: allow adding sample numbers for existing samples - // TODO: Do not allow validation or measurement entry without condition +// TODO: restore sample describe('/sample', () => { let server; @@ -187,7 +187,6 @@ describe('/sample', () => { res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, user: 'admin'} }); }); - it('works with an API key', done => { TestHelper.request(server, done, { method: 'get', @@ -197,7 +196,6 @@ describe('/sample', () => { res: {_id: '400000000000000000000003', number: '33', type: 'part', color: 'black', batch: '1704-005', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {comment: '', sample_references: [{sample_id: '400000000000000000000004', relation: 'granulate to sample'}], custom_fields: {'not allowed for new applications': true}}, user: 'admin'} }); }); - it('returns a deleted sample for a maintain/admin user', done => { TestHelper.request(server, done, { method: 'get', @@ -207,7 +205,6 @@ describe('/sample', () => { res: {_id: '400000000000000000000005', number: 'Rng33', type: 'granulate', color: 'black', batch: '1653000308', condition: {condition_template: '200000000000000000000003'}, material: {_id: '100000000000000000000005', name: 'Amodel A 1133 HS', supplier: 'Solvay', group: 'PPA', mineral: 0, glass_fiber: 33, carbon_fiber: 0, numbers: [{color: 'black', number: '5514262406'}]}, notes: {}, user: 'admin'} }); }); - it('returns 403 for a write user when requesting a deleted sample', done => { TestHelper.request(server, done, { method: 'get', @@ -216,7 +213,6 @@ describe('/sample', () => { httpStatus: 403 }); }); - it('returns 404 for an unknown sample', done => { TestHelper.request(server, done, { method: 'get', @@ -225,7 +221,6 @@ describe('/sample', () => { httpStatus: 404 }); }); - it('rejects an invalid id', done => { TestHelper.request(server, done, { method: 'get', @@ -234,7 +229,6 @@ describe('/sample', () => { httpStatus: 404 }); }); - it('rejects unauthorized requests', done => { TestHelper.request(server, done, { method: 'get', @@ -589,6 +583,15 @@ describe('/sample', () => { res: {status: 'Condition template not available'} }); }); + it('rejects editing a deleted sample', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/400000000000000000000005', + auth: {basic: 'admin'}, + httpStatus: 403, + req: {} + }); + }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'put', diff --git a/src/routes/sample.ts b/src/routes/sample.ts index ed1afb3..3976231 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -69,6 +69,9 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => { if (!sampleData) { return res.status(404).json({status: 'Not found'}); } + if (sampleData.status === globals.status.deleted) { + return res.status(403).json({status: 'Forbidden'}); + } // only maintain and admin are allowed to edit other user's data if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return; From d58026426c8e3b5cdcce5006065791b8b4e119e5 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 13:05:00 +0200 Subject: [PATCH 2/7] delete measurements with sample --- api/sample.yaml | 2 +- src/routes/sample.spec.ts | 23 +++++++++++++++++++++-- src/routes/sample.ts | 30 ++++++++++++++++++------------ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/api/sample.yaml b/api/sample.yaml index cea8de7..9f52f77 100644 --- a/api/sample.yaml +++ b/api/sample.yaml @@ -69,7 +69,7 @@ put: summary: change sample description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to edit samples created by another user' - x-doc: status is reset to 0 on any changes, deleted samples cannot be changed # TODO + x-doc: status is reset to 0 on any changes, deleted samples cannot be changed tags: - /sample security: diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index f0bbe88..1bbfd5d 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -2,13 +2,15 @@ import should from 'should/as-function'; import SampleModel from '../models/sample'; import NoteModel from '../models/note'; import NoteFieldModel from '../models/note_field'; +import MeasurementModel from '../models/measurement'; import TestHelper from "../test/helper"; import globals from '../globals'; +import mongoose from 'mongoose'; // TODO: generate output for ML in format DPT -> data, implement filtering, field selection +// TODO: generate csv // TODO: filter by not completely filled/no measurements // TODO: write script for data import -// TODO: delete everything (measurements, condition) with sample // TODO: allow adding sample numbers for existing samples // TODO: Do not allow validation or measurement entry without condition @@ -752,6 +754,24 @@ describe('/sample', () => { }); }); }); + it('deletes associated measurements', done => { + TestHelper.request(server, done, { + method: 'delete', + url: '/sample/400000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200 + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({status: 'OK'}); + MeasurementModel.find({sample_id: mongoose.Types.ObjectId('400000000000000000000001')}).lean().exec((err, data: any) => { + if (err) return done(err); + should(data).matchEach(sample => { + should(sample).have.property('status', -1); + }); + done(); + }); + }); + }); it('rejects deleting samples of other users for write users', done => { TestHelper.request(server, done, { method: 'delete', @@ -768,7 +788,6 @@ describe('/sample', () => { httpStatus: 404 }); }); - it('rejects deleting a sample referenced by conditions'); // TODO after decision it('rejects requests from a read user', done => { TestHelper.request(server, done, { method: 'delete', diff --git a/src/routes/sample.ts b/src/routes/sample.ts index 3976231..9166bbb 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -10,7 +10,7 @@ import MaterialModel from '../models/material'; import NoteModel from '../models/note'; import NoteFieldModel from '../models/note_field'; import IdValidate from './validate/id'; -import mongoose from "mongoose"; +import mongoose from 'mongoose'; import ConditionTemplateModel from '../models/condition_template'; import ParametersValidate from './validate/parameters'; import globals from '../globals'; @@ -141,18 +141,24 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => { await SampleModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => { // set sample status if (err) return next(err); - if (sampleData.note_id !== null) { // handle notes - NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields - if (err) return next(err); - if (data.hasOwnProperty('custom_fields')) { // update note_fields - customFieldsChange(Object.keys(data.custom_fields), -1); - } + + // set status of associated measurements also to deleted + MeasurementModel.update({sample_id: mongoose.Types.ObjectId(req.params.id)}, {status: -1}).lean().exec(err => { + if (err) return next(err); + + if (sampleData.note_id !== null) { // handle notes + NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields + if (err) return next(err); + if (data.hasOwnProperty('custom_fields')) { // update note_fields + customFieldsChange(Object.keys(data.custom_fields), -1); + } + res.json({status: 'OK'}); + }); + } + else { res.json({status: 'OK'}); - }); - } - else { - res.json({status: 'OK'}); - } + } + }); }); }); }); From c4752d12bad8f50bdc49a42ca01eba05f3c3525e Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 13:16:15 +0200 Subject: [PATCH 3/7] adapted /measurements --- api/measurement.yaml | 4 ++-- src/routes/measurement.spec.ts | 9 +++++++++ src/routes/measurement.ts | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/api/measurement.yaml b/api/measurement.yaml index 298b04e..9116a8c 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -4,7 +4,7 @@ get: summary: measurement values by id description: 'Auth: all, levels: read, write, maintain, dev, admin' - x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision + x-doc: deleted samples are available only for maintain/admin tags: - /measurement responses: @@ -25,7 +25,7 @@ put: summary: change measurement description: 'Auth: basic, levels: write, maintain, dev, admin' - x-doc: status is reset to 0 on any changes + x-doc: status is reset to 0 on any changes, deleted measurements cannot be edited tags: - /measurement security: diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index 5af91a3..113847f 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -255,6 +255,15 @@ describe('/measurement', () => { httpStatus: 404 }); }); + it('rejects editing a deleted measurement', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/800000000000000000000004', + auth: {basic: 'admin'}, + httpStatus: 403, + req: {} + }); + }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'put', diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts index 0d0f0f6..ab9d50e 100644 --- a/src/routes/measurement.ts +++ b/src/routes/measurement.ts @@ -38,6 +38,9 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => { if (!data) { return res.status(404).json({status: 'Not found'}); } + if (data.status === globals.status.deleted) { + return res.status(403).json({status: 'Forbidden'}); + } // add properties needed for sampleIdCheck measurement.measurement_template = data.measurement_template; From 1c2631c6fba9ac1f4230d51536e965b85428b512 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 14:11:19 +0200 Subject: [PATCH 4/7] adapted /materials --- api/material.yaml | 4 ++-- src/index.ts | 1 + src/routes/material.spec.ts | 26 ++++++++++++++++++++++++++ src/routes/material.ts | 15 +++++++++------ src/routes/sample.ts | 2 +- src/test/db.json | 17 +++++++++++++++++ 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/api/material.yaml b/api/material.yaml index d184a3f..51af0ef 100644 --- a/api/material.yaml +++ b/api/material.yaml @@ -48,7 +48,7 @@ get: summary: get material details description: 'Auth: all, levels: read, write, maintain, dev, admin' - x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision + x-doc: deleted samples are available only for maintain/admin tags: - /material responses: @@ -67,7 +67,7 @@ put: summary: change material description: 'Auth: basic, levels: write, maintain, dev, admin' - x-doc: status is reset to 0 on any changes + x-doc: status is reset to 0 on any changes, deleted samples cannot be changed tags: - /material security: diff --git a/src/index.ts b/src/index.ts index 0de6ff4..7dda199 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import db from './db'; // TODO: coverage // TODO: think about the display of deleted/new samples and validation in data and UI // TODO: improve error coverage +// TODO: guess properties from material name in UI // tell if server is running in debug or production environment console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT ====='); diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index 6c70e07..330d5b7 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -204,6 +204,23 @@ describe('/material', () => { res: {_id: '100000000000000000000007', name: 'Ultramid A4H', supplier: 'BASF', group: 'PA66', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: [{color: 'black', number: ''}]} }); }); + it('returns a deleted material for a maintain/admin user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/100000000000000000000008', + auth: {basic: 'admin'}, + httpStatus: 200, + res: {_id: '100000000000000000000008', name: 'Latamid 66 H 2 G 30', supplier: 'LATI', group: 'PA66', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'blue', number: '5513943509'}]} + }); + }); + it('returns 403 for a write user when requesting a deleted material', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/100000000000000000000008', + auth: {basic: 'janedoe'}, + httpStatus: 403 + }); + }); it('rejects an invalid id', done => { TestHelper.request(server, done, { method: 'get', @@ -363,6 +380,15 @@ describe('/material', () => { req: {}, }); }); + it('rejects editing a deleted material', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/100000000000000000000008', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {} + }); + }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'put', diff --git a/src/routes/material.ts b/src/routes/material.ts index 4a1adb8..ffba3ef 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -34,14 +34,14 @@ router.get('/materials/:group(new|deleted)', (req, res, next) => { router.get('/material/' + IdValidate.parameter(), (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; - MaterialModel.findById(req.params.id).lean().exec((err, data) => { + MaterialModel.findById(req.params.id).lean().exec((err, data: any) => { if (err) return next(err); - if (data) { - res.json(MaterialValidate.output(data)); - } - else { - res.status(404).json({status: 'Not found'}); + + if (!data) { + return res.status(404).json({status: 'Not found'}); } + if (data.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted materials only available for maintain/admin + res.json(MaterialValidate.output(data)); }); }); @@ -55,6 +55,9 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => { if (!materialData) { return res.status(404).json({status: 'Not found'}); } + if (materialData.status === globals.status.deleted) { + return res.status(403).json({status: 'Forbidden'}); + } if (material.hasOwnProperty('name') && material.name !== materialData.name) { if (!await nameCheck(material, res, next)) return; } diff --git a/src/routes/sample.ts b/src/routes/sample.ts index 9166bbb..23e786a 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -43,7 +43,7 @@ router.get('/sample/' + IdValidate.parameter(), (req, res, next) => { if (err) return next(err); if (sampleData) { - if (sampleData.status ===globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted samples only available for maintain/admin + if (sampleData.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted samples only available for maintain/admin sampleData.material = sampleData.material_id; // map data to right keys sampleData.user = sampleData.user_id.name; sampleData.notes = sampleData.note_id ? sampleData.note_id : {}; diff --git a/src/test/db.json b/src/test/db.json index 372b09a..de4070f 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -268,6 +268,23 @@ ], "status": 0, "__v": 0 + }, + { + "_id": {"$oid":"100000000000000000000008"}, + "name": "Latamid 66 H 2 G 30", + "supplier": "LATI", + "group": "PA66", + "mineral": 0, + "glass_fiber": 30, + "carbon_fiber": 0, + "numbers": [ + { + "color": "blue", + "number": "5513943509" + } + ], + "status": -1, + "__v": 0 } ], "measurements": [ From 0fd0cc8da4a0717d13e4b1cde8c84d6bdb2f9a5d Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 14:41:35 +0200 Subject: [PATCH 5/7] restore samples --- api/sample.yaml | 23 ++++++++++++++++ src/routes/sample.spec.ts | 56 ++++++++++++++++++++++++++++++++++++++- src/routes/sample.ts | 13 +++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/api/sample.yaml b/api/sample.yaml index 9f52f77..67f25ac 100644 --- a/api/sample.yaml +++ b/api/sample.yaml @@ -119,6 +119,29 @@ 500: $ref: 'api.yaml#/components/responses/500' +/sample/restore/{id}: + parameters: + - $ref: 'api.yaml#/components/parameters/Id' + put: + summary: restore sample + description: 'Auth: basic, levels: maintain, admin' + x-doc: status is set to 0 + tags: + - /sample + security: + - BasicAuth: [] + responses: + 200: + $ref: 'api.yaml#/components/responses/Ok' + 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' + /sample/new: post: summary: add sample diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index 1bbfd5d..b90a722 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -14,7 +14,6 @@ import mongoose from 'mongoose'; // TODO: allow adding sample numbers for existing samples // TODO: Do not allow validation or measurement entry without condition -// TODO: restore sample describe('/sample', () => { let server; @@ -821,6 +820,61 @@ describe('/sample', () => { }); }); + describe('PUT /sample/restore/{id}', () => { + it('sets the status', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/restore/400000000000000000000005', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'OK'}); + SampleModel.findById('400000000000000000000005').lean().exec((err, data: any) => { + if (err) return done(err); + should(data).have.property('status',globals.status.new); + done(); + }); + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/restore/400000000000000000000005', + auth: {key: 'admin'}, + httpStatus: 401, + req: {} + }); + }); + it('rejects a write user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/restore/400000000000000000000005', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {} + }); + }); + it('returns 404 for an unknown sample', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/restore/000000000000000000000005', + auth: {basic: 'admin'}, + httpStatus: 404, + req: {} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/restore/400000000000000000000005', + httpStatus: 401, + req: {} + }); + }); + }); + describe('POST /sample/new', () => { it('returns the right sample', done => { TestHelper.request(server, done, { diff --git a/src/routes/sample.ts b/src/routes/sample.ts index 23e786a..e8ed1f7 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -163,6 +163,19 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => { }); }); +router.put('/sample/restore/' + IdValidate.parameter(), (req, res, next) => { + if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; + + SampleModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).lean().exec((err, data) => { + if (err) return next(err); + + if (!data) { + return res.status(404).json({status: 'Not found'}); + } + res.json({status: 'OK'}); + }); +}); + router.post('/sample/new', async (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; From 52039317e0166f2799c7cd5ddf065d35eddfec6d Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 14:54:52 +0200 Subject: [PATCH 6/7] restore materials --- api/material.yaml | 23 +++++++++++++++ src/routes/material.spec.ts | 57 +++++++++++++++++++++++++++++++++++-- src/routes/material.ts | 13 +++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/api/material.yaml b/api/material.yaml index 51af0ef..967071c 100644 --- a/api/material.yaml +++ b/api/material.yaml @@ -117,6 +117,29 @@ 500: $ref: 'api.yaml#/components/responses/500' +/material/restore/{id}: + parameters: + - $ref: 'api.yaml#/components/parameters/Id' + put: + summary: restore material + description: 'Auth: basic, levels: maintain, admin' + x-doc: status is set to 0 + tags: + - /material + security: + - BasicAuth: [] + responses: + 200: + $ref: 'api.yaml#/components/responses/Ok' + 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' + /material/new: post: summary: add material diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index 330d5b7..56f094e 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -7,8 +7,6 @@ import globals from '../globals'; // TODO: color name must be unique to get color number // TODO: separate supplier/ material name into own collections -// TODO: restore material - describe('/material', () => { let server; before(done => TestHelper.before(done)); @@ -496,6 +494,61 @@ describe('/material', () => { }); }); + describe('PUT /material/restore/{id}', () => { + it('sets the status', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/restore/100000000000000000000008', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'OK'}); + MaterialModel.findById('100000000000000000000008').lean().exec((err, data: any) => { + if (err) return done(err); + should(data).have.property('status',globals.status.new); + done(); + }); + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/restore/100000000000000000000008', + auth: {key: 'admin'}, + httpStatus: 401, + req: {} + }); + }); + it('rejects a write user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/restore/100000000000000000000008', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {} + }); + }); + it('returns 404 for an unknown sample', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/restore/000000000000000000000008', + auth: {basic: 'admin'}, + httpStatus: 404, + req: {} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/restore/100000000000000000000008', + httpStatus: 401, + req: {} + }); + }); + }); + describe('POST /material/new', () => { it('returns the right material', done => { TestHelper.request(server, done, { diff --git a/src/routes/material.ts b/src/routes/material.ts index ffba3ef..1711eb5 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -95,6 +95,19 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { }); }); +router.put('/material/restore/' + IdValidate.parameter(), (req, res, next) => { + if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; + + MaterialModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).lean().exec((err, data) => { + if (err) return next(err); + + if (!data) { + return res.status(404).json({status: 'Not found'}); + } + res.json({status: 'OK'}); + }); +}); + router.post('/material/new', async (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; From d924ee5a8cfc9d1f7644ff4af9de2d5f36c71522 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 28 May 2020 15:03:49 +0200 Subject: [PATCH 7/7] restore measurements --- api/measurement.yaml | 23 ++++++++++++++ src/routes/measurement.spec.ts | 57 ++++++++++++++++++++++++++++++++-- src/routes/measurement.ts | 13 ++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/api/measurement.yaml b/api/measurement.yaml index 9116a8c..3068d97 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -77,6 +77,29 @@ 500: $ref: 'api.yaml#/components/responses/500' +/measurement/restore/{id}: + parameters: + - $ref: 'api.yaml#/components/parameters/Id' + put: + summary: restore measurement + description: 'Auth: basic, levels: maintain, admin' + x-doc: status is set to 0 + tags: + - /measurement + security: + - BasicAuth: [] + responses: + 200: + $ref: 'api.yaml#/components/responses/Ok' + 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' + /measurement/new: post: summary: add measurement diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index 113847f..c27bf63 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -3,8 +3,6 @@ import MeasurementModel from '../models/measurement'; import TestHelper from "../test/helper"; import globals from '../globals'; -// TODO: restore measurements for m/a - describe('/measurement', () => { let server; @@ -367,6 +365,61 @@ describe('/measurement', () => { }); }); + describe('PUT /measurement/restore/{id}', () => { + it('sets the status', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/restore/800000000000000000000004', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'OK'}); + MeasurementModel.findById('800000000000000000000004').lean().exec((err, data: any) => { + if (err) return done(err); + should(data).have.property('status',globals.status.new); + done(); + }); + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/restore/800000000000000000000004', + auth: {key: 'admin'}, + httpStatus: 401, + req: {} + }); + }); + it('rejects a write user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/restore/800000000000000000000004', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {} + }); + }); + it('returns 404 for an unknown sample', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/restore/000000000000000000000004', + auth: {basic: 'admin'}, + httpStatus: 404, + req: {} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/measurement/restore/800000000000000000000004', + httpStatus: 401, + req: {} + }); + }); + }); + describe('POST /measurement/new', () => { it('returns the right measurement', done => { TestHelper.request(server, done, { diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts index ab9d50e..e7f6271 100644 --- a/src/routes/measurement.ts +++ b/src/routes/measurement.ts @@ -78,6 +78,19 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => { }); }); +router.put('/measurement/restore/' + IdValidate.parameter(), (req, res, next) => { + if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; + + MeasurementModel.findByIdAndUpdate(req.params.id, {status: globals.status.new}).lean().exec((err, data) => { + if (err) return next(err); + + if (!data) { + return res.status(404).json({status: 'Not found'}); + } + res.json({status: 'OK'}); + }); +}); + router.post('/measurement/new', async (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;