Archived
2

Merge pull request #11 in ~VLE2FE/dfop-api from condition-merge to develop

* commit '8276e5108c86e92739ffd830b056a3884ce35cf3':
  /api/ subroutes only available in dev/test
  implemented code coverage
  adapted /measurements to use sample_id
  adapted existing /sample methods to condition, removed /condition route
This commit is contained in:
Veit Lukas (PEA4-Fe) 2020-05-28 12:20:09 +02:00
commit dc8828dbeb
39 changed files with 2012 additions and 1451 deletions

View File

@ -4,6 +4,8 @@
<w>bcrypt</w>
<w>cfenv</w>
<w>dfopdb</w>
<w>janedoe</w>
<w>testcomment</w>
</words>
</dictionary>
</component>

View File

@ -1,13 +0,0 @@
<component name="libraryTable">
<library name="dist" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/dist" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/dist" />
</CLASSES>
<SOURCES />
</library>
</component>

View File

@ -54,7 +54,6 @@ tags:
- name: /
- name: /sample
- name: /material
- name: /condition
- name: /measurement
- name: /template
- name: /model
@ -66,7 +65,6 @@ paths:
- $ref: 'others.yaml'
- $ref: 'sample.yaml'
- $ref: 'material.yaml'
- $ref: 'condition.yaml'
- $ref: 'measurement.yaml'
- $ref: 'template.yaml'
- $ref: 'model.yaml'

View File

@ -1,111 +0,0 @@
/condition/{id}:
parameters:
- $ref: 'api.yaml#/components/parameters/Id'
get:
summary: condition by id
description: 'Auth: all, levels: read, write, maintain, dev, admin'
x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
tags:
- /condition
responses:
200:
description: condition details
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/Condition'
400:
$ref: 'api.yaml#/components/responses/400'
401:
$ref: 'api.yaml#/components/responses/401'
404:
$ref: 'api.yaml#/components/responses/404'
500:
$ref: 'api.yaml#/components/responses/500'
put:
summary: change condition
description: 'Auth: basic, levels: write, maintain, dev, admin <br>Only maintain and admin are allowed to reference samples created by another user'
x-doc: status is reset to 0 on any changes
tags:
- /condition
security:
- BasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
properties:
parameters:
type: object
responses:
200:
description: condition details
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/Condition'
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'
delete:
summary: delete condition
description: 'Auth: basic, levels: write, maintain, dev, admin'
x-doc: sets status to -1
tags:
- /condition
security:
- BasicAuth: []
responses:
200:
$ref: 'api.yaml#/components/responses/Ok'
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'
/condition/new:
post:
summary: add condition
description: 'Auth: basic, levels: write, maintain, dev, admin <br>Only maintain and admin are allowed to reference samples created by another user'
x-doc: 'Adds status: 0 automatically'
tags:
- /condition
security:
- BasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/Condition'
responses:
200:
description: condition details
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/Condition'
400:
$ref: 'api.yaml#/components/responses/400'
401:
$ref: 'api.yaml#/components/responses/401'
403:
$ref: 'api.yaml#/components/responses/403'
500:
$ref: 'api.yaml#/components/responses/500'

View File

@ -19,7 +19,7 @@
500:
$ref: 'api.yaml#/components/responses/500'
/samples{group}:
/samples/{group}:
parameters:
- $ref: 'api.yaml#/components/parameters/Group'
get:
@ -48,7 +48,7 @@
get:
summary: TODO sample details
description: 'Auth: all, levels: read, write, maintain, dev, admin'
x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
x-doc: deleted samples are available only for maintain/admin
tags:
- /sample
responses:

View File

@ -24,6 +24,15 @@ SampleProperties:
batch:
type: string
example: 1560237365
condition:
type: object
properties:
condition_template:
$ref: 'api.yaml#/components/schemas/Id'
example:
condition_template: 5ea0450ed851c30a90e70894
material: hot air
weeks: 5
SampleRefs:
allOf:
@ -55,7 +64,7 @@ Sample:
type: array
items:
properties:
id:
sample_id:
$ref: 'api.yaml#/components/schemas/Id'
relation:
type: string
@ -67,7 +76,8 @@ SampleDetail:
- $ref: 'api.yaml#/components/schemas/SampleProperties'
properties:
material:
$ref: 'api.yaml#/components/schemas/Material'
allOf:
- $ref: 'api.yaml#/components/schemas/Material'
notes:
type: object
properties:
@ -77,10 +87,14 @@ SampleDetail:
type: array
items:
$ref: 'api.yaml#/components/schemas/Id'
conditions:
measurements:
type: array
items:
$ref: 'api.yaml#/components/schemas/Condition'
allOf:
- $ref: 'api.yaml#/components/schemas/Measurement'
user:
type: string
example: admin
Material:
allOf:
@ -115,26 +129,11 @@ Material:
type: string
example: 5514263423
Condition:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
properties:
sample_id:
$ref: 'api.yaml#/components/schemas/Id'
number:
type: string
readOnly: true
example: B1
parameters:
type: object
treatment_template:
$ref: 'api.yaml#/components/schemas/Id'
Measurement:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
properties:
condition_id:
sample_id:
$ref: 'api.yaml#/components/schemas/Id'
values:
type: object
@ -166,7 +165,7 @@ Template:
min: 0
max: 2
TreatmentTemplate:
ConditionTemplate:
allOf:
- $ref: 'api.yaml#/components/schemas/Template'
properties:

View File

@ -1,6 +1,6 @@
/template/treatments:
/template/conditions:
get:
summary: all available treatment methods
summary: all available condition methods
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags:
- /template
@ -8,23 +8,23 @@
- BasicAuth: []
responses:
200:
description: list of treatments
description: list of conditions
content:
application/json:
schema:
type: array
items:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate'
$ref: 'api.yaml#/components/schemas/ConditionTemplate'
401:
$ref: 'api.yaml#/components/responses/401'
500:
$ref: 'api.yaml#/components/responses/500'
/template/treatment/{id}:
/template/condition/{id}:
parameters:
- $ref: 'api.yaml#/components/parameters/Id'
get:
summary: treatment method details
summary: condition method details
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /template
@ -32,11 +32,11 @@
- BasicAuth: []
responses:
200:
description: treatment details
description: condition details
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate'
$ref: 'api.yaml#/components/schemas/ConditionTemplate'
401:
$ref: 'api.yaml#/components/responses/401'
404:
@ -44,7 +44,7 @@
500:
$ref: 'api.yaml#/components/responses/500'
put:
summary: change treatment method
summary: change condition 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:
@ -56,14 +56,14 @@
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate'
$ref: 'api.yaml#/components/schemas/ConditionTemplate'
responses:
200:
description: treatment details
description: condition details
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate'
$ref: 'api.yaml#/components/schemas/ConditionTemplate'
400:
$ref: 'api.yaml#/components/responses/400'
401:
@ -75,9 +75,9 @@
500:
$ref: 'api.yaml#/components/responses/500'
/template/treatment/new:
/template/condition/new:
post:
summary: add treatment method
summary: add condition method
description: 'Auth: basic, levels: maintain, admin'
tags:
- /template
@ -88,14 +88,14 @@
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate'
$ref: 'api.yaml#/components/schemas/ConditionTemplate'
responses:
200:
description: treatment details
description: condition details
content:
application/json:
schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate'
$ref: 'api.yaml#/components/schemas/ConditionTemplate'
400:
$ref: 'api.yaml#/components/responses/400'
401:

976
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,12 @@
"main": "index.js",
"scripts": {
"tsc": "tsc",
"tsc-full": "del /q dist\\* & (for /d %x in (dist\\*) do @rd /s /q \"%x\") & tsc",
"test": "mocha dist/**/**.spec.js",
"start": "tsc && node dist/index.js || exit 1",
"dev": "nodemon -e ts,yaml --exec \"npm run start\"",
"loadDev": "node dist/test/loadDev.js"
"loadDev": "node dist/test/loadDev.js",
"coverage": "tsc && nyc --reporter=html --reporter=text mocha dist/**/**.spec.js --timeout 5000"
},
"keywords": [],
"author": "",
@ -44,6 +46,7 @@
"devDependencies": {
"@types/lodash": "^4.14.150",
"mocha": "^7.1.2",
"nyc": "^15.0.1",
"should": "^13.2.3",
"supertest": "^4.0.2"
}

View File

@ -42,15 +42,18 @@ export default class db {
});
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
mongoose.connection.on('disconnected', () => { // reset state on disconnect
console.info('Database disconnected');
this.state.db = 0;
done();
if (process.env.NODE_ENV !== 'test') { // Do not interfere with testing
console.info('Database disconnected');
this.state.db = 0;
}
});
process.on('SIGINT', () => { // close connection when app is terminated
mongoose.connection.close(() => {
console.info('Mongoose default connection disconnected through app termination');
process.exit(0);
});
if (!this.state.db) { // database still connected
mongoose.connection.close(() => {
console.info('Mongoose default connection disconnected through app termination');
process.exit(0);
});
}
});
mongoose.connection.once('open', () => {
mongoose.set('useFindAndModify', false);
@ -60,6 +63,14 @@ export default class db {
});
}
static disconnect (done) {
mongoose.connection.close(() => {
console.info(process.env.NODE_ENV === 'test' ? '' : `Disconnected from database`);
this.state.db = 0;
done();
});
}
static getState () {
return this.state;
}

View File

@ -5,7 +5,13 @@ const globals = {
'maintain',
'dev',
'admin'
]
],
status: { // document statuses
deleted: -1,
new: 0,
validated: 10,
}
};
export default globals;

View File

@ -11,6 +11,9 @@ import db from './db';
// TODO: validation: VZ, Humidity: min/max value, DPT: filename
// TODO: condition values not needed on initial add
// TODO: add multiple samples at once
// TODO: coverage
// TODO: think about the display of deleted/new samples and validation in data and UI
// TODO: improve error coverage
// tell if server is running in debug or production environment
console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
@ -48,14 +51,22 @@ app.use((req, res, next) => { // no database connection error
});
app.use(require('./helpers/authorize')); // handle authentication
// redirect /api routes for Angular proxy in development
if (process.env.NODE_ENV !== 'production') {
app.use('/api/:url', (req, res) => {
req.url = '/' + req.params.url;
app.handle(req, res);
});
}
// require routes
app.use('/api', require('./routes/root'));
app.use('/api', require('./routes/sample'));
app.use('/api', require('./routes/material'));
app.use('/api', require('./routes/template'));
app.use('/api', require('./routes/user'));
app.use('/api', require('./routes/condition'));
app.use('/api', require('./routes/measurement'));
app.use('/', require('./routes/root'));
app.use('/', require('./routes/sample'));
app.use('/', require('./routes/material'));
app.use('/', require('./routes/template'));
app.use('/', require('./routes/user'));
app.use('/', require('./routes/measurement'));
// static files
app.use('/static', express.static('static'));

View File

@ -1,13 +0,0 @@
import mongoose from 'mongoose';
import SampleModel from './sample';
import TreatmentTemplateModel from './treatment_template';
const ConditionSchema = new mongoose.Schema({
sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
number: String,
parameters: mongoose.Schema.Types.Mixed,
treatment_template: {type: mongoose.Schema.Types.ObjectId, ref: TreatmentTemplateModel},
status: Number
});
export default mongoose.model('condition', ConditionSchema);

View File

@ -1,13 +1,12 @@
import mongoose from 'mongoose';
const TreatmentTemplateSchema = new mongoose.Schema({
const ConditionTemplateSchema = new mongoose.Schema({
name: String,
version: Number,
number_prefix: String,
parameters: [{
name: String,
range: mongoose.Schema.Types.Mixed
}]
}, {minimize: false}); // to allow empty objects
export default mongoose.model('treatment_template', TreatmentTemplateSchema);
export default mongoose.model('condition_template', ConditionTemplateSchema);

View File

@ -1,12 +1,14 @@
import mongoose from 'mongoose';
import ConditionModel from './condition';
import SampleModel from './sample';
import MeasurementTemplateModel from './measurement_template';
const MeasurementSchema = new mongoose.Schema({
condition_id: {type: mongoose.Schema.Types.ObjectId, ref: ConditionModel},
sample_id: {type: mongoose.Schema.Types.ObjectId, ref: SampleModel},
values: mongoose.Schema.Types.Mixed,
measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel},
status: Number
});
}, {minimize: false});
export default mongoose.model('measurement', MeasurementSchema);

View File

@ -3,7 +3,7 @@ import mongoose from 'mongoose';
const NoteSchema = new mongoose.Schema({
comment: String,
sample_references: [{
id: mongoose.Schema.Types.ObjectId,
sample_id: mongoose.Schema.Types.ObjectId,
relation: String
}],
custom_fields: mongoose.Schema.Types.Mixed

View File

@ -9,10 +9,11 @@ const SampleSchema = new mongoose.Schema({
type: String,
color: String,
batch: String,
condition: mongoose.Schema.Types.Mixed,
material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel},
status: Number
});
}, {minimize: false});
export default mongoose.model('sample', SampleSchema);

View File

@ -1,583 +0,0 @@
import should from 'should/as-function';
import ConditionModel from '../models/condition';
import TestHelper from "../test/helper";
// TODO: adding conditions allowed only for m/a
// TODO: deleted data only visible for m/a
// TODO: restore deleted
// TODO: remove number_prefix
describe('/condition', () => {
let server;
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
describe('GET /condition/{id}', () => {
it('returns the right condition', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
});
});
it('returns the right condition for an API key', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/condition/700000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 200,
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
});
});
it('rejects an invalid id', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/condition/70000000000t000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404
});
});
it('rejects an unknown id', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/condition/000000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/condition/700000000000000000000001',
httpStatus: 401
});
});
});
describe('PUT /condition{id}', () => {
it('returns the right condition', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {},
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}}
});
});
it('keeps unchanged properties', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {parameters: {material: 'copper', weeks: 3}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}});
ConditionModel.findById('700000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status', 10);
done();
});
});
});
it('keeps only one unchanged parameter', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {parameters: {material: 'copper'}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}});
ConditionModel.findById('700000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status', 10);
done();
});
});
});
it('changes the given properties', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {parameters: {material: 'hot air', weeks: 10}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}});
ConditionModel.findById('700000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
should(data.sample_id.toString()).be.eql('400000000000000000000001');
should(data).have.property('number', 'A1');
should(data.treatment_template.toString()).be.eql('200000000000000000000001');
should(data).have.property('status', 0);
should(data).have.property('parameters');
should(data.parameters).have.property('material', 'hot air');
should(data.parameters).have.property('weeks', 10);
done();
});
});
});
it('allows changing only one parameter', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {parameters: {weeks: 8}},
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 8}}
});
});
it('rejects changing the condition number', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {number: 'C2'},
res: {status: 'Invalid body format', details: '"number" is not allowed'}
});
});
it('rejects not specified parameters', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {parameters: {xx: 13}},
res: {status: 'Invalid body format', details: '"xx" is not allowed'}
});
});
it('rejects a parameter not in the value range', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {parameters: {material: 'xxx'}},
res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'}
});
});
it('rejects a parameter below minimum range', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {parameters: {weeks: -10}},
res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'}
});
});
it('rejects a parameter above maximum range', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {parameters: {weeks: 11}},
res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'}
});
});
it('rejects a new treatment_template', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {treatment_template: '200000000000000000000002'},
res: {status: 'Invalid body format', details: '"treatment_template" is not allowed'}
});
});
it('rejects editing a condition for a write user who did not create this condition', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000003',
auth: {basic: 'janedoe'},
httpStatus: 403,
req: {parameters: {weeks: 8}}
});
});
it('accepts editing a condition of another user for a maintain/admin user', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {parameters: {material: 'hot air', weeks: 10}},
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}}
});
});
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 401,
req: {parameters: {material: 'hot air', weeks: 10}}
});
});
it('rejects requests from a read user', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
auth: {basic: 'user'},
httpStatus: 403,
req: {parameters: {material: 'hot air', weeks: 10}}
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/condition/700000000000000000000001',
httpStatus: 401,
req: {parameters: {material: 'hot air', weeks: 10}}
});
});
});
describe('DELETE /condition/{id}', () => {
it('sets the status to deleted', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000004',
auth: {basic: 'janedoe'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({status: 'OK'});
ConditionModel.findById('700000000000000000000004').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
should(data.sample_id.toString()).be.eql('400000000000000000000001');
should(data).have.property('number', 'A2');
should(data.treatment_template.toString()).be.eql('200000000000000000000001');
should(data).have.property('status', -1);
should(data).have.property('parameters');
should(data.parameters).have.property('material', 'hot air');
should(data.parameters).have.property('weeks', 5);
done();
});
});
});
it('rejects deleting a condition referenced by measurements'/*, done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000002',
auth: {basic: 'janedoe'},
httpStatus: 200,
res: {status: 'Condition still in use'}
});
}*/); // TODO after decision
it('rejects an invalid id', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/70000000000w000000000002',
auth: {basic: 'janedoe'},
httpStatus: 404
});
});
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000004',
auth: {key: 'janedoe'},
httpStatus: 401
});
});
it('rejects requests from a read user', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000004',
auth: {basic: 'user'},
httpStatus: 403
});
});
it('rejects a write user deleting a condition belonging to a sample of another user', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000003',
auth: {basic: 'janedoe'},
httpStatus: 403
});
});
it('accepts an maintain/admin user deleting a condition belonging to a sample of another user', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000004',
auth: {basic: 'admin'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({status: 'OK'});
done();
});
});
it('returns 404 for an unknown id', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/000000000000000000000002',
auth: {basic: 'janedoe'},
httpStatus: 404
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'delete',
url: '/condition/700000000000000000000004',
httpStatus: 401
});
});
});
describe('POST /condition/new', () => {
it('returns the right condition', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
}).end((err, res) => {
if (err) return done(err);
should(res.body).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('sample_id', '400000000000000000000002');
should(res.body).have.property('number', 'A2');
should(res.body).have.property('treatment_template', '200000000000000000000001');
should(res.body).have.property('parameters');
should(res.body.parameters).have.property('material', 'hot air');
should(res.body.parameters).have.property('weeks', 10);
done();
});
});
it('stores the condition', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
}).end((err, res) => {
if (err) return done(err);
ConditionModel.findById(res.body._id).lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
should(data.sample_id.toString()).be.eql('400000000000000000000002');
should(data).have.property('number', 'A2');
should(data.treatment_template.toString()).be.eql('200000000000000000000001');
should(data).have.property('status', 0);
should(data).have.property('parameters');
should(data.parameters).have.property('material', 'hot air');
should(data.parameters).have.property('weeks', 10);
done();
});
});
});
it('stores the first condition as 1', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'admin'},
httpStatus: 200,
req: {sample_id: '400000000000000000000003', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
}).end((err, res) => {
if (err) return done(err);
ConditionModel.findById(res.body._id).lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template', 'status', '__v');
should(data.sample_id.toString()).be.eql('400000000000000000000003');
should(data).have.property('number', 'A1');
should(data.treatment_template.toString()).be.eql('200000000000000000000001');
should(data).have.property('status', 0);
should(data).have.property('parameters');
should(data.parameters).have.property('material', 'hot air');
should(data.parameters).have.property('weeks', 10);
done();
});
});
});
it('rejects an invalid sample id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '4000000000h0000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"sample_id" with value "4000000000h0000000000002" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
it('rejects a sample id not available', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '000000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
res: {status: 'Sample id not available'}
});
});
it('rejects an invalid treatment_template id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000h00000000001'},
res: {status: 'Invalid body format', details: '"treatment_template" with value "200000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
it('rejects a treatment_template which does not exist', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '000000000000000000000001'},
res: {status: 'Treatment template not available'}
});
});
it('rejects setting a condition number', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000001', number: 'A7', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"number" is not allowed'}
});
});
it('rejects not specified parameters', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10, xx: 12}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"xx" is not allowed'}
});
});
it('rejects missing parameters', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air'}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"weeks" is required'}
});
});
it('rejects a parameter not in the value range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'xxx', weeks: 10}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'}
});
});
it('rejects a parameter below minimum range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: -10}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'}
});
});
it('rejects a parameter above maximum range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 11}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'}
});
});
it('rejects a missing sample id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
res: {status: 'Invalid body format', details: '"sample_id" is required'}
});
});
it('rejects a missing treatment_template', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}},
res: {status: 'Invalid body format', details: '"treatment_template" is required'}
});
});
it('rejects adding a condition to the sample of an other user for a write user', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 403,
req: {sample_id: '400000000000000000000003', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
});
});
it('accepts adding a condition to the sample of an other user for a maintain/admin user', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'admin'},
httpStatus: 200,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
}).end((err, res) => {
if (err) return done(err);
should(res.body).have.only.keys('_id', 'sample_id', 'number', 'parameters', 'treatment_template');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('sample_id', '400000000000000000000002');
should(res.body).have.property('number', 'A2');
should(res.body).have.property('treatment_template', '200000000000000000000001');
should(res.body).have.property('parameters');
should(res.body.parameters).have.property('material', 'hot air');
should(res.body.parameters).have.property('weeks', 10);
done();
});
});
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {key: 'janedoe'},
httpStatus: 401,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
});
});
it('rejects requests from a read user', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
auth: {basic: 'user'},
httpStatus: 403,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/condition/new',
httpStatus: 401,
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
});
});
});
});

View File

@ -1,133 +0,0 @@
import express from 'express';
import _ from 'lodash';
import ConditionValidate from './validate/condition';
import ParametersValidate from './validate/parameters';
import res400 from './validate/res400';
import SampleModel from '../models/sample';
import ConditionModel from '../models/condition';
import TreatmentTemplateModel from '../models/treatment_template';
import IdValidate from './validate/id';
const router = express.Router();
router.get('/condition/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
ConditionModel.findById(req.params.id).lean().exec((err, data) => {
if (err) return next(err);
if (data) {
res.json(ConditionValidate.output(data));
}
else {
res.status(404).json({status: 'Not found'});
}
});
});
router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => {
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
const {error, value: condition} = ConditionValidate.input(req.body, 'change');
if (error) return res400(error, res);
const data = await ConditionModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
if (data instanceof Error) return;
if (!data) {
res.status(404).json({status: 'Not found'});
}
// add properties needed for sampleIdCheck
condition.treatment_template = data.treatment_template;
condition.sample_id = data.sample_id;
if (!await sampleIdCheck(condition, req, res, next)) return;
if (condition.parameters) {
condition.parameters = _.assign({}, data.parameters, condition.parameters);
if (!_.isEqual(condition.parameters, data.parameters)) { // parameters did not change
condition.status = 0;
}
}
if (!await treatmentCheck(condition, 'change', res, next)) return;
await ConditionModel.findByIdAndUpdate(req.params.id, condition, {new: true}).lean().exec((err, data) => {
if (err) return next(err);
res.json(ConditionValidate.output(data));
});
});
router.delete('/condition/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
ConditionModel.findById(req.params.id).lean().exec(async (err, data: any) => {
if (err) return next(err);
if (!data) {
res.status(404).json({status: 'Not found'});
}
if (!await sampleIdCheck(data, req, res, next)) return;
await ConditionModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => {
if (err) return next(err);
res.json({status: 'OK'});
});
});
});
router.post('/condition/new', async (req, res, next) => {
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
const {error, value: condition} = ConditionValidate.input(req.body, 'new');
if (error) return res400(error, res);
if (!await sampleIdCheck(condition, req, res, next)) return;
const treatmentData = await treatmentCheck(condition, 'new', res, next)
if (!treatmentData) return;
condition.number = await numberGenerate(condition, treatmentData, next);
if (!condition.number) return;
condition.status = 0; // set status to new
await new ConditionModel(condition).save((err, data) => {
if (err) return next(err);
res.json(ConditionValidate.output(data.toObject()));
});
})
module.exports = router;
async function sampleIdCheck (condition, req, res, next) { // validate sample_id, returns false if invalid
const sampleData = await SampleModel.findById(condition.sample_id).lean().exec().catch(err => {next(err); return false;}) as any;
if (!sampleData) { // sample_id not found
res.status(400).json({status: 'Sample id not available'});
return false
}
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
return true;
}
async function numberGenerate (condition, treatmentData, next) { // generate number, returns false on error
const conditionData = await ConditionModel // find condition with highest number belonging to the same sample
.find({sample_id: condition.sample_id, number: new RegExp('^' + treatmentData.number_prefix + '[0-9]+$', 'm')})
.sort({number: -1})
.limit(1)
.lean()
.exec()
.catch(err => next(err)) as any;
if (conditionData instanceof Error) return false;
return treatmentData.number_prefix + (conditionData.length > 0 ? Number(conditionData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1); // return new number
}
async function treatmentCheck (condition, param, res, next) { // validate treatment template, returns false if invalid, otherwise template data
const treatmentData = await TreatmentTemplateModel.findById(condition.treatment_template).lean().exec().catch(err => next(err)) as any;
if (treatmentData instanceof Error) return false;
if (!treatmentData) { // template not found
res.status(400).json({status: 'Treatment template not available'});
return false;
}
// validate parameters
const {error, value: ignore} = ParametersValidate.input(condition.parameters, treatmentData.parameters, param);
if (error) {res400(error, res); return false;}
return treatmentData;
}

View File

@ -2,6 +2,7 @@ import should from 'should/as-function';
import _ from 'lodash';
import MaterialModel from '../models/material';
import TestHelper from "../test/helper";
import globals from '../globals';
// TODO: color name must be unique to get color number
// TODO: separate supplier/ material name into own collections
@ -11,6 +12,7 @@ describe('/material', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
describe('GET /materials', () => {
it('returns all materials', done => {
@ -22,7 +24,7 @@ describe('/material', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length);
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.validated).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@ -50,7 +52,7 @@ describe('/material', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length);
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === globals.status.validated).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@ -89,7 +91,7 @@ describe('/material', () => {
if (err) return done(err);
const json = require('../test/db.json');
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 0).length);
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status ===globals.status.new).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@ -105,7 +107,7 @@ describe('/material', () => {
should(number).have.property('number').be.type('string');
});
MaterialModel.findById(material._id).lean().exec((err, data) => {
should(data).have.property('status', 0);
should(data).have.property('status',globals.status.new);
if (--asyncCounter === 0) {
done();
}
@ -123,7 +125,7 @@ describe('/material', () => {
if (err) return done(err);
const json = require('../test/db.json');
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === -1).length);
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status ===globals.status.deleted).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string');
@ -139,13 +141,12 @@ describe('/material', () => {
should(number).have.property('number').be.type('string');
});
MaterialModel.findById(material._id).lean().exec((err, data) => {
should(data).have.property('status', -1);
should(data).have.property('status',globals.status.deleted);
if (--asyncCounter === 0) {
done();
}
});
});
done();
});
});
it('rejects requests from a write user', done => {
@ -249,7 +250,7 @@ describe('/material', () => {
should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
done();
});
});
@ -266,7 +267,7 @@ describe('/material', () => {
should(res.body).be.eql({_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}, {color: 'natural', number: '5514263422'}]});
MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
done();
});
});
@ -513,7 +514,7 @@ describe('/material', () => {
should(data[0]).have.property('mineral', '0');
should(data[0]).have.property('glass_fiber', '30');
should(data[0]).have.property('carbon_fiber', '0');
should(data[0]).have.property('status', 0);
should(data[0]).have.property('status',globals.status.new);
should(data[0].numbers).have.lengthOf(0);
done();
});
@ -552,7 +553,7 @@ describe('/material', () => {
should(data[0]).have.property('mineral', '0');
should(data[0]).have.property('glass_fiber', '30');
should(data[0]).have.property('carbon_fiber', '0');
should(data[0]).have.property('status', 0);
should(data[0]).have.property('status',globals.status.new);
should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
done();
});

View File

@ -7,6 +7,7 @@ import SampleModel from '../models/sample';
import IdValidate from './validate/id';
import res400 from './validate/res400';
import mongoose from 'mongoose';
import globals from '../globals';
@ -15,7 +16,7 @@ const router = express.Router();
router.get('/materials', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
MaterialModel.find({status: 10}).lean().exec((err, data) => {
MaterialModel.find({status:globals.status.validated}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors
});
@ -24,14 +25,7 @@ router.get('/materials', (req, res, next) => {
router.get('/materials/:group(new|deleted)', (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
let status;
switch (req.params.group) {
case 'new': status = 0;
break;
case 'deleted': status = -1;
break;
}
MaterialModel.find({status: status}).lean().exec((err, data) => {
MaterialModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors
});
@ -67,7 +61,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
// check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) {
material.status = 0; // set status to new
material.status = globals.status.new; // set status to new
}
await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
@ -86,7 +80,7 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
if (data.length) {
return res.status(400).json({status: 'Material still in use'});
}
MaterialModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec((err, data) => {
MaterialModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec((err, data) => {
if (err) return next(err);
if (data) {
res.json({status: 'OK'});
@ -106,7 +100,7 @@ router.post('/material/new', async (req, res, next) => {
if (!await nameCheck(material, res, next)) return;
material.status = 0; // set status to new
material.status = globals.status.new; // set status to new
await new MaterialModel(material).save((err, data) => {
if (err) return next(err);
res.json(MaterialValidate.output(data.toObject()));

View File

@ -1,8 +1,9 @@
import should from 'should/as-function';
import MeasurementModel from '../models/measurement';
import TestHelper from "../test/helper";
import globals from '../globals';
// TODO: allow empty values
// TODO: restore measurements for m/a
describe('/measurement', () => {
@ -10,15 +11,16 @@ describe('/measurement', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
describe('GET /mesurement/{id}', () => {
describe('GET /measurement/{id}', () => {
it('returns the right measurement', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/measurement/800000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
res: {_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
});
});
it('returns the measurement for an API key', done => {
@ -27,7 +29,24 @@ describe('/measurement', () => {
url: '/measurement/800000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 200,
res: {_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
});
});
it('returns deleted measurements for a maintain/admin user', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/measurement/800000000000000000000004',
auth: {basic: 'admin'},
httpStatus: 200,
res: {_id: '800000000000000000000004', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003'}
});
});
it('rejects requests for deleted measurements from a write user', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/measurement/800000000000000000000004',
auth: {basic: 'janedoe'},
httpStatus: 403
});
});
it('rejects an invalid id', done => {
@ -63,7 +82,7 @@ describe('/measurement', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {},
res: {_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'}
});
});
it('keeps unchanged values', done => {
@ -75,10 +94,10 @@ describe('/measurement', () => {
req: {values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'});
should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
done();
});
});
@ -92,10 +111,10 @@ describe('/measurement', () => {
req: {values: {'weight %': 0.5}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.5, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'});
should(res.body).be.eql({_id: '800000000000000000000002', sample_id: '400000000000000000000002', values: {'weight %': 0.5, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'});
MeasurementModel.findById('800000000000000000000002').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
done();
});
});
@ -109,12 +128,12 @@ describe('/measurement', () => {
req: {values: {dpt: [[1,2],[3,4],[5,6]]}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '800000000000000000000001', condition_id: '700000000000000000000001', values: {dpt: [[1,2],[3,4],[5,6]]}, measurement_template: '300000000000000000000001'});
should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[1,2],[3,4],[5,6]]}, measurement_template: '300000000000000000000001'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => {
should(data).have.only.keys('_id', 'condition_id', 'values', 'measurement_template', 'status', '__v');
should(data.condition_id.toString()).be.eql('700000000000000000000001');
should(data).have.only.keys('_id', 'sample_id', 'values', 'measurement_template', 'status', '__v');
should(data.sample_id.toString()).be.eql('400000000000000000000001');
should(data.measurement_template.toString()).be.eql('300000000000000000000001');
should(data).have.property('status', 0);
should(data).have.property('status',globals.status.new);
should(data).have.property('values');
should(data.values).have.property('dpt', [[1,2],[3,4],[5,6]]);
done();
@ -128,7 +147,17 @@ describe('/measurement', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {values: {'weight %': 0.9}},
res: {_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'}
res: {_id: '800000000000000000000002', sample_id: '400000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'}
});
});
it('allows keeping empty values empty', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/measurement/800000000000000000000005',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {values: {'weight %': 0.9}},
res: {_id: '800000000000000000000005', sample_id: '400000000000000000000002', values: {'weight %': 0.9, 'standard deviation': null}, measurement_template: '300000000000000000000002'}
});
});
it('rejects not specified values', done => {
@ -148,7 +177,7 @@ describe('/measurement', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {values: {val1: 4}},
res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3]'}
res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3, null]'}
});
});
it('rejects a value below minimum range', done => {
@ -181,6 +210,16 @@ describe('/measurement', () => {
res: {status: 'Invalid body format', details: '"measurement_template" is not allowed'}
});
});
it('rejects a new sample id', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/measurement/800000000000000000000002',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {values: {'weight %': 0.9, 'standard deviation': 0.3}, sample_id: '400000000000000000000002'},
res: {status: 'Invalid body format', details: '"sample_id" is not allowed'}
});
});
it('rejects editing a measurement for a write user who did not create this measurement', done => {
TestHelper.request(server, done, {
method: 'put',
@ -197,7 +236,7 @@ describe('/measurement', () => {
auth: {basic: 'admin'},
httpStatus: 200,
req: {values: {'weight %': 0.9, 'standard deviation': 0.3}},
res: {_id: '800000000000000000000002', condition_id: '700000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.3}, measurement_template: '300000000000000000000002'}
res: {_id: '800000000000000000000002', sample_id: '400000000000000000000002', values: {'weight %': 0.9, 'standard deviation': 0.3}, measurement_template: '300000000000000000000002'}
});
});
it('rejects an invalid id', done => {
@ -256,7 +295,7 @@ describe('/measurement', () => {
should(res.body).be.eql({status: 'OK'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status', -1);
should(data).have.property('status',globals.status.deleted);
done();
});
});
@ -326,12 +365,12 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
should(res.body).have.only.keys('_id', 'condition_id', 'values', 'measurement_template');
should(res.body).have.only.keys('_id', 'sample_id', 'values', 'measurement_template');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('condition_id', '700000000000000000000001');
should(res.body).have.property('sample_id', '400000000000000000000001');
should(res.body).have.property('measurement_template', '300000000000000000000002');
should(res.body).have.property('values');
should(res.body.values).have.property('weight %', 0.8);
@ -345,13 +384,13 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
MeasurementModel.findById(res.body._id).lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.only.keys('_id', 'condition_id', 'values', 'measurement_template', 'status', '__v');
should(data.condition_id.toString()).be.eql('700000000000000000000001');
should(data).have.only.keys('_id', 'sample_id', 'values', 'measurement_template', 'status', '__v');
should(data.sample_id.toString()).be.eql('400000000000000000000001');
should(data.measurement_template.toString()).be.eql('300000000000000000000002');
should(data).have.property('status', 0);
should(data).have.property('values');
@ -361,24 +400,24 @@ describe('/measurement', () => {
});
});
});
it('rejects an invalid condition id', done => {
it('rejects an invalid sample id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000h00000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"condition_id" with value "700000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
req: {sample_id: '400000000000h00000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"sample_id" with value "400000000000h00000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
it('rejects a condition id not available', done => {
it('rejects a sample id not available', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '000000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Condition id not available'}
req: {sample_id: '000000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Sample id not available'}
});
});
it('rejects an invalid measurement_template id', done => {
@ -387,7 +426,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '30000000000h000000000002'},
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '30000000000h000000000002'},
res: {status: 'Invalid body format', details: '"measurement_template" with value "30000000000h000000000002" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
@ -397,7 +436,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '000000000000000000000002'},
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '000000000000000000000002'},
res: {status: 'Measurement template not available'}
});
});
@ -407,18 +446,27 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1, xx: 44}, measurement_template: '300000000000000000000002'},
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1, xx: 44}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"xx" is not allowed'}
});
});
it('rejects missing values', done => {
it('accepts missing values', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"standard deviation" is required'}
httpStatus: 200,
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
should(res.body).have.only.keys('_id', 'sample_id', 'values', 'measurement_template');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('sample_id', '400000000000000000000001');
should(res.body).have.property('measurement_template', '300000000000000000000002');
should(res.body).have.property('values');
should(res.body.values).have.property('weight %', 0.8);
should(res.body.values).have.property('standard deviation', null);
done();
});
});
it('rejects a value not in the value range', done => {
@ -427,8 +475,8 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {val1: 4}, measurement_template: '300000000000000000000003'},
res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3]'}
req: {sample_id: '400000000000000000000001', values: {val1: 4}, measurement_template: '300000000000000000000003'},
res: {status: 'Invalid body format', details: '"val1" must be one of [1, 2, 3, null]'}
});
});
it('rejects a value below minimum range', done => {
@ -437,7 +485,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': -1, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
req: {sample_id: '400000000000000000000001', values: {'weight %': -1, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"weight %" must be larger than or equal to 0'}
});
});
@ -447,18 +495,18 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 2}, measurement_template: '300000000000000000000002'},
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 2}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"standard deviation" must be less than or equal to 0.5'}
});
});
it('rejects a missing condition id', done => {
it('rejects a missing sample id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'},
res: {status: 'Invalid body format', details: '"condition_id" is required'}
res: {status: 'Invalid body format', details: '"sample_id" is required'}
});
});
it('rejects a missing measurement_template', done => {
@ -467,7 +515,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}},
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}},
res: {status: 'Invalid body format', details: '"measurement_template" is required'}
});
});
@ -477,7 +525,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'janedoe'},
httpStatus: 403,
req: {condition_id: '700000000000000000000003', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000003', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
it('accepts adding a measurement to the sample of another user for a maintain/admin user', done => {
@ -486,12 +534,12 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'admin'},
httpStatus: 200,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
}).end((err, res) => {
if (err) return done(err);
should(res.body).have.only.keys('_id', 'condition_id', 'values', 'measurement_template');
should(res.body).have.only.keys('_id', 'sample_id', 'values', 'measurement_template');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('condition_id', '700000000000000000000001');
should(res.body).have.property('sample_id', '400000000000000000000001');
should(res.body).have.property('measurement_template', '300000000000000000000002');
should(res.body).have.property('values');
should(res.body.values).have.property('weight %', 0.8);
@ -505,7 +553,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {key: 'janedoe'},
httpStatus: 401,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
it('rejects requests from a read user', done => {
@ -514,7 +562,7 @@ describe('/measurement', () => {
url: '/measurement/new',
auth: {basic: 'user'},
httpStatus: 403,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
it('rejects unauthorized requests', done => {
@ -522,7 +570,7 @@ describe('/measurement', () => {
method: 'post',
url: '/measurement/new',
httpStatus: 401,
req: {condition_id: '700000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
req: {sample_id: '400000000000000000000001', values: {'weight %': 0.8, 'standard deviation': 0.1}, measurement_template: '300000000000000000000002'}
});
});
});

View File

@ -2,12 +2,13 @@ import express from 'express';
import _ from 'lodash';
import MeasurementModel from '../models/measurement';
import ConditionModel from '../models/condition';
import MeasurementTemplateModel from '../models/measurement_template';
import SampleModel from '../models/sample';
import MeasurementValidate from './validate/measurement';
import IdValidate from './validate/id';
import res400 from './validate/res400';
import ParametersValidate from './validate/parameters';
import globals from '../globals';
const router = express.Router();
@ -15,11 +16,12 @@ const router = express.Router();
router.get('/measurement/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
MeasurementModel.findById(req.params.id).lean().exec((err, data) => {
MeasurementModel.findById(req.params.id).lean().exec((err, data: any) => {
if (err) return next(err);
if (!data) {
return res.status(404).json({status: 'Not found'});
}
if (data.status ===globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted measurements only available for maintain/admin
res.json(MeasurementValidate.output(data));
});
@ -34,19 +36,19 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
const data = await MeasurementModel.findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
if (data instanceof Error) return;
if (!data) {
res.status(404).json({status: 'Not found'});
return res.status(404).json({status: 'Not found'});
}
// add properties needed for conditionIdCheck
// add properties needed for sampleIdCheck
measurement.measurement_template = data.measurement_template;
measurement.condition_id = data.condition_id;
if (!await conditionIdCheck(measurement, req, res, next)) return;
measurement.sample_id = data.sample_id;
if (!await sampleIdCheck(measurement, req, res, next)) return;
// check for changes
if (measurement.values) {
if (measurement.values) { // fill not changed values from database
measurement.values = _.assign({}, data.values, measurement.values);
if (!_.isEqual(measurement.values, data.values)) {
measurement.status = 0; // set status to new
measurement.status = globals.status.new; // set status to new
}
}
@ -63,12 +65,12 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => {
MeasurementModel.findById(req.params.id).lean().exec(async (err, data) => {
if (err) return next(err);
if (!data) {
res.status(404).json({status: 'Not found'});
return res.status(404).json({status: 'Not found'});
}
if (!await conditionIdCheck(data, req, res, next)) return;
await MeasurementModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => {
if (!await sampleIdCheck(data, req, res, next)) return;
await MeasurementModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => {
if (err) return next(err);
res.json({status: 'OK'});
return res.json({status: 'OK'});
});
});
});
@ -79,8 +81,9 @@ router.post('/measurement/new', async (req, res, next) => {
const {error, value: measurement} = MeasurementValidate.input(req.body, 'new');
if (error) return res400(error, res);
if (!await conditionIdCheck(measurement, req, res, next)) return;
if (!await templateCheck(measurement, 'new', res, next)) return;
if (!await sampleIdCheck(measurement, req, res, next)) return;
measurement.values = await templateCheck(measurement, 'new', res, next);
if (!measurement.values) return;
measurement.status = 0;
await new MeasurementModel(measurement).save((err, data) => {
@ -93,25 +96,38 @@ router.post('/measurement/new', async (req, res, next) => {
module.exports = router;
async function conditionIdCheck (measurement, req, res, next) { // validate condition_id, returns false if invalid
const sampleData = await ConditionModel.findById(measurement.condition_id).populate('sample_id').lean().exec().catch(err => {next(err); return false;}) as any;
async function sampleIdCheck (measurement, req, res, next) { // validate sample_id, returns false if invalid or user has no access for this sample
const sampleData = await SampleModel.findById(measurement.sample_id).lean().exec().catch(err => {next(err); return false;}) as any;
if (!sampleData) { // sample_id not found
res.status(400).json({status: 'Condition id not available'});
res.status(400).json({status: 'Sample id not available'});
return false
}
if (sampleData.sample_id.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
return true;
}
async function templateCheck (measurement, param, res, next) { // validate measurement_template and values, param for new/change
async function templateCheck (measurement, param, res, next) { // validate measurement_template and values, returns values, true if values are {} or false if invalid, param for 'new'/'change'
const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template).lean().exec().catch(err => {next(err); return false;}) as any;
if (!templateData) { // template not found
res.status(400).json({status: 'Measurement template not available'});
return false
}
// fill not given values for new measurements
if (param === 'new') {
if (Object.keys(measurement.values).length === 0) {
res.status(400).json({status: 'At least one value is required'});
return false
}
const fillValues = {}; // initialize not given values with null
templateData.parameters.forEach(parameter => {
fillValues[parameter.name] = null;
});
measurement.values = _.assign({}, fillValues, measurement.values);
}
// validate values
const {error, value: ignore} = ParametersValidate.input(measurement.values, templateData.parameters, param);
const {error, value} = ParametersValidate.input(measurement.values, templateData.parameters, 'null');
if (error) {res400(error, res); return false;}
return true;
return value || true;
}

View File

@ -1,4 +1,5 @@
import TestHelper from "../test/helper";
import db from '../db';
describe('/', () => {
@ -6,6 +7,7 @@ describe('/', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
describe('GET /', () => {
it('returns the root message', done => {
@ -40,7 +42,15 @@ describe('/', () => {
TestHelper.request(server, done, {
method: 'get',
url: '/authorized',
auth: {name: 'admin', pass: 'Abc123!!'},
auth: {basic: {name: 'admin', pass: 'Abc123!!'}},
httpStatus: 401
});
});
it('does not work with incorrect username', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/authorized',
auth: {basic: {name: 'adminxx', pass: 'Abc123!!'}},
httpStatus: 401
});
});
@ -66,4 +76,65 @@ describe('/', () => {
});
});
});
describe('An invalid JSON body', () => {
it('is rejected', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/',
httpStatus: 400,
reqType: 'json',
req: '{"xxx"}',
res: {status: 'Invalid JSON body'}
});
});
});
describe('A not connected database', () => { // RUN AS LAST OR RECONNECT DATABASE!!
it('resolves to an 500 error', done => {
db.disconnect(() => {
TestHelper.request(server, done, {
method: 'get',
url: '/',
httpStatus: 500
});
});
});
});
});
describe('The /api/{url} redirect', () => {
let server;
let counter = 0; // count number of current test method
before(done => {
process.env.port = '2999';
db.connect('test', done);
});
beforeEach(done => {
process.env.NODE_ENV = counter === 1 ? 'production' : 'test';
counter ++;
server = TestHelper.beforeEach(server, done);
});
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
it('returns the right method', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/api/authorized',
auth: {basic: 'admin'},
httpStatus: 200,
res: {status: 'Authorization successful', method: 'basic'}
});
});
it('is disabled in production', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/api/authorized',
auth: {basic: 'admin'},
httpStatus: 404
});
});
});

View File

@ -3,16 +3,23 @@ import SampleModel from '../models/sample';
import NoteModel from '../models/note';
import NoteFieldModel from '../models/note_field';
import TestHelper from "../test/helper";
import globals from '../globals';
// TODO: generate output for ML in format DPT -> data, implement filtering, field selection
// 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
describe('/sample', () => {
let server;
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
describe('GET /samples', () => {
it('returns all samples', done => {
@ -24,14 +31,16 @@ describe('/sample', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status ===globals.status.validated).length);
should(res.body).matchEach(sample => {
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
should(sample).have.property('condition').be.type('object');
should(sample.condition).have.property('condition_template').be.type('string');
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
@ -48,17 +57,19 @@ describe('/sample', () => {
}).end((err, res) => {
if (err) return done(err);
const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
should(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
should(material).have.property('_id').be.type('string');
should(material).have.property('number').be.type('string');
should(material).have.property('type').be.type('string');
should(material).have.property('color').be.type('string');
should(material).have.property('batch').be.type('string');
should(material).have.property('material_id').be.type('string');
should(material).have.property('note_id');
should(material).have.property('user_id').be.type('string');
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status ===globals.status.validated).length);
should(res.body).matchEach(sample => {
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
should(sample).have.property('condition').be.type('object');
should(sample.condition).have.property('condition_template').be.type('string');
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
});
done();
});
@ -83,25 +94,28 @@ describe('/sample', () => {
if (err) return done(err);
const json = require('../test/db.json');
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 0).length);
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status ===globals.status.new).length);
should(res.body).matchEach(sample => {
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
should(sample).have.property('condition').be.type('object');
if (Object.keys(sample.condition).length > 0) {
should(sample.condition).have.property('condition_template').be.type('string');
}
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
SampleModel.findById(sample._id).lean().exec((err, data) => {
should(data).have.property('status', 0);
should(data).have.property('status',globals.status.new);
if (--asyncCounter === 0) {
done();
}
});
});
done();
});
});
it('returns all deleted samples', done => {
@ -116,23 +130,26 @@ describe('/sample', () => {
let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === -1).length);
should(res.body).matchEach(sample => {
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(sample).have.property('_id').be.type('string');
should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').be.type('string');
should(sample).have.property('condition').be.type('object');
should(sample.condition).have.property('condition_template').be.type('string');
should(sample.condition).have.property('condition_template').be.type('string');
should(sample.condition).have.property('condition_template').be.type('string');
should(sample).have.property('material_id').be.type('string');
should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string');
SampleModel.findById(sample._id).lean().exec((err, data) => {
should(data).have.property('status', -1);
should(data).have.property('status',globals.status.deleted);
if (--asyncCounter === 0) {
done();
}
});
});
done();
});
});
it('rejects requests from a write user', done => {
@ -160,6 +177,73 @@ describe('/sample', () => {
});
});
describe('GET /sample/{id}', () => {
it('returns the right sample', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/sample/400000000000000000000003',
auth: {basic: 'janedoe'},
httpStatus: 200,
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',
url: '/sample/400000000000000000000003',
auth: {key: 'janedoe'},
httpStatus: 200,
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',
url: '/sample/400000000000000000000005',
auth: {basic: 'admin'},
httpStatus: 200,
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',
url: '/sample/400000000000000000000005',
auth: {basic: 'janedoe'},
httpStatus: 403
});
});
it('returns 404 for an unknown sample', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/sample/000000000000000000000005',
auth: {basic: 'janedoe'},
httpStatus: 404
});
});
it('rejects an invalid id', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/sample/400000000h00000000000005',
auth: {basic: 'janedoe'},
httpStatus: 404
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/sample/400000000000000000000005',
httpStatus: 401
});
});
});
describe('PUT /sample/{id}', () => {
it('returns the right sample', done => {
TestHelper.request(server, done, {
@ -168,7 +252,7 @@ describe('/sample', () => {
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {},
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
});
});
it('keeps unchanged properties', done => {
@ -177,21 +261,22 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}}
req: {type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', notes: {}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '1');
should(data).have.property('color', 'black');
should(data).have.property('type', 'granulate');
should(data).have.property('batch', '');
should(data).have.property('condition', {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'});
should(data.material_id.toString()).be.eql('100000000000000000000004');
should(data.user_id.toString()).be.eql('000000000000000000000002');
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
should(data).have.property('note_id', null);
done();
});
@ -206,10 +291,27 @@ describe('/sample', () => {
req: {type: 'granulate'}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
done();
});
});
});
it('keeps an unchanged condition', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}}
}).end((err, res) => {
if (err) return done(err);
should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
should(data).have.property('status',globals.status.validated);
done();
});
});
@ -223,18 +325,21 @@ describe('/sample', () => {
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'});
should(res.body).be.eql({_id: '400000000000000000000002', number: '21', type: 'granulate', color: 'natural', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, 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.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', '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.condition).have.property('material', 'copper');
should(data.condition).have.property('weeks', 3);
should(data.condition.condition_template.toString()).be.eql('200000000000000000000001');
should(data.material_id.toString()).be.eql('100000000000000000000001');
should(data.user_id.toString()).be.eql('000000000000000000000002');
should(data).have.property('status', 10);
should(data).have.property('status',globals.status.validated);
should(data.note_id.toString()).be.eql('500000000000000000000001');
done();
});
@ -246,20 +351,21 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {type: 'part', color: 'signalviolet', batch: '114531', condition: {condition_template: '200000000000000000000003'}, material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end(err => {
if (err) return done (err);
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err);
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '1');
should(data).have.property('color', 'signalviolet');
should(data).have.property('type', 'part');
should(data).have.property('batch', '114531');
should(data).have.property('condition', {condition_template: '200000000000000000000003'});
should(data.material_id.toString()).be.eql('100000000000000000000002');
should(data.user_id.toString()).be.eql('000000000000000000000002');
should(data).have.property('status', 0);
should(data).have.property('status',globals.status.new);
should(data).have.property('note_id');
NoteModel.findById(data.note_id).lean().exec((err, data: any) => {
if (err) return done (err);
@ -267,7 +373,7 @@ describe('/sample', () => {
should(data).have.property('comment', 'Testcomment');
should(data).have.property('sample_references');
should(data.sample_references).have.lengthOf(1);
should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0].sample_id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0]).have.property('relation', 'part to this sample');
done();
});
@ -350,7 +456,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Color not available for material'}
});
});
@ -360,7 +466,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '000000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '000000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Material not available'}
});
});
@ -370,7 +476,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {number: 25, type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {number: 25, type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"number" is not allowed'}
});
});
@ -380,7 +486,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '000000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Sample reference not available'}
});
});
@ -390,7 +496,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
@ -400,7 +506,87 @@ describe('/sample', () => {
url: '/sample/10000000000h000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
it('rejects not specified condition parameters', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {material: 'copper', weeks: 3, xxx: 44, condition_template: '200000000000000000000001'}},
res: {status: 'Invalid body format', details: '"xxx" is not allowed'}
});
});
it('rejects a condition parameter not in the value range', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {material: 'xx', weeks: 3, condition_template: '200000000000000000000001'}},
res: {status: 'Invalid body format', details: '"material" must be one of [copper, hot air]'}
});
});
it('rejects a condition parameter below minimum range', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {material: 'copper', weeks: 0, condition_template: '200000000000000000000001'}},
res: {status: 'Invalid body format', details: '"weeks" must be larger than or equal to 1'}
});
});
it('rejects a condition parameter above maximum range', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {material: 'copper', weeks: 10.5, condition_template: '200000000000000000000001'}},
res: {status: 'Invalid body format', details: '"weeks" must be less than or equal to 10'}
});
});
it('rejects an invalid condition template', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {material: 'copper', weeks: 3, condition_template: '200000000000h00000000001'}},
res: {status: 'Condition template not available'}
});
});
it('rejects an unknown condition template', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {material: 'copper', weeks: 3, condition_template: '000000000000000000000001'}},
res: {status: 'Condition template not available'}
});
});
it('allows keeping an empty condition empty', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000006',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {condition: {}},
res: {_id: '400000000000000000000006', number: 'Rng36', type: 'granulate', color: 'black', batch: '', condition: {}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
});
});
it('rejects an changing back to an empty condition', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {condition: {}},
res: {status: 'Condition template not available'}
});
});
it('rejects an API key', done => {
@ -409,7 +595,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 401,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
it('rejects changes for samples from another user for a write user', done => {
@ -428,7 +614,7 @@ describe('/sample', () => {
auth: {basic: 'admin'},
httpStatus: 200,
req: {},
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', condition: {condition_template: '200000000000000000000001', material: 'copper', weeks: 3}, material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}
});
});
it('rejects requests from a read user', done => {
@ -437,7 +623,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001',
auth: {basic: 'user'},
httpStatus: 403,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
it('returns 404 for an unknown sample', done => {
@ -446,7 +632,7 @@ describe('/sample', () => {
url: '/sample/000000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
})
it('rejects unauthorized requests', done => {
@ -454,7 +640,7 @@ describe('/sample', () => {
method: 'put',
url: '/sample/400000000000000000000001',
httpStatus: 401,
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
});
});
});
@ -471,15 +657,18 @@ describe('/sample', () => {
should(res.body).be.eql({status: 'OK'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data).have.property('_id');
should(data).have.property('number', '1');
should(data).have.property('color', 'black');
should(data).have.property('type', 'granulate');
should(data).have.property('batch', '');
should(data.condition).have.property('material', 'copper');
should(data.condition).have.property('weeks', 3);
should(data.condition.condition_template.toString()).be.eql('200000000000000000000001');
should(data.material_id.toString()).be.eql('100000000000000000000004');
should(data.user_id.toString()).be.eql('000000000000000000000002');
should(data).have.property('status', -1);
should(data).have.property('status',globals.status.deleted);
should(data).have.property('note_id', null);
done();
});
@ -536,7 +725,7 @@ describe('/sample', () => {
NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
if (err) return done(err);
should(data).have.property('sample_references').with.lengthOf(1);
should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0].sample_id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0]).have.property('relation', 'part to sample');
done();
});
@ -555,7 +744,7 @@ describe('/sample', () => {
should(res.body).be.eql({status: 'OK'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data) => {
if (err) return done(err);
should(data).have.property('status', -1);
should(data).have.property('status',globals.status.deleted);
done();
});
});
@ -617,15 +806,16 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end((err, res) => {
if (err) return done (err);
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('number', 'Rng34');
should(res.body).have.property('number', 'Rng37');
should(res.body).have.property('color', 'black');
should(res.body).have.property('type', 'granulate');
should(res.body).have.property('batch', '1560237365');
should(res.body).have.property('condition', {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'});
should(res.body).have.property('material_id', '100000000000000000000001');
should(res.body).have.property('note_id').be.type('string');
should(res.body).have.property('user_id', '000000000000000000000002');
@ -638,21 +828,22 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end(err => {
if (err) return done (err);
SampleModel.find({number: 'Rng34'}).lean().exec((err, data: any) => {
SampleModel.find({number: 'Rng37'}).lean().exec((err, data: any) => {
if (err) return done (err);
should(data).have.lengthOf(1);
should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id', 'status', '__v');
should(data[0]).have.property('_id');
should(data[0]).have.property('number', 'Rng34');
should(data[0]).have.property('number', 'Rng37');
should(data[0]).have.property('color', 'black');
should(data[0]).have.property('type', 'granulate');
should(data[0]).have.property('batch', '1560237365');
should(data[0]).have.property('condition', {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'});
should(data[0].material_id.toString()).be.eql('100000000000000000000001');
should(data[0].user_id.toString()).be.eql('000000000000000000000002');
should(data[0]).have.property('status', 0);
should(data[0]).have.property('status',globals.status.new);
should(data[0]).have.property('note_id');
NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => {
if (err) return done (err);
@ -660,7 +851,7 @@ describe('/sample', () => {
should(data).have.property('comment', 'Testcomment');
should(data).have.property('sample_references');
should(data.sample_references).have.lengthOf(1);
should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0].sample_id.toString()).be.eql('400000000000000000000003');
should(data.sample_references[0]).have.property('relation', 'part to this sample');
done();
});
@ -710,10 +901,10 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'johnnydoe'},
httpStatus: 200,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end((err, res) => {
if (err) return done (err);
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('number', 'Fe1');
should(res.body).have.property('color', 'black');
@ -725,13 +916,35 @@ describe('/sample', () => {
done();
});
});
it('accepts a sample without condition', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 200,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
}).end((err, res) => {
if (err) return done (err);
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'condition', 'material_id', 'note_id', 'user_id');
should(res.body).have.property('_id').be.type('string');
should(res.body).have.property('number', 'Rng37');
should(res.body).have.property('color', 'black');
should(res.body).have.property('type', 'granulate');
should(res.body).have.property('batch', '1560237365');
should(res.body).have.property('condition', {});
should(res.body).have.property('material_id', '100000000000000000000001');
should(res.body).have.property('note_id').be.type('string');
should(res.body).have.property('user_id', '000000000000000000000002');
done();
});
});
it('rejects a color not defined for the material', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Color not available for material'}
});
});
@ -741,7 +954,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '000000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '000000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Material not available'}
});
});
@ -751,7 +964,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {number: 'Rng34', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {number: 'Rng34', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"number" is not allowed'}
});
});
@ -761,17 +974,97 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '000000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Sample reference not available'}
});
});
it('rejects an invalid condition_template id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '20000h000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects a not existing condition_template id', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, condition_template: '000000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects not specified condition parameters', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3, xxx: 23, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects missing condition parameters', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects condition parameters not in the value range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'xxx', weeks: 3, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects a condition parameter below minimum range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 0, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects a condition parameter above maximum range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 11, condition_template: '20000000000000000000001'}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects a condition without condition template', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', condition: {material: 'copper', weeks: 3}, material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Condition template not available'}
});
});
it('rejects a missing color', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"color" is required'}
});
});
@ -781,7 +1074,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"type" is required'}
});
});
@ -791,7 +1084,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"batch" is required'}
});
});
@ -801,7 +1094,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"material_id" is required'}
});
});
@ -811,7 +1104,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'janedoe'},
httpStatus: 400,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}},
res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
});
});
@ -821,7 +1114,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {key: 'janedoe'},
httpStatus: 401,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
});
it('rejects requests from a read user', done => {
@ -830,7 +1123,7 @@ describe('/sample', () => {
url: '/sample/new',
auth: {basic: 'user'},
httpStatus: 403,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
});
it('rejects unauthorized requests', done => {
@ -838,7 +1131,7 @@ describe('/sample', () => {
method: 'post',
url: '/sample/new',
httpStatus: 401,
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{sample_id: '400000000000000000000003', relation: 'part to this sample'}]}}
});
});
});

View File

@ -5,10 +5,15 @@ import SampleValidate from './validate/sample';
import NoteFieldValidate from './validate/note_field';
import res400 from './validate/res400';
import SampleModel from '../models/sample'
import MeasurementModel from '../models/measurement';
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 ConditionTemplateModel from '../models/condition_template';
import ParametersValidate from './validate/parameters';
import globals from '../globals';
const router = express.Router();
@ -16,7 +21,7 @@ const router = express.Router();
router.get('/samples', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
SampleModel.find({status: 10}).lean().exec((err, data) => {
SampleModel.find({status: globals.status.validated}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
})
@ -25,17 +30,32 @@ router.get('/samples', (req, res, next) => {
router.get('/samples/:group(new|deleted)', (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
let status;
switch (req.params.group) {
case 'new': status = 0;
break;
case 'deleted': status = -1;
break;
}
SampleModel.find({status: status}).lean().exec((err, data) => {
SampleModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
if (err) return next(err);
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
})
});
});
router.get('/sample/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
SampleModel.findById(req.params.id).populate('material_id').populate('user_id', 'name').populate('note_id').lean().exec((err, sampleData: any) => {
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
sampleData.material = sampleData.material_id; // map data to right keys
sampleData.user = sampleData.user_id.name;
sampleData.notes = sampleData.note_id ? sampleData.note_id : {};
MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data) => {
sampleData.measurements = data;
res.json(SampleValidate.output(sampleData, 'details'));
});
}
else {
res.status(404).json({status: 'Not found'});
}
});
});
router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
@ -60,6 +80,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
if (!await materialCheck(sample, res, next, sampleData.material_id)) return;
}
if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) { // do not execute check if condition is and was empty
if (!await conditionCheck(sample.condition, 'change', res, next)) return;
}
if (sample.hasOwnProperty('notes')) {
let newNotes = true;
if (sampleData.note_id !== null) { // old notes data exists
@ -89,10 +113,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
// check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
sample.status = 0;
sample.status = globals.status.new;
}
await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => {
await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data: any) => {
if (err) return next(err);
res.json(SampleValidate.output(data));
});
@ -112,7 +136,7 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
// only maintain and admin are allowed to edit other user's data
if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
await SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { // set sample status
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
@ -133,6 +157,10 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
router.post('/sample/new', async (req, res, next) => {
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
if (!req.body.hasOwnProperty('condition')) { // add empty condition if not specified
req.body.condition = {};
}
const {error, value: sample} = SampleValidate.input(req.body, 'new');
if (error) return res400(error, res);
@ -143,7 +171,11 @@ router.post('/sample/new', async (req, res, next) => {
customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
}
sample.status = 0; // set status to new
if (!_.isEmpty(sample.condition)) { // do not execute check if condition is empty
if (!await conditionCheck(sample.condition, 'change', res, next)) return;
}
sample.status = globals.status.new; // set status to new
sample.number = await numberGenerate(sample, req, res, next);
if (!sample.number) return;
@ -152,6 +184,7 @@ router.post('/sample/new', async (req, res, next) => {
delete sample.notes;
sample.note_id = data._id;
sample.user_id = req.authDetails.id;
new SampleModel(sample).save((err, data) => {
if (err) return next(err);
res.json(SampleValidate.output(data.toObject()));
@ -172,14 +205,15 @@ router.get('/sample/notes/fields', (req, res, next) => {
module.exports = router;
async function numberGenerate (sample, req, res, next) { // generate number, returns false on error
async function numberGenerate (sample, req, res, next) { // generate number in format Location32, returns false on error
const sampleData = await SampleModel
.find({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
.findOne({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
.sort({number: -1})
.lean()
.exec()
.catch(err => next(err));
if (sampleData instanceof Error) return false;
return req.authDetails.location + (sampleData.length > 0 ? Number(sampleData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1);
return req.authDetails.location + (sampleData ? Number(sampleData.number.replace(/[^0-9]+/g, '')) + 1 : 1);
}
async function materialCheck (sample, res, next, id = sample.material_id) { // validate material_id and color, returns false if invalid
@ -196,13 +230,31 @@ async function materialCheck (sample, res, next, id = sample.material_id) { //
return true;
}
async function conditionCheck (condition, param, res, next) { // validate treatment template, returns false if invalid, otherwise template data
if (!condition.condition_template || !IdValidate.valid(condition.condition_template)) { // template id not found
res.status(400).json({status: 'Condition template not available'});
return false;
}
const conditionData = await ConditionTemplateModel.findById(condition.condition_template).lean().exec().catch(err => next(err)) as any;
if (conditionData instanceof Error) return false;
if (!conditionData) { // template not found
res.status(400).json({status: 'Condition template not available'});
return false;
}
// validate parameters
const {error, value: ignore} = ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param);
if (error) {res400(error, res); return false;}
return conditionData;
}
function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference
return new Promise(resolve => {
if (sample.notes.sample_references.length > 0) { // there are sample_references
let referencesCount = sample.notes.sample_references.length; // count to keep track of running async operations
sample.notes.sample_references.forEach(reference => {
SampleModel.findById(reference.id).lean().exec((err, data) => {
SampleModel.findById(reference.sample_id).lean().exec((err, data) => {
if (err) {next(err); resolve(false)}
if (!data) {
res.status(400).json({status: 'Sample reference not available'});
@ -230,7 +282,7 @@ function customFieldsChange (fields, amount) { // update custom_fields and resp
if (err) return console.error(err);
})
}
else if (data.qty <= 0) {
else if (data.qty <= 0) { // delete document if field is not used anymore
NoteFieldModel.findOneAndDelete({name: field}).lean().exec(err => {
if (err) return console.error(err);
});

View File

@ -1,36 +1,38 @@
import should from 'should/as-function';
import _ from 'lodash';
import TemplateTreatmentModel from '../models/treatment_template';
import TemplateConditionModel from '../models/condition_template';
import TemplateMeasurementModel from '../models/measurement_template';
import TestHelper from "../test/helper";
// TODO: do not allow usage of old templates for new samples
// TODO: remove number_prefix
// TODO: template parameters are not allowed to be condition_template
describe('/template', () => {
let server;
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
describe('/template/treatment', () => {
describe('GET /template/treatments', () => {
it('returns all treatment templates', done => {
describe('/template/condition', () => {
describe('GET /template/conditions', () => {
it('returns all condition templates', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/template/treatments',
url: '/template/conditions',
auth: {basic: 'janedoe'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
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', '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');
should(treatment).have.property('number_prefix').be.type('string');
should(treatment.parameters).matchEach(number => {
should(res.body).have.lengthOf(json.collections.condition_templates.length);
should(res.body).matchEach(condition => {
should(condition).have.only.keys('_id', 'name', 'version', 'parameters');
should(condition).have.property('_id').be.type('string');
should(condition).have.property('name').be.type('string');
should(condition).have.property('version').be.type('number');
should(condition.parameters).matchEach(number => {
should(number).have.only.keys('name', 'range');
should(number).have.property('name').be.type('string');
should(number).have.property('range').be.type('object');
@ -42,7 +44,7 @@ describe('/template', () => {
it('rejects an API key', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/template/treatments',
url: '/template/conditions',
auth: {key: 'janedoe'},
httpStatus: 401
});
@ -50,26 +52,26 @@ describe('/template', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/template/treatments',
url: '/template/conditions',
httpStatus: 401
});
});
});
describe('GET /template/treatment/{id}', () => {
it('returns the right treatment template', done => {
describe('GET /template/condition/{id}', () => {
it('returns the right condition template', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 200,
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}}]}
res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, 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/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {key: 'janedoe'},
httpStatus: 401
});
@ -77,7 +79,7 @@ describe('/template', () => {
it('rejects an unknown id', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/template/treatment/000000000000000000000001',
url: '/template/condition/000000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 404
});
@ -85,58 +87,57 @@ describe('/template', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
httpStatus: 401
});
});
});
describe('PUT /template/treatment/{name}', () => {
it('returns the right treatment template', done => {
describe('PUT /template/condition/{name}', () => {
it('returns the right condition template', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/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}}]}
res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, 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/200000000000000000000001',
url: '/template/condition/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', version: 1, number_prefix: 'A', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}
res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, 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',
url: '/template/condition/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}}]}
res: {_id: '200000000000000000000001', name: 'heat treatment', version: 1, 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/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
}).end((err, res) => {
if (err) return done(err);
TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => {
TemplateConditionModel.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.only.keys('_id', 'name', 'version', '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');
@ -148,18 +149,17 @@ describe('/template', () => {
it('allows changing only one property', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat aging'}
}).end((err, res) => {
if (err) return done(err);
TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => {
TemplateConditionModel.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.only.keys('_id', 'name', 'version', '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');
@ -170,59 +170,59 @@ describe('/template', () => {
it('supports values ranges', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
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]}}]});
should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {values: [1, 2, 5]}}]});
done();
});
});
it('supports min max ranges', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
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}}]});
should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {min: 1, max: 11}}]});
done();
});
});
it('supports array type ranges', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
req: {parameters: [{name: 'time', range: {type: 'array'}}]}
}).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: {type: 'array'}}]});
should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {type: 'array'}}]});
done();
});
});
it('supports empty ranges', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 200,
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: {}}]});
should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, parameters: [{name: 'time', range: {}}]});
done();
});
});
it('rejects not specified parameters', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]},
@ -232,7 +232,7 @@ describe('/template', () => {
it('rejects an invalid id', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/2000000000h0000000000001',
url: '/template/condition/2000000000h0000000000001',
auth: {basic: 'admin'},
httpStatus: 404,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
@ -241,26 +241,16 @@ describe('/template', () => {
it('rejects an unknown id', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/000000000000000000000001',
url: '/template/condition/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',
url: '/template/condition/200000000000000000000001',
auth: {key: 'admin'},
httpStatus: 401,
req: {}
@ -269,7 +259,7 @@ describe('/template', () => {
it('rejects requests from a write user', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
auth: {basic: 'janedoe'},
httpStatus: 403,
req: {}
@ -278,27 +268,26 @@ describe('/template', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'put',
url: '/template/treatment/200000000000000000000001',
url: '/template/condition/200000000000000000000001',
httpStatus: 401,
req: {}
});
});
});
describe('POST /template/treatment/new', () => {
it('returns the right treatment template', done => {
describe('POST /template/condition/new', () => {
it('returns the right condition template', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat treatment3', number_prefix: 'C', parameters: [{name: 'material', range: {values: ['copper']}}]}
req: {name: 'heat treatment3', 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.only.keys('_id', 'name', 'version', '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');
@ -310,18 +299,17 @@ describe('/template', () => {
it('stores the template', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 200,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
}).end((err, res) => {
if (err) return done(err);
TemplateTreatmentModel.findById(res.body._id).lean().exec((err, data:any) => {
TemplateConditionModel.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.only.keys('_id', 'name', 'version', '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');
@ -333,117 +321,97 @@ describe('/template', () => {
it('rejects a missing name', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]},
req: {parameters: [{name: 'time', range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"name" is required'}
});
});
it('rejects a missing number prefix', done => {
it('rejects a number prefix', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/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'}
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"number_prefix" is not allowed'}
});
});
it('rejects missing parameters', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'C'},
req: {name: 'heat aging'},
res: {status: 'Invalid body format', details: '"parameters" is required'}
});
});
it('rejects a missing parameter name', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{range: {min: 1}}]},
req: {name: 'heat aging', parameters: [{range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"parameters[0].name" is required'}
});
});
it('rejects a number prefix containing numbers', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'AB5', parameters: [{name: 'time', range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"number_prefix" with value "AB5" fails to match the required pattern: /^[a-zA-Z]+$/'}
});
});
it('rejects a missing parameter range', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time'}]},
req: {name: 'heat aging', parameters: [{name: 'time'}]},
res: {status: 'Invalid body format', details: '"parameters[0].range" is required'}
});
});
it('rejects an invalid parameter range property', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {xx: 1}}]},
req: {name: 'heat aging', parameters: [{name: 'time', range: {xx: 1}}]},
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/treatment/new',
url: '/template/condition/new',
auth: {basic: 'admin'},
httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {}}], xx: 33},
req: {name: 'heat aging', parameters: [{name: 'time', range: {}}], xx: 33},
res: {status: 'Invalid body format', details: '"xx" is not allowed'}
});
});
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',
url: '/template/condition/new',
auth: {key: 'admin'},
httpStatus: 401,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
it('rejects requests from a write user', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
auth: {basic: 'janedoe'},
httpStatus: 403,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'post',
url: '/template/treatment/new',
url: '/template/condition/new',
httpStatus: 401,
req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]}
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
});
});
});

View File

@ -2,8 +2,8 @@ import express from 'express';
import _ from 'lodash';
import TemplateValidate from './validate/template';
import TemplateTreatmentModel from '../models/treatment_template';
import TemplateMeasurementModel from '../models/measurement_template';
import ConditionTemplateModel from '../models/condition_template';
import MeasurementTemplateModel from '../models/measurement_template';
import res400 from './validate/res400';
import IdValidate from './validate/id';
@ -11,23 +11,23 @@ import IdValidate from './validate/id';
const router = express.Router();
router.get('/template/:collection(measurements|treatments)', (req, res, next) => {
router.get('/template/:collection(measurements|conditions)', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
model(req).find({}).lean().exec((err, data) => {
if (err) next (err);
res.json(_.compact(data.map(e => TemplateValidate.output(e, req.params.collection)))); // validate all and filter null values from validation errors
res.json(_.compact(data.map(e => TemplateValidate.output(e)))); // validate all and filter null values from validation errors
});
});
router.get('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), (req, res, next) => {
router.get('/template/:collection(measurement|condition)/' + IdValidate.parameter(), (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
model(req).findById(req.params.id).lean().exec((err, data) => {
if (err) next (err);
if (data) {
res.json(TemplateValidate.output(data, req.params.collection));
res.json(TemplateValidate.output(data));
}
else {
res.status(404).json({status: 'Not found'});
@ -35,64 +35,46 @@ router.get('/template/:collection(measurement|treatment)/' + IdValidate.paramete
});
});
router.put('/template/:collection(measurement|treatment)/' + IdValidate.parameter(), async (req, res, next) => {
router.put('/template/:collection(measurement|condition)/' + 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);
const {error, value: template} = TemplateValidate.input(req.body, 'change');
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 (_.has(template, 'number_prefix') && template.number_prefix !== templateData.number_prefix) { // got new number_prefix
if (!await numberPrefixCheck(template, req, res, next)) return;
return res.status(404).json({status: 'Not found'});
}
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed
template.version = templateData.version + 1; // increase version
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => { // save new template, fill with old properties
if (err) next (err);
res.json(TemplateValidate.output(data.toObject(), req.params.collection));
res.json(TemplateValidate.output(data.toObject()));
});
}
else {
res.json(TemplateValidate.output(templateData, req.params.collection));
res.json(TemplateValidate.output(templateData));
}
});
router.post('/template/:collection(measurement|treatment)/new', async (req, res, next) => {
router.post('/template/:collection(measurement|condition)/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);
const {error, value: template} = TemplateValidate.input(req.body, 'new');
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; // set template version
await new (model(req))(template).save((err, data) => {
if (err) next (err);
res.json(TemplateValidate.output(data.toObject(), req.params.collection));
res.json(TemplateValidate.output(data.toObject()));
});
});
module.exports = router;
async function numberPrefixCheck (template, req, res, next) { // check if number_prefix is available
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 right template model
return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel;
return req.params.collection === 'condition' ? ConditionTemplateModel : MeasurementTemplateModel;
}

View File

@ -9,6 +9,7 @@ describe('/user', () => {
before(done => TestHelper.before(done));
beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done));
after(done => TestHelper.after(done));
describe('GET /users', () => {
it('returns all users', done => {
@ -288,7 +289,7 @@ describe('/user', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {pass: 'password'},
res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$/'}
res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$)[a-zA-Z0-9!"#%&\'()*+,\\-.\\/:;<=>?@[\\]^_`{|}~]{8,}$/'}
});
});
it('rejects requests from non-admins for another user', done => {
@ -546,7 +547,7 @@ describe('/user', () => {
auth: {basic: 'admin'},
httpStatus: 400,
req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'password', level: 'read', location: 'Rng', device_name: 'Alpha II'},
res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$/'}
res: {status: 'Invalid body format', details: '"pass" with value "password" fails to match the required pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$)[a-zA-Z0-9!"#%&\'()*+,\\-.\\/:;<=>?@[\\]^_`{|}~]{8,}$/'}
});
});
it('rejects requests from non-admins', done => {

View File

@ -40,7 +40,6 @@ router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => {
const username = getUsername(req, res);
if (!username) return;
console.log(username);
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
if (error) return res400(error, res);
@ -154,8 +153,6 @@ function getUsername (req, res) { // returns username or false if action is not
async function usernameCheck (name, res, next) { // check if username is already taken
const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
if (userData instanceof Error) return false;
console.log(userData);
console.log(UserValidate.isSpecialName(name));
if (userData || UserValidate.isSpecialName(name)) {
res.status(400).json({status: 'Username already taken'});
return false;

View File

@ -1,50 +0,0 @@
import Joi from '@hapi/joi';
import IdValidate from './id';
export default class ConditionValidate {
private static condition = {
number: Joi.string()
.max(128),
parameters: Joi.object()
.pattern(/.*/, Joi.alternatives()
.try(
Joi.string().max(128),
Joi.number(),
Joi.boolean(),
Joi.array()
)
)
}
static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
return Joi.object({
sample_id: IdValidate.get().required(),
parameters: this.condition.parameters.required(),
treatment_template: IdValidate.get().required()
}).validate(data);
}
else if (param === 'change') {
return Joi.object({
parameters: this.condition.parameters
}).validate(data);
}
else {
return{error: 'No parameter specified!', value: {}};
}
}
static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
const {value, error} = Joi.object({
_id: IdValidate.get(),
sample_id: IdValidate.get(),
number: this.condition.number,
parameters: this.condition.parameters,
treatment_template: IdValidate.get()
}).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
}

View File

@ -1,39 +1,39 @@
import joi from '@hapi/joi';
import Joi from '@hapi/joi';
import IdValidate from './id';
export default class MaterialValidate { // validate input for material
private static material = {
name: joi.string()
name: Joi.string()
.max(128),
supplier: joi.string()
supplier: Joi.string()
.max(128),
group: joi.string()
group: Joi.string()
.max(128),
mineral: joi.number()
mineral: Joi.number()
.integer()
.min(0)
.max(100),
glass_fiber: joi.number()
glass_fiber: Joi.number()
.integer()
.min(0)
.max(100),
carbon_fiber: joi.number()
carbon_fiber: Joi.number()
.integer()
.min(0)
.max(100),
numbers: joi.array()
.items(joi.object({
color: joi.string()
numbers: Joi.array()
.items(Joi.object({
color: Joi.string()
.max(128)
.required(),
number: joi.string()
number: Joi.string()
.max(128)
.allow('')
.required()
@ -42,7 +42,7 @@ export default class MaterialValidate { // validate input for material
static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
return joi.object({
return Joi.object({
name: this.material.name.required(),
supplier: this.material.supplier.required(),
group: this.material.group.required(),
@ -53,7 +53,7 @@ export default class MaterialValidate { // validate input for material
}).validate(data);
}
else if (param === 'change') {
return joi.object({
return Joi.object({
name: this.material.name,
supplier: this.material.supplier,
group: this.material.group,
@ -70,7 +70,7 @@ export default class MaterialValidate { // validate input for material
static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
const {value, error} = joi.object({
const {value, error} = Joi.object({
_id: IdValidate.get(),
name: this.material.name,
supplier: this.material.supplier,
@ -82,4 +82,17 @@ export default class MaterialValidate { // validate input for material
}).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
static outputV() { // return output validator
return Joi.object({
_id: IdValidate.get(),
name: this.material.name,
supplier: this.material.supplier,
group: this.material.group,
mineral: this.material.mineral,
glass_fiber: this.material.glass_fiber,
carbon_fiber: this.material.carbon_fiber,
numbers: this.material.numbers
});
}
}

View File

@ -12,13 +12,14 @@ export default class MeasurementValidate {
Joi.boolean(),
Joi.array()
)
.allow(null)
)
};
static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
return Joi.object({
condition_id: IdValidate.get().required(),
sample_id: IdValidate.get().required(),
values: this.measurement.values.required(),
measurement_template: IdValidate.get().required()
}).validate(data);
@ -37,7 +38,7 @@ export default class MeasurementValidate {
data = IdValidate.stringify(data);
const {value, error} = Joi.object({
_id: IdValidate.get(),
condition_id: IdValidate.get(),
sample_id: IdValidate.get(),
values: this.measurement.values,
measurement_template: IdValidate.get()
}).validate(data, {stripUnknown: true});

View File

@ -1,7 +1,7 @@
import Joi from '@hapi/joi';
export default class ParametersValidate {
static input (data, parameters, param) { // data to validate, parameters from template, param: 'new', 'change'
static input (data, parameters, param) { // data to validate, parameters from template, param: 'new', 'change', 'null'(null values are allowed)
let joiObject = {};
parameters.forEach(parameter => {
if (parameter.range.hasOwnProperty('values')) { // append right validation method according to parameter
@ -39,6 +39,9 @@ export default class ParametersValidate {
if (param === 'new') {
joiObject[parameter.name] = joiObject[parameter.name].required()
}
else if (param === 'null') {
joiObject[parameter.name] = joiObject[parameter.name].allow(null)
}
});
return Joi.object(joiObject).validate(data);
}

View File

@ -1,6 +1,8 @@
import Joi from '@hapi/joi';
import IdValidate from './id';
import UserValidate from './user';
import MaterialValidate from './material';
export default class SampleValidate {
private static sample = {
@ -17,13 +19,16 @@ export default class SampleValidate {
.max(128)
.allow(''),
condition: Joi.object(),
notes: Joi.object({
comment: Joi.string()
.max(512),
.max(512)
.allow(''),
sample_references: Joi.array()
.items(Joi.object({
id: IdValidate.get(),
sample_id: IdValidate.get(),
relation: Joi.string()
.max(128)
@ -47,6 +52,7 @@ export default class SampleValidate {
color: this.sample.color.required(),
type: this.sample.type.required(),
batch: this.sample.batch.required(),
condition: this.sample.condition.required(),
material_id: IdValidate.get().required(),
notes: this.sample.notes.required()
}).validate(data);
@ -56,6 +62,7 @@ export default class SampleValidate {
color: this.sample.color,
type: this.sample.type,
batch: this.sample.batch,
condition: this.sample.condition,
material_id: IdValidate.get(),
notes: this.sample.notes,
}).validate(data);
@ -65,18 +72,39 @@ export default class SampleValidate {
}
}
static output (data) { // validate output and strip unwanted properties, returns null if not valid
static output (data, param = 'refs') { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
const {value, error} = Joi.object({
_id: IdValidate.get(),
number: this.sample.number,
color: this.sample.color,
type: this.sample.type,
batch: this.sample.batch,
material_id: IdValidate.get(),
note_id: IdValidate.get().allow(null),
user_id: IdValidate.get()
}).validate(data, {stripUnknown: true});
let joiObject;
if (param === 'refs') {
joiObject = {
_id: IdValidate.get(),
number: this.sample.number,
color: this.sample.color,
type: this.sample.type,
batch: this.sample.batch,
condition: this.sample.condition,
material_id: IdValidate.get(),
note_id: IdValidate.get().allow(null),
user_id: IdValidate.get()
};
}
else if(param === 'details') {
joiObject = {
_id: IdValidate.get(),
number: this.sample.number,
color: this.sample.color,
type: this.sample.type,
batch: this.sample.batch,
condition: this.sample.condition,
material: MaterialValidate.outputV(),
notes: this.sample.notes,
user: UserValidate.username()
}
}
else {
return null;
}
const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
}

View File

@ -9,13 +9,7 @@ export default class TemplateValidate {
version: Joi.number()
.min(1),
number_prefix: Joi.string()
.pattern(/^[a-zA-Z]+$/)
.min(1)
.max(16),
parameters: Joi.array()
.min(1)
.items(
Joi.object({
name: Joi.string()
@ -43,63 +37,32 @@ export default class TemplateValidate {
)
};
static input (data, param, template) { // validate input, set param to 'new' to make all attributes required
static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') {
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);
}
return Joi.object({
name: this.template.name.required(),
parameters: this.template.parameters.required()
}).validate(data);
}
else if (param === 'change') {
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);
}
return Joi.object({
name: this.template.name,
parameters: this.template.parameters
}).validate(data);
}
else {
return{error: 'No parameter specified!', value: {}};
}
}
static output (data, template) { // validate output and strip unwanted properties, returns null if not valid
static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data);
let joiObject;
if (template === 'treatment') { // differentiate between measurement and treatment (has number_prefix) template
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});
const {value, error} = Joi.object({
_id: IdValidate.get(),
name: this.template.name,
version: this.template.version,
parameters: this.template.parameters
}).validate(data, {stripUnknown: true});
return error !== undefined? null : value;
}
}

View File

@ -84,4 +84,8 @@ export default class UserValidate { // validate input for user
static isSpecialName (name) { // true if name belongs to special names
return this.specialUsernames.indexOf(name) > -1;
}
static username() {
return this.user.name;
}
}

View File

@ -7,6 +7,11 @@
"type": "granulate",
"color": "black",
"batch": "",
"condition": {
"material": "copper",
"weeks": 3,
"condition_template": {"$oid":"200000000000000000000001"}
},
"material_id": {"$oid":"100000000000000000000004"},
"note_id": null,
"user_id": {"$oid":"000000000000000000000002"},
@ -19,6 +24,11 @@
"type": "granulate",
"color": "natural",
"batch": "1560237365",
"condition": {
"material": "copper",
"weeks": 3,
"condition_template": {"$oid":"200000000000000000000001"}
},
"material_id": {"$oid":"100000000000000000000001"},
"note_id": {"$oid":"500000000000000000000001"},
"user_id": {"$oid":"000000000000000000000002"},
@ -31,6 +41,11 @@
"type": "part",
"color": "black",
"batch": "1704-005",
"condition": {
"material": "copper",
"weeks": 3,
"condition_template": {"$oid":"200000000000000000000001"}
},
"material_id": {"$oid":"100000000000000000000005"},
"note_id": {"$oid":"500000000000000000000002"},
"user_id": {"$oid":"000000000000000000000003"},
@ -43,6 +58,11 @@
"type": "granulate",
"color": "black",
"batch": "1653000308",
"condition": {
"material": "hot air",
"weeks": 5,
"condition_template": {"$oid":"200000000000000000000001"}
},
"material_id": {"$oid":"100000000000000000000005"},
"note_id": {"$oid":"500000000000000000000003"},
"user_id": {"$oid":"000000000000000000000003"},
@ -55,11 +75,27 @@
"type": "granulate",
"color": "black",
"batch": "1653000308",
"condition": {
"condition_template": {"$oid":"200000000000000000000003"}
},
"material_id": {"$oid":"100000000000000000000005"},
"note_id": {"$oid":"500000000000000000000003"},
"note_id": null,
"user_id": {"$oid":"000000000000000000000003"},
"status": -1,
"__v": 0
},
{
"_id": {"$oid":"400000000000000000000006"},
"number": "Rng36",
"type": "granulate",
"color": "black",
"batch": "",
"condition": {},
"material_id": {"$oid":"100000000000000000000004"},
"note_id": null,
"user_id": {"$oid":"000000000000000000000002"},
"status": 0,
"__v": 0
}
],
"notes": [
@ -73,7 +109,7 @@
"_id": {"$oid":"500000000000000000000002"},
"comment": "",
"sample_references": [{
"id": {"$oid":"400000000000000000000004"},
"sample_id": {"$oid":"400000000000000000000004"},
"relation": "granulate to sample"
}],
"custom_fields": {
@ -85,7 +121,7 @@
"_id": {"$oid":"500000000000000000000003"},
"comment": "",
"sample_references": [{
"id": {"$oid":"400000000000000000000003"},
"sample_id": {"$oid":"400000000000000000000003"},
"relation": "part to sample"
}],
"custom_fields": {
@ -234,60 +270,10 @@
"__v": 0
}
],
"conditions": [
{
"_id": {"$oid":"700000000000000000000001"},
"sample_id": {"$oid":"400000000000000000000001"},
"number": "A1",
"parameters": {
"material": "copper",
"weeks": 3
},
"treatment_template": {"$oid":"200000000000000000000001"},
"status": 10,
"__v": 0
},
{
"_id": {"$oid":"700000000000000000000002"},
"sample_id": {"$oid":"400000000000000000000002"},
"number": "A1",
"parameters": {
"material": "copper",
"weeks": 3
},
"treatment_template": {"$oid":"200000000000000000000001"},
"status": 10,
"__v": 0
},
{
"_id": {"$oid":"700000000000000000000003"},
"sample_id": {"$oid":"400000000000000000000004"},
"number": "A1",
"parameters": {
"material": "copper",
"weeks": 3
},
"treatment_template": {"$oid":"200000000000000000000001"},
"status": 10,
"__v": 0
},
{
"_id": {"$oid":"700000000000000000000004"},
"sample_id": {"$oid":"400000000000000000000001"},
"number": "A2",
"parameters": {
"material": "hot air",
"weeks": 5
},
"treatment_template": {"$oid":"200000000000000000000001"},
"status": 10,
"__v": 0
}
],
"measurements": [
{
"_id": {"$oid":"800000000000000000000001"},
"condition_id": {"$oid":"700000000000000000000001"},
"sample_id": {"$oid":"400000000000000000000001"},
"values": {
"dpt": [
[3997.12558,98.00555],
@ -301,7 +287,7 @@
},
{
"_id": {"$oid":"800000000000000000000002"},
"condition_id": {"$oid":"700000000000000000000002"},
"sample_id": {"$oid":"400000000000000000000002"},
"values": {
"weight %": 0.5,
"standard deviation": 0.2
@ -312,21 +298,41 @@
},
{
"_id": {"$oid":"800000000000000000000003"},
"condition_id": {"$oid":"700000000000000000000003"},
"sample_id": {"$oid":"400000000000000000000003"},
"values": {
"val1": 1
},
"status": 0,
"measurement_template": {"$oid":"300000000000000000000003"},
"__v": 0
},
{
"_id": {"$oid":"800000000000000000000004"},
"sample_id": {"$oid":"400000000000000000000003"},
"values": {
"val1": 1
},
"status": -1,
"measurement_template": {"$oid":"300000000000000000000003"},
"__v": 0
},
{
"_id": {"$oid":"800000000000000000000005"},
"sample_id": {"$oid":"400000000000000000000002"},
"values": {
"weight %": 0.5,
"standard deviation":null
},
"status": 10,
"measurement_template": {"$oid":"300000000000000000000002"},
"__v": 0
}
],
"treatment_templates": [
"condition_templates": [
{
"_id": {"$oid":"200000000000000000000001"},
"name": "heat treatment",
"version": 1,
"number_prefix": "A",
"parameters": [
{
"name": "material",
@ -351,7 +357,6 @@
"_id": {"$oid":"200000000000000000000002"},
"name": "heat treatment 2",
"version": 2,
"number_prefix": "B",
"parameters": [
{
"name": "material",
@ -359,6 +364,14 @@
}
],
"__v": 0
},
{
"_id": {"$oid":"200000000000000000000003"},
"name": "raw material",
"version": 1,
"parameters": [
],
"__v": 0
}
],
"measurement_templates": [

View File

@ -10,6 +10,7 @@ export default class TestHelper {
user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
}
public static res = { // default responses
400: {status: 'Bad request'},
401: {status: 'Unauthorized'},
@ -38,7 +39,11 @@ export default class TestHelper {
server.close(done);
}
static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res}
static after(done) {
db.disconnect(done);
}
static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res, default (set to false if you want to dismiss default .end handling)}
let st = supertest(server);
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
@ -57,6 +62,9 @@ export default class TestHelper {
st = st.delete(options.url)
break;
}
if (options.hasOwnProperty('reqType')) { // request body
st = st.type(options.reqType);
}
if (options.hasOwnProperty('req')) { // request body
st = st.send(options.req);
}