Archived
2

adapted existing /sample methods to condition, removed /condition route

This commit is contained in:
VLE2FE 2020-05-27 14:31:17 +02:00
parent 2829752d0c
commit 0acb9dd6fc
34 changed files with 1753 additions and 1374 deletions

View File

@ -4,6 +4,8 @@
<w>bcrypt</w> <w>bcrypt</w>
<w>cfenv</w> <w>cfenv</w>
<w>dfopdb</w> <w>dfopdb</w>
<w>janedoe</w>
<w>testcomment</w>
</words> </words>
</dictionary> </dictionary>
</component> </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

@ -66,7 +66,6 @@ paths:
- $ref: 'others.yaml' - $ref: 'others.yaml'
- $ref: 'sample.yaml' - $ref: 'sample.yaml'
- $ref: 'material.yaml' - $ref: 'material.yaml'
- $ref: 'condition.yaml'
- $ref: 'measurement.yaml' - $ref: 'measurement.yaml'
- $ref: 'template.yaml' - $ref: 'template.yaml'
- $ref: 'model.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: 500:
$ref: 'api.yaml#/components/responses/500' $ref: 'api.yaml#/components/responses/500'
/samples{group}: /samples/{group}:
parameters: parameters:
- $ref: 'api.yaml#/components/parameters/Group' - $ref: 'api.yaml#/components/parameters/Group'
get: get:
@ -48,7 +48,7 @@
get: get:
summary: TODO sample details summary: TODO sample details
description: 'Auth: all, levels: read, write, maintain, dev, admin' 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: tags:
- /sample - /sample
responses: responses:

View File

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

View File

@ -1,6 +1,6 @@
/template/treatments: /template/conditions:
get: get:
summary: all available treatment methods summary: all available condition methods
description: 'Auth: basic, levels: read, write, maintain, dev, admin' description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags: tags:
- /template - /template
@ -8,23 +8,23 @@
- BasicAuth: [] - BasicAuth: []
responses: responses:
200: 200:
description: list of treatments description: list of conditions
content: content:
application/json: application/json:
schema: schema:
type: array type: array
items: items:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate' $ref: 'api.yaml#/components/schemas/ConditionTemplate'
401: 401:
$ref: 'api.yaml#/components/responses/401' $ref: 'api.yaml#/components/responses/401'
500: 500:
$ref: 'api.yaml#/components/responses/500' $ref: 'api.yaml#/components/responses/500'
/template/treatment/{id}: /template/condition/{id}:
parameters: parameters:
- $ref: 'api.yaml#/components/parameters/Id' - $ref: 'api.yaml#/components/parameters/Id'
get: get:
summary: treatment method details summary: condition method details
description: 'Auth: basic, levels: read, write, maintain, admin' description: 'Auth: basic, levels: read, write, maintain, admin'
tags: tags:
- /template - /template
@ -32,11 +32,11 @@
- BasicAuth: [] - BasicAuth: []
responses: responses:
200: 200:
description: treatment details description: condition details
content: content:
application/json: application/json:
schema: schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate' $ref: 'api.yaml#/components/schemas/ConditionTemplate'
401: 401:
$ref: 'api.yaml#/components/responses/401' $ref: 'api.yaml#/components/responses/401'
404: 404:
@ -44,7 +44,7 @@
500: 500:
$ref: 'api.yaml#/components/responses/500' $ref: 'api.yaml#/components/responses/500'
put: put:
summary: change treatment method summary: change condition method
description: 'Auth: basic, levels: maintain, admin' 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 x-doc: With a change a new version is set, resulting in a new template with a new id
tags: tags:
@ -56,14 +56,14 @@
content: content:
application/json: application/json:
schema: schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate' $ref: 'api.yaml#/components/schemas/ConditionTemplate'
responses: responses:
200: 200:
description: treatment details description: condition details
content: content:
application/json: application/json:
schema: schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate' $ref: 'api.yaml#/components/schemas/ConditionTemplate'
400: 400:
$ref: 'api.yaml#/components/responses/400' $ref: 'api.yaml#/components/responses/400'
401: 401:
@ -75,9 +75,9 @@
500: 500:
$ref: 'api.yaml#/components/responses/500' $ref: 'api.yaml#/components/responses/500'
/template/treatment/new: /template/condition/new:
post: post:
summary: add treatment method summary: add condition method
description: 'Auth: basic, levels: maintain, admin' description: 'Auth: basic, levels: maintain, admin'
tags: tags:
- /template - /template
@ -88,14 +88,14 @@
content: content:
application/json: application/json:
schema: schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate' $ref: 'api.yaml#/components/schemas/ConditionTemplate'
responses: responses:
200: 200:
description: treatment details description: condition details
content: content:
application/json: application/json:
schema: schema:
$ref: 'api.yaml#/components/schemas/TreatmentTemplate' $ref: 'api.yaml#/components/schemas/ConditionTemplate'
400: 400:
$ref: 'api.yaml#/components/responses/400' $ref: 'api.yaml#/components/responses/400'
401: 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", "main": "index.js",
"scripts": { "scripts": {
"tsc": "tsc", "tsc": "tsc",
"tsc-full": "del /q dist\\* & (for /d %x in (dist\\*) do @rd /s /q \"%x\") & tsc",
"test": "mocha dist/**/**.spec.js", "test": "mocha dist/**/**.spec.js",
"start": "tsc && node dist/index.js || exit 1", "start": "tsc && node dist/index.js || exit 1",
"dev": "nodemon -e ts,yaml --exec \"npm run start\"", "dev": "nodemon -e ts,yaml --exec \"npm run start\"",
"loadDev": "node dist/test/loadDev.js" "loadDev": "node dist/test/loadDev.js",
"coverage": "nyc --reporter=html --reporter=tex mocha dist/**/**.spec.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -44,6 +46,7 @@
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.150", "@types/lodash": "^4.14.150",
"mocha": "^7.1.2", "mocha": "^7.1.2",
"nyc": "^15.0.1",
"should": "^13.2.3", "should": "^13.2.3",
"supertest": "^4.0.2" "supertest": "^4.0.2"
} }

View File

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

View File

@ -11,6 +11,7 @@ import db from './db';
// TODO: validation: VZ, Humidity: min/max value, DPT: filename // TODO: validation: VZ, Humidity: min/max value, DPT: filename
// TODO: condition values not needed on initial add // TODO: condition values not needed on initial add
// TODO: add multiple samples at once // TODO: add multiple samples at once
// TODO: coverage
// tell if server is running in debug or production environment // tell if server is running in debug or production environment
console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT ====='); console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
@ -48,14 +49,20 @@ app.use((req, res, next) => { // no database connection error
}); });
app.use(require('./helpers/authorize')); // handle authentication app.use(require('./helpers/authorize')); // handle authentication
// redirect /api routes for Angular proxy in development
app.use('/api/:url', (req, res) => {
req.url = '/' + req.params.url;
app.handle(req, res);
});
// require routes // require routes
app.use('/api', require('./routes/root')); app.use('/', require('./routes/root'));
app.use('/api', require('./routes/sample')); app.use('/', require('./routes/sample'));
app.use('/api', require('./routes/material')); app.use('/', require('./routes/material'));
app.use('/api', require('./routes/template')); app.use('/', require('./routes/template'));
app.use('/api', require('./routes/user')); app.use('/', require('./routes/user'));
app.use('/api', require('./routes/condition')); app.use('/', require('./routes/measurement'));
app.use('/api', require('./routes/measurement'));
// static files // static files
app.use('/static', express.static('static')); 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'; import mongoose from 'mongoose';
const TreatmentTemplateSchema = new mongoose.Schema({ const ConditionTemplateSchema = new mongoose.Schema({
name: String, name: String,
version: Number, version: Number,
number_prefix: String,
parameters: [{ parameters: [{
name: String, name: String,
range: mongoose.Schema.Types.Mixed range: mongoose.Schema.Types.Mixed
}] }]
}, {minimize: false}); // to allow empty objects }, {minimize: false}); // to allow empty objects
export default mongoose.model('treatment_template', TreatmentTemplateSchema); export default mongoose.model('condition_template', ConditionTemplateSchema);

View File

@ -1,9 +1,11 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import ConditionModel from './condition'; import SampleModel from './sample';
import MeasurementTemplateModel from './measurement_template'; import MeasurementTemplateModel from './measurement_template';
// TODO: change to sample_id
const MeasurementSchema = new mongoose.Schema({ 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, values: mongoose.Schema.Types.Mixed,
measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel}, measurement_template: {type: mongoose.Schema.Types.ObjectId, ref: MeasurementTemplateModel},
status: Number status: Number

View File

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

View File

@ -9,10 +9,11 @@ const SampleSchema = new mongoose.Schema({
type: String, type: String,
color: String, color: String,
batch: String, batch: String,
condition: mongoose.Schema.Types.Mixed,
material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel}, material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel}, note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel}, user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel},
status: Number status: Number
}); }, {minimize: false});
export default mongoose.model('sample', SampleSchema); 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 _ from 'lodash';
import MaterialModel from '../models/material'; import MaterialModel from '../models/material';
import TestHelper from "../test/helper"; import TestHelper from "../test/helper";
import globals from '../globals';
// TODO: color name must be unique to get color number // TODO: color name must be unique to get color number
// TODO: separate supplier/ material name into own collections // TODO: separate supplier/ material name into own collections
@ -22,7 +23,7 @@ describe('/material', () => {
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); 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(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers'); should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string'); should(material).have.property('_id').be.type('string');
@ -50,7 +51,7 @@ describe('/material', () => {
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); 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(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers'); should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string'); should(material).have.property('_id').be.type('string');
@ -89,7 +90,7 @@ describe('/material', () => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); const json = require('../test/db.json');
let asyncCounter = res.body.length; 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(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers'); should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string'); should(material).have.property('_id').be.type('string');
@ -105,7 +106,7 @@ describe('/material', () => {
should(number).have.property('number').be.type('string'); should(number).have.property('number').be.type('string');
}); });
MaterialModel.findById(material._id).lean().exec((err, data) => { 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) { if (--asyncCounter === 0) {
done(); done();
} }
@ -123,7 +124,7 @@ describe('/material', () => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); const json = require('../test/db.json');
let asyncCounter = res.body.length; 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(res.body).matchEach(material => {
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers'); should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
should(material).have.property('_id').be.type('string'); should(material).have.property('_id').be.type('string');
@ -139,7 +140,7 @@ describe('/material', () => {
should(number).have.property('number').be.type('string'); should(number).have.property('number').be.type('string');
}); });
MaterialModel.findById(material._id).lean().exec((err, data) => { 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) { if (--asyncCounter === 0) {
done(); 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'}]}); 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) => { MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('status', 10); should(data).have.property('status',globals.status.validated);
done(); 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'}]}); 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) => { MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('status', 10); should(data).have.property('status',globals.status.validated);
done(); done();
}); });
}); });
@ -513,7 +514,7 @@ describe('/material', () => {
should(data[0]).have.property('mineral', '0'); should(data[0]).have.property('mineral', '0');
should(data[0]).have.property('glass_fiber', '30'); should(data[0]).have.property('glass_fiber', '30');
should(data[0]).have.property('carbon_fiber', '0'); 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); should(data[0].numbers).have.lengthOf(0);
done(); done();
}); });
@ -552,7 +553,7 @@ describe('/material', () => {
should(data[0]).have.property('mineral', '0'); should(data[0]).have.property('mineral', '0');
should(data[0]).have.property('glass_fiber', '30'); should(data[0]).have.property('glass_fiber', '30');
should(data[0]).have.property('carbon_fiber', '0'); 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: ''}); should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
done(); done();
}); });

View File

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

View File

@ -1,6 +1,7 @@
import should from 'should/as-function'; import should from 'should/as-function';
import MeasurementModel from '../models/measurement'; import MeasurementModel from '../models/measurement';
import TestHelper from "../test/helper"; import TestHelper from "../test/helper";
import globals from '../globals';
// TODO: allow empty values // TODO: allow empty values
@ -78,7 +79,7 @@ describe('/measurement', () => {
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', condition_id: '700000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]]}, measurement_template: '300000000000000000000001'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => { MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('status', 10); should(data).have.property('status',globals.status.validated);
done(); done();
}); });
}); });
@ -95,7 +96,7 @@ describe('/measurement', () => {
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', condition_id: '700000000000000000000002', values: {'weight %': 0.5, 'standard deviation': 0.2}, measurement_template: '300000000000000000000002'});
MeasurementModel.findById('800000000000000000000002').lean().exec((err, data: any) => { MeasurementModel.findById('800000000000000000000002').lean().exec((err, data: any) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('status', 10); should(data).have.property('status',globals.status.validated);
done(); done();
}); });
}); });
@ -114,7 +115,7 @@ describe('/measurement', () => {
should(data).have.only.keys('_id', 'condition_id', 'values', 'measurement_template', 'status', '__v'); should(data).have.only.keys('_id', 'condition_id', 'values', 'measurement_template', 'status', '__v');
should(data.condition_id.toString()).be.eql('700000000000000000000001'); should(data.condition_id.toString()).be.eql('700000000000000000000001');
should(data.measurement_template.toString()).be.eql('300000000000000000000001'); 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).have.property('values');
should(data.values).have.property('dpt', [[1,2],[3,4],[5,6]]); should(data.values).have.property('dpt', [[1,2],[3,4],[5,6]]);
done(); done();
@ -256,7 +257,7 @@ describe('/measurement', () => {
should(res.body).be.eql({status: 'OK'}); should(res.body).be.eql({status: 'OK'});
MeasurementModel.findById('800000000000000000000001').lean().exec((err, data) => { MeasurementModel.findById('800000000000000000000001').lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('status', -1); should(data).have.property('status',globals.status.deleted);
done(); done();
}); });
}); });

View File

@ -2,12 +2,12 @@ import express from 'express';
import _ from 'lodash'; import _ from 'lodash';
import MeasurementModel from '../models/measurement'; import MeasurementModel from '../models/measurement';
import ConditionModel from '../models/condition';
import MeasurementTemplateModel from '../models/measurement_template'; import MeasurementTemplateModel from '../models/measurement_template';
import MeasurementValidate from './validate/measurement'; import MeasurementValidate from './validate/measurement';
import IdValidate from './validate/id'; import IdValidate from './validate/id';
import res400 from './validate/res400'; import res400 from './validate/res400';
import ParametersValidate from './validate/parameters'; import ParametersValidate from './validate/parameters';
import globals from '../globals';
const router = express.Router(); const router = express.Router();
@ -46,7 +46,7 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
if (measurement.values) { if (measurement.values) {
measurement.values = _.assign({}, data.values, measurement.values); measurement.values = _.assign({}, data.values, measurement.values);
if (!_.isEqual(measurement.values, data.values)) { if (!_.isEqual(measurement.values, data.values)) {
measurement.status = 0; // set status to new measurement.status = globals.status.new; // set status to new
} }
} }
@ -66,7 +66,7 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
} }
if (!await conditionIdCheck(data, req, res, next)) return; if (!await conditionIdCheck(data, req, res, next)) return;
await MeasurementModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { await MeasurementModel.findByIdAndUpdate(req.params.id, {status:globals.status.deleted}).lean().exec(err => {
if (err) return next(err); if (err) return next(err);
res.json({status: 'OK'}); res.json({status: 'OK'});
}); });
@ -93,14 +93,14 @@ router.post('/measurement/new', async (req, res, next) => {
module.exports = router; module.exports = router;
async function conditionIdCheck (measurement, req, res, next) { // validate condition_id, returns false if invalid async function conditionIdCheck (measurement, req, res, next) { // validate condition_id, returns false if invalid // TODO
const sampleData = await ConditionModel.findById(measurement.condition_id).populate('sample_id').lean().exec().catch(err => {next(err); return false;}) as any; // const sampleData = await ConditionModel.findById(measurement.condition_id).populate('sample_id').lean().exec().catch(err => {next(err); return false;}) as any;
if (!sampleData) { // sample_id not found // if (!sampleData) { // sample_id not found
res.status(400).json({status: 'Condition id not available'}); // res.status(400).json({status: 'Condition id not available'});
return false 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.sample_id.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return false; // sample does not belong to user
return true; // 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, param for new/change

View File

@ -3,10 +3,16 @@ import SampleModel from '../models/sample';
import NoteModel from '../models/note'; import NoteModel from '../models/note';
import NoteFieldModel from '../models/note_field'; import NoteFieldModel from '../models/note_field';
import TestHelper from "../test/helper"; import TestHelper from "../test/helper";
import globals from '../globals';
// TODO: generate output for ML in format DPT -> data, implement filtering, field selection // 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: write script for data import
// TODO: delete everything (measurements, condition) with sample // 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', () => { describe('/sample', () => {
let server; let server;
@ -24,14 +30,16 @@ describe('/sample', () => {
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); 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(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('_id').be.type('string');
should(sample).have.property('number').be.type('string'); should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string'); should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string'); should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').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('material_id').be.type('string');
should(sample).have.property('note_id'); should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string'); should(sample).have.property('user_id').be.type('string');
@ -48,17 +56,19 @@ describe('/sample', () => {
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); 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(material => { should(res.body).matchEach(sample => {
should(material).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(material).have.property('_id').be.type('string'); should(sample).have.property('_id').be.type('string');
should(material).have.property('number').be.type('string'); should(sample).have.property('number').be.type('string');
should(material).have.property('type').be.type('string'); should(sample).have.property('type').be.type('string');
should(material).have.property('color').be.type('string'); should(sample).have.property('color').be.type('string');
should(material).have.property('batch').be.type('string'); should(sample).have.property('batch').be.type('string');
should(material).have.property('material_id').be.type('string'); should(sample).have.property('condition').be.type('object');
should(material).have.property('note_id'); should(sample.condition).have.property('condition_template').be.type('string');
should(material).have.property('user_id').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(); done();
}); });
@ -83,25 +93,28 @@ describe('/sample', () => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); const json = require('../test/db.json');
let asyncCounter = res.body.length; 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(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('_id').be.type('string');
should(sample).have.property('number').be.type('string'); should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string'); should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string'); should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').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('material_id').be.type('string');
should(sample).have.property('note_id'); should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string'); should(sample).have.property('user_id').be.type('string');
SampleModel.findById(sample._id).lean().exec((err, data) => { 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) { if (--asyncCounter === 0) {
done(); done();
} }
}); });
}); });
done();
}); });
}); });
it('returns all deleted samples', done => { it('returns all deleted samples', done => {
@ -116,23 +129,26 @@ describe('/sample', () => {
let asyncCounter = res.body.length; let asyncCounter = res.body.length;
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === -1).length); should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === -1).length);
should(res.body).matchEach(sample => { 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('_id').be.type('string');
should(sample).have.property('number').be.type('string'); should(sample).have.property('number').be.type('string');
should(sample).have.property('type').be.type('string'); should(sample).have.property('type').be.type('string');
should(sample).have.property('color').be.type('string'); should(sample).have.property('color').be.type('string');
should(sample).have.property('batch').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('material_id').be.type('string');
should(sample).have.property('note_id'); should(sample).have.property('note_id');
should(sample).have.property('user_id').be.type('string'); should(sample).have.property('user_id').be.type('string');
SampleModel.findById(sample._id).lean().exec((err, data) => { 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) { if (--asyncCounter === 0) {
done(); done();
} }
}); });
}); });
done();
}); });
}); });
it('rejects requests from a write user', done => { it('rejects requests from a write user', done => {
@ -160,6 +176,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 => { // TODO: make tests work
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}', () => { describe('PUT /sample/{id}', () => {
it('returns the right sample', done => { it('returns the right sample', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
@ -168,7 +251,7 @@ describe('/sample', () => {
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, httpStatus: 200,
req: {}, 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 => { it('keeps unchanged properties', done => {
@ -177,21 +260,22 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, 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) => { }).end((err, res) => {
if (err) return done(err); 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) => { SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err); 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('_id');
should(data).have.property('number', '1'); should(data).have.property('number', '1');
should(data).have.property('color', 'black'); should(data).have.property('color', 'black');
should(data).have.property('type', 'granulate'); should(data).have.property('type', 'granulate');
should(data).have.property('batch', ''); 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.material_id.toString()).be.eql('100000000000000000000004');
should(data.user_id.toString()).be.eql('000000000000000000000002'); 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); should(data).have.property('note_id', null);
done(); done();
}); });
@ -206,10 +290,27 @@ describe('/sample', () => {
req: {type: 'granulate'} req: {type: 'granulate'}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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) => { SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err); 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(); done();
}); });
}); });
@ -223,18 +324,21 @@ describe('/sample', () => {
req: {notes: {comment: 'Stoff gesperrt', sample_references: []}} req: {notes: {comment: 'Stoff gesperrt', sample_references: []}}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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) => { SampleModel.findById('400000000000000000000002').lean().exec((err, data: any) => {
if (err) return done (err); 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('_id');
should(data).have.property('number', '21'); should(data).have.property('number', '21');
should(data).have.property('color', 'natural'); should(data).have.property('color', 'natural');
should(data).have.property('type', 'granulate'); should(data).have.property('type', 'granulate');
should(data).have.property('batch', '1560237365'); 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.material_id.toString()).be.eql('100000000000000000000001');
should(data.user_id.toString()).be.eql('000000000000000000000002'); 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'); should(data.note_id.toString()).be.eql('500000000000000000000001');
done(); done();
}); });
@ -246,20 +350,21 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, 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 => { }).end(err => {
if (err) return done (err); if (err) return done (err);
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done (err); 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('_id');
should(data).have.property('number', '1'); should(data).have.property('number', '1');
should(data).have.property('color', 'signalviolet'); should(data).have.property('color', 'signalviolet');
should(data).have.property('type', 'part'); should(data).have.property('type', 'part');
should(data).have.property('batch', '114531'); should(data).have.property('batch', '114531');
should(data).have.property('condition', {condition_template: '200000000000000000000003'});
should(data.material_id.toString()).be.eql('100000000000000000000002'); should(data.material_id.toString()).be.eql('100000000000000000000002');
should(data.user_id.toString()).be.eql('000000000000000000000002'); 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'); should(data).have.property('note_id');
NoteModel.findById(data.note_id).lean().exec((err, data: any) => { NoteModel.findById(data.note_id).lean().exec((err, data: any) => {
if (err) return done (err); if (err) return done (err);
@ -267,7 +372,7 @@ describe('/sample', () => {
should(data).have.property('comment', 'Testcomment'); should(data).have.property('comment', 'Testcomment');
should(data).have.property('sample_references'); should(data).have.property('sample_references');
should(data.sample_references).have.lengthOf(1); 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'); should(data.sample_references[0]).have.property('relation', 'part to this sample');
done(); done();
}); });
@ -350,7 +455,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Color not available for material'}
}); });
}); });
@ -360,7 +465,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Material not available'}
}); });
}); });
@ -370,7 +475,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"number" is not allowed'}
}); });
}); });
@ -380,7 +485,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Sample reference not available'}
}); });
}); });
@ -390,7 +495,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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}/'} res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
}); });
}); });
@ -400,7 +505,87 @@ describe('/sample', () => {
url: '/sample/10000000000h000000000001', url: '/sample/10000000000h000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 404, 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 => { it('rejects an API key', done => {
@ -409,7 +594,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {key: 'janedoe'}, auth: {key: 'janedoe'},
httpStatus: 401, 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 => { it('rejects changes for samples from another user for a write user', done => {
@ -428,7 +613,7 @@ describe('/sample', () => {
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {}, 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 => { it('rejects requests from a read user', done => {
@ -437,7 +622,7 @@ describe('/sample', () => {
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
auth: {basic: 'user'}, auth: {basic: 'user'},
httpStatus: 403, 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 => { it('returns 404 for an unknown sample', done => {
@ -446,7 +631,7 @@ describe('/sample', () => {
url: '/sample/000000000000000000000001', url: '/sample/000000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 404, 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 => { it('rejects unauthorized requests', done => {
@ -454,7 +639,7 @@ describe('/sample', () => {
method: 'put', method: 'put',
url: '/sample/400000000000000000000001', url: '/sample/400000000000000000000001',
httpStatus: 401, 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 +656,18 @@ describe('/sample', () => {
should(res.body).be.eql({status: 'OK'}); should(res.body).be.eql({status: 'OK'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
if (err) return done(err); 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('_id');
should(data).have.property('number', '1'); should(data).have.property('number', '1');
should(data).have.property('color', 'black'); should(data).have.property('color', 'black');
should(data).have.property('type', 'granulate'); should(data).have.property('type', 'granulate');
should(data).have.property('batch', ''); 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.material_id.toString()).be.eql('100000000000000000000004');
should(data.user_id.toString()).be.eql('000000000000000000000002'); 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); should(data).have.property('note_id', null);
done(); done();
}); });
@ -536,7 +724,7 @@ describe('/sample', () => {
NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => { NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('sample_references').with.lengthOf(1); 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'); should(data.sample_references[0]).have.property('relation', 'part to sample');
done(); done();
}); });
@ -555,7 +743,7 @@ describe('/sample', () => {
should(res.body).be.eql({status: 'OK'}); should(res.body).be.eql({status: 'OK'});
SampleModel.findById('400000000000000000000001').lean().exec((err, data) => { SampleModel.findById('400000000000000000000001').lean().exec((err, data) => {
if (err) return done(err); if (err) return done(err);
should(data).have.property('status', -1); should(data).have.property('status',globals.status.deleted);
done(); done();
}); });
}); });
@ -617,15 +805,16 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, 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) => { }).end((err, res) => {
if (err) return done (err); 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('_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('color', 'black');
should(res.body).have.property('type', 'granulate'); should(res.body).have.property('type', 'granulate');
should(res.body).have.property('batch', '1560237365'); 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('material_id', '100000000000000000000001');
should(res.body).have.property('note_id').be.type('string'); should(res.body).have.property('note_id').be.type('string');
should(res.body).have.property('user_id', '000000000000000000000002'); should(res.body).have.property('user_id', '000000000000000000000002');
@ -638,21 +827,22 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, 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 => { }).end(err => {
if (err) return done (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); if (err) return done (err);
should(data).have.lengthOf(1); 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('_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('color', 'black');
should(data[0]).have.property('type', 'granulate'); should(data[0]).have.property('type', 'granulate');
should(data[0]).have.property('batch', '1560237365'); 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].material_id.toString()).be.eql('100000000000000000000001');
should(data[0].user_id.toString()).be.eql('000000000000000000000002'); 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'); should(data[0]).have.property('note_id');
NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => { NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => {
if (err) return done (err); if (err) return done (err);
@ -660,7 +850,7 @@ describe('/sample', () => {
should(data).have.property('comment', 'Testcomment'); should(data).have.property('comment', 'Testcomment');
should(data).have.property('sample_references'); should(data).have.property('sample_references');
should(data.sample_references).have.lengthOf(1); 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'); should(data.sample_references[0]).have.property('relation', 'part to this sample');
done(); done();
}); });
@ -710,10 +900,10 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'johnnydoe'}, auth: {basic: 'johnnydoe'},
httpStatus: 200, 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) => { }).end((err, res) => {
if (err) return done (err); 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('_id').be.type('string');
should(res.body).have.property('number', 'Fe1'); should(res.body).have.property('number', 'Fe1');
should(res.body).have.property('color', 'black'); should(res.body).have.property('color', 'black');
@ -725,13 +915,35 @@ describe('/sample', () => {
done(); 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 => { it('rejects a color not defined for the material', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Color not available for material'}
}); });
}); });
@ -741,7 +953,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Material not available'}
}); });
}); });
@ -751,7 +963,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"number" is not allowed'}
}); });
}); });
@ -761,17 +973,97 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} 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 => { it('rejects a missing color', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"color" is required'}
}); });
}); });
@ -781,7 +1073,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"type" is required'}
}); });
}); });
@ -791,7 +1083,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"batch" is required'}
}); });
}); });
@ -801,7 +1093,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"material_id" is required'}
}); });
}); });
@ -811,7 +1103,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 400, 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}/'} res: {status: 'Invalid body format', details: '"material_id" with value "10000000000h000000000001" fails to match the required pattern: /[0-9a-f]{24}/'}
}); });
}); });
@ -821,7 +1113,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {key: 'janedoe'}, auth: {key: 'janedoe'},
httpStatus: 401, 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 => { it('rejects requests from a read user', done => {
@ -830,7 +1122,7 @@ describe('/sample', () => {
url: '/sample/new', url: '/sample/new',
auth: {basic: 'user'}, auth: {basic: 'user'},
httpStatus: 403, 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 => { it('rejects unauthorized requests', done => {
@ -838,7 +1130,7 @@ describe('/sample', () => {
method: 'post', method: 'post',
url: '/sample/new', url: '/sample/new',
httpStatus: 401, 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 NoteFieldValidate from './validate/note_field';
import res400 from './validate/res400'; import res400 from './validate/res400';
import SampleModel from '../models/sample' import SampleModel from '../models/sample'
import MeasurementModel from '../models/measurement';
import MaterialModel from '../models/material'; import MaterialModel from '../models/material';
import NoteModel from '../models/note'; import NoteModel from '../models/note';
import NoteFieldModel from '../models/note_field'; import NoteFieldModel from '../models/note_field';
import IdValidate from './validate/id'; 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(); const router = express.Router();
@ -16,7 +21,7 @@ const router = express.Router();
router.get('/samples', (req, res, next) => { router.get('/samples', (req, res, next) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; 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); if (err) return next(err);
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors 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) => { router.get('/samples/:group(new|deleted)', (req, res, next) => {
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
let status; SampleModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => {
switch (req.params.group) {
case 'new': status = 0;
break;
case 'deleted': status = -1;
break;
}
SampleModel.find({status: status}).lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors 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) => { 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 (!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')) { if (sample.hasOwnProperty('notes')) {
let newNotes = true; let newNotes = true;
if (sampleData.note_id !== null) { // old notes data exists if (sampleData.note_id !== null) { // old notes data exists
@ -89,10 +113,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
// check for changes // check for changes
if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) { 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); if (err) return next(err);
res.json(SampleValidate.output(data)); 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 // 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; 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 (err) return next(err);
if (sampleData.note_id !== null) { // handle notes if (sampleData.note_id !== null) { // handle notes
NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields 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) => { router.post('/sample/new', async (req, res, next) => {
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; 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'); const {error, value: sample} = SampleValidate.input(req.body, 'new');
if (error) return res400(error, res); 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); 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); sample.number = await numberGenerate(sample, req, res, next);
if (!sample.number) return; if (!sample.number) return;
@ -152,6 +184,7 @@ router.post('/sample/new', async (req, res, next) => {
delete sample.notes; delete sample.notes;
sample.note_id = data._id; sample.note_id = data._id;
sample.user_id = req.authDetails.id; sample.user_id = req.authDetails.id;
new SampleModel(sample).save((err, data) => { new SampleModel(sample).save((err, data) => {
if (err) return next(err); if (err) return next(err);
res.json(SampleValidate.output(data.toObject())); res.json(SampleValidate.output(data.toObject()));
@ -172,14 +205,15 @@ router.get('/sample/notes/fields', (req, res, next) => {
module.exports = router; 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 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() .lean()
.exec() .exec()
.catch(err => next(err)); .catch(err => next(err));
if (sampleData instanceof Error) return false; 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 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; 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 function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference
return new Promise(resolve => { return new Promise(resolve => {
if (sample.notes.sample_references.length > 0) { // there are sample_references 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 let referencesCount = sample.notes.sample_references.length; // count to keep track of running async operations
sample.notes.sample_references.forEach(reference => { 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 (err) {next(err); resolve(false)}
if (!data) { if (!data) {
res.status(400).json({status: 'Sample reference not available'}); 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); 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 => { NoteFieldModel.findOneAndDelete({name: field}).lean().exec(err => {
if (err) return console.error(err); if (err) return console.error(err);
}); });

View File

@ -1,10 +1,12 @@
import should from 'should/as-function'; import should from 'should/as-function';
import _ from 'lodash'; import _ from 'lodash';
import TemplateTreatmentModel from '../models/treatment_template'; import TemplateConditionModel from '../models/condition_template';
import TemplateMeasurementModel from '../models/measurement_template'; import TemplateMeasurementModel from '../models/measurement_template';
import TestHelper from "../test/helper"; import TestHelper from "../test/helper";
// TODO: do not allow usage of old templates for new samples // 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', () => { describe('/template', () => {
let server; let server;
@ -12,25 +14,24 @@ describe('/template', () => {
beforeEach(done => server = TestHelper.beforeEach(server, done)); beforeEach(done => server = TestHelper.beforeEach(server, done));
afterEach(done => TestHelper.afterEach(server, done)); afterEach(done => TestHelper.afterEach(server, done));
describe('/template/treatment', () => { describe('/template/condition', () => {
describe('GET /template/treatments', () => { describe('GET /template/conditions', () => {
it('returns all treatment templates', done => { it('returns all condition templates', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatments', url: '/template/conditions',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
const json = require('../test/db.json'); const json = require('../test/db.json');
should(res.body).have.lengthOf(json.collections.treatment_templates.length); should(res.body).have.lengthOf(json.collections.condition_templates.length);
should(res.body).matchEach(treatment => { should(res.body).matchEach(condition => {
should(treatment).have.only.keys('_id', 'name', 'version', 'parameters', 'number_prefix'); should(condition).have.only.keys('_id', 'name', 'version', 'parameters');
should(treatment).have.property('_id').be.type('string'); should(condition).have.property('_id').be.type('string');
should(treatment).have.property('name').be.type('string'); should(condition).have.property('name').be.type('string');
should(treatment).have.property('version').be.type('number'); should(condition).have.property('version').be.type('number');
should(treatment).have.property('number_prefix').be.type('string'); should(condition.parameters).matchEach(number => {
should(treatment.parameters).matchEach(number => {
should(number).have.only.keys('name', 'range'); should(number).have.only.keys('name', 'range');
should(number).have.property('name').be.type('string'); should(number).have.property('name').be.type('string');
should(number).have.property('range').be.type('object'); should(number).have.property('range').be.type('object');
@ -42,7 +43,7 @@ describe('/template', () => {
it('rejects an API key', done => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatments', url: '/template/conditions',
auth: {key: 'janedoe'}, auth: {key: 'janedoe'},
httpStatus: 401 httpStatus: 401
}); });
@ -50,26 +51,26 @@ describe('/template', () => {
it('rejects unauthorized requests', done => { it('rejects unauthorized requests', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatments', url: '/template/conditions',
httpStatus: 401 httpStatus: 401
}); });
}); });
}); });
describe('GET /template/treatment/{id}', () => { describe('GET /template/condition/{id}', () => {
it('returns the right treatment template', done => { it('returns the right condition template', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 200, 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 => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {key: 'janedoe'}, auth: {key: 'janedoe'},
httpStatus: 401 httpStatus: 401
}); });
@ -77,7 +78,7 @@ describe('/template', () => {
it('rejects an unknown id', done => { it('rejects an unknown id', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatment/000000000000000000000001', url: '/template/condition/000000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 404 httpStatus: 404
}); });
@ -85,58 +86,57 @@ describe('/template', () => {
it('rejects unauthorized requests', done => { it('rejects unauthorized requests', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
httpStatus: 401 httpStatus: 401
}); });
}); });
}); });
describe('PUT /template/treatment/{name}', () => { describe('PUT /template/condition/{name}', () => {
it('returns the right treatment template', done => { it('returns the right condition template', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {}, 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 => { it('keeps unchanged properties', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {name: 'heat treatment', parameters: [{name: 'material', range: {values: ['copper', 'hot air']}}, {name: 'weeks', range: {min: 1, max: 10}}]}, 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 => { it('keeps only one unchanged property', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {name: 'heat treatment'}, 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 => { it('changes the given properties', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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); 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('name', 'heat aging');
should(data).have.property('version', 2); should(data).have.property('version', 2);
should(data).have.property('number_prefix', 'A');
should(data).have.property('parameters').have.lengthOf(1); should(data).have.property('parameters').have.lengthOf(1);
should(data.parameters[0]).have.property('name', 'time'); should(data.parameters[0]).have.property('name', 'time');
should(data.parameters[0]).have.property('range'); should(data.parameters[0]).have.property('range');
@ -148,18 +148,17 @@ describe('/template', () => {
it('allows changing only one property', done => { it('allows changing only one property', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {name: 'heat aging'} req: {name: 'heat aging'}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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); 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('name', 'heat aging');
should(data).have.property('version', 2); should(data).have.property('version', 2);
should(data).have.property('number_prefix', 'A');
should(data).have.property('parameters').have.lengthOf(2); should(data).have.property('parameters').have.lengthOf(2);
should(data.parameters[0]).have.property('name', 'material'); should(data.parameters[0]).have.property('name', 'material');
should(data.parameters[1]).have.property('name', 'weeks'); should(data.parameters[1]).have.property('name', 'weeks');
@ -170,59 +169,59 @@ describe('/template', () => {
it('supports values ranges', done => { it('supports values ranges', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]} req: {parameters: [{name: 'time', range: {values: [1, 2, 5]}}]}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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(); done();
}); });
}); });
it('supports min max ranges', done => { it('supports min max ranges', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]} req: {parameters: [{name: 'time', range: {min: 1, max: 11}}]}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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(); done();
}); });
}); });
it('supports array type ranges', done => { it('supports array type ranges', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {parameters: [{name: 'time', range: {type: 'array'}}]} req: {parameters: [{name: 'time', range: {type: 'array'}}]}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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(); done();
}); });
}); });
it('supports empty ranges', done => { it('supports empty ranges', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, httpStatus: 200,
req: {parameters: [{name: 'time', range: {}}]} req: {parameters: [{name: 'time', range: {}}]}
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); 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(); done();
}); });
}); });
it('rejects not specified parameters', done => { it('rejects not specified parameters', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]}, req: {name: 'heat treatment', parameters: [{name: 'material', range: {xx: 5}}]},
@ -232,7 +231,7 @@ describe('/template', () => {
it('rejects an invalid id', done => { it('rejects an invalid id', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/2000000000h0000000000001', url: '/template/condition/2000000000h0000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 404, httpStatus: 404,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}
@ -241,26 +240,16 @@ describe('/template', () => {
it('rejects an unknown id', done => { it('rejects an unknown id', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/000000000000000000000001', url: '/template/condition/000000000000000000000001',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 404, httpStatus: 404,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]} 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 => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 401, httpStatus: 401,
req: {} req: {}
@ -269,7 +258,7 @@ describe('/template', () => {
it('rejects requests from a write user', done => { it('rejects requests from a write user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403, httpStatus: 403,
req: {} req: {}
@ -278,27 +267,26 @@ describe('/template', () => {
it('rejects unauthorized requests', done => { it('rejects unauthorized requests', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'put', method: 'put',
url: '/template/treatment/200000000000000000000001', url: '/template/condition/200000000000000000000001',
httpStatus: 401, httpStatus: 401,
req: {} req: {}
}); });
}); });
}); });
describe('POST /template/treatment/new', () => { describe('POST /template/condition/new', () => {
it('returns the right treatment template', done => { it('returns the right condition template', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, 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) => { }).end((err, res) => {
if (err) return done(err); 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('name', 'heat treatment3');
should(res.body).have.property('version', 1); 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).have.property('parameters').have.lengthOf(1);
should(res.body.parameters[0]).have.property('name', 'material'); should(res.body.parameters[0]).have.property('name', 'material');
should(res.body.parameters[0]).have.property('range'); should(res.body.parameters[0]).have.property('range');
@ -310,18 +298,17 @@ describe('/template', () => {
it('stores the template', done => { it('stores the template', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200, 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) => { }).end((err, res) => {
if (err) return done(err); 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); 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('name', 'heat aging');
should(data).have.property('version', 1); should(data).have.property('version', 1);
should(data).have.property('number_prefix', 'C');
should(data).have.property('parameters').have.lengthOf(1); should(data).have.property('parameters').have.lengthOf(1);
should(data.parameters[0]).have.property('name', 'time'); should(data.parameters[0]).have.property('name', 'time');
should(data.parameters[0]).have.property('range'); should(data.parameters[0]).have.property('range');
@ -333,117 +320,97 @@ describe('/template', () => {
it('rejects a missing name', done => { it('rejects a missing name', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, 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'} 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, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
req: {name: 'heat aging', parameters: [{name: 'time', range: {min: 1}}]}, req: {name: 'heat aging', number_prefix: 'C', parameters: [{name: 'time', range: {min: 1}}]},
res: {status: 'Invalid body format', details: '"number_prefix" is required'} res: {status: 'Invalid body format', details: '"number_prefix" is not allowed'}
}); });
}); });
it('rejects missing parameters', done => { it('rejects missing parameters', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
req: {name: 'heat aging', number_prefix: 'C'}, req: {name: 'heat aging'},
res: {status: 'Invalid body format', details: '"parameters" is required'} res: {status: 'Invalid body format', details: '"parameters" is required'}
}); });
}); });
it('rejects a missing parameter name', done => { it('rejects a missing parameter name', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, 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'} 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 => { it('rejects a missing parameter range', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"parameters[0].range" is required'}
}); });
}); });
it('rejects an invalid parameter range property', done => { it('rejects an invalid parameter range property', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, 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'} res: {status: 'Invalid body format', details: '"parameters[0].range.xx" is not allowed'}
}); });
}); });
it('rejects wrong properties', done => { it('rejects wrong properties', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, 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'} 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 => { it('rejects an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 401, 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 => { it('rejects requests from a write user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403, 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 => { it('rejects unauthorized requests', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'post', method: 'post',
url: '/template/treatment/new', url: '/template/condition/new',
httpStatus: 401, 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 _ from 'lodash';
import TemplateValidate from './validate/template'; import TemplateValidate from './validate/template';
import TemplateTreatmentModel from '../models/treatment_template'; import ConditionTemplateModel from '../models/condition_template';
import TemplateMeasurementModel from '../models/measurement_template'; import MeasurementTemplateModel from '../models/measurement_template';
import res400 from './validate/res400'; import res400 from './validate/res400';
import IdValidate from './validate/id'; import IdValidate from './validate/id';
@ -11,23 +11,23 @@ import IdValidate from './validate/id';
const router = express.Router(); 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; if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
model(req).find({}).lean().exec((err, data) => { model(req).find({}).lean().exec((err, data) => {
if (err) next (err); 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; if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
model(req).findById(req.params.id).lean().exec((err, data) => { model(req).findById(req.params.id).lean().exec((err, data) => {
if (err) next (err); if (err) next (err);
if (data) { if (data) {
res.json(TemplateValidate.output(data, req.params.collection)); res.json(TemplateValidate.output(data));
} }
else { else {
res.status(404).json({status: 'Not found'}); res.status(404).json({status: 'Not found'});
@ -35,10 +35,10 @@ 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; 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); if (error) return res400(error, res);
const templateData = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any; const templateData = await model(req).findById(req.params.id).lean().exec().catch(err => {next(err);}) as any;
@ -47,52 +47,34 @@ router.put('/template/:collection(measurement|treatment)/' + IdValidate.paramete
res.status(404).json({status: 'Not found'}); 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;
}
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed
template.version = templateData.version + 1; // increase version 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 await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => { // save new template, fill with old properties
if (err) next (err); if (err) next (err);
res.json(TemplateValidate.output(data.toObject(), req.params.collection)); res.json(TemplateValidate.output(data.toObject()));
}); });
} }
else { 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; 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 (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 template.version = 1; // set template version
await new (model(req))(template).save((err, data) => { await new (model(req))(template).save((err, data) => {
if (err) next (err); if (err) next (err);
res.json(TemplateValidate.output(data.toObject(), req.params.collection)); res.json(TemplateValidate.output(data.toObject()));
}); });
}); });
module.exports = router; 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 function model (req) { // return right template model
return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel; return req.params.collection === 'condition' ? ConditionTemplateModel : MeasurementTemplateModel;
} }

View File

@ -40,7 +40,6 @@ router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => {
const username = getUsername(req, res); const username = getUsername(req, res);
if (!username) return; if (!username) return;
console.log(username);
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : '')); const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
if (error) return res400(error, res); 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 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; const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
if (userData instanceof Error) return false; if (userData instanceof Error) return false;
console.log(userData);
console.log(UserValidate.isSpecialName(name));
if (userData || UserValidate.isSpecialName(name)) { if (userData || UserValidate.isSpecialName(name)) {
res.status(400).json({status: 'Username already taken'}); res.status(400).json({status: 'Username already taken'});
return false; 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'; import IdValidate from './id';
export default class MaterialValidate { // validate input for material export default class MaterialValidate { // validate input for material
private static material = { private static material = {
name: joi.string() name: Joi.string()
.max(128), .max(128),
supplier: joi.string() supplier: Joi.string()
.max(128), .max(128),
group: joi.string() group: Joi.string()
.max(128), .max(128),
mineral: joi.number() mineral: Joi.number()
.integer() .integer()
.min(0) .min(0)
.max(100), .max(100),
glass_fiber: joi.number() glass_fiber: Joi.number()
.integer() .integer()
.min(0) .min(0)
.max(100), .max(100),
carbon_fiber: joi.number() carbon_fiber: Joi.number()
.integer() .integer()
.min(0) .min(0)
.max(100), .max(100),
numbers: joi.array() numbers: Joi.array()
.items(joi.object({ .items(Joi.object({
color: joi.string() color: Joi.string()
.max(128) .max(128)
.required(), .required(),
number: joi.string() number: Joi.string()
.max(128) .max(128)
.allow('') .allow('')
.required() .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 static input (data, param) { // validate input, set param to 'new' to make all attributes required
if (param === 'new') { if (param === 'new') {
return joi.object({ return Joi.object({
name: this.material.name.required(), name: this.material.name.required(),
supplier: this.material.supplier.required(), supplier: this.material.supplier.required(),
group: this.material.group.required(), group: this.material.group.required(),
@ -53,7 +53,7 @@ export default class MaterialValidate { // validate input for material
}).validate(data); }).validate(data);
} }
else if (param === 'change') { else if (param === 'change') {
return joi.object({ return Joi.object({
name: this.material.name, name: this.material.name,
supplier: this.material.supplier, supplier: this.material.supplier,
group: this.material.group, 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 static output (data) { // validate output and strip unwanted properties, returns null if not valid
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
const {value, error} = joi.object({ const {value, error} = Joi.object({
_id: IdValidate.get(), _id: IdValidate.get(),
name: this.material.name, name: this.material.name,
supplier: this.material.supplier, supplier: this.material.supplier,
@ -82,4 +82,17 @@ export default class MaterialValidate { // validate input for material
}).validate(data, {stripUnknown: true}); }).validate(data, {stripUnknown: true});
return error !== undefined? null : value; 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

@ -1,6 +1,8 @@
import Joi from '@hapi/joi'; import Joi from '@hapi/joi';
import IdValidate from './id'; import IdValidate from './id';
import UserValidate from './user';
import MaterialValidate from './material';
export default class SampleValidate { export default class SampleValidate {
private static sample = { private static sample = {
@ -17,13 +19,16 @@ export default class SampleValidate {
.max(128) .max(128)
.allow(''), .allow(''),
condition: Joi.object(),
notes: Joi.object({ notes: Joi.object({
comment: Joi.string() comment: Joi.string()
.max(512), .max(512)
.allow(''),
sample_references: Joi.array() sample_references: Joi.array()
.items(Joi.object({ .items(Joi.object({
id: IdValidate.get(), sample_id: IdValidate.get(),
relation: Joi.string() relation: Joi.string()
.max(128) .max(128)
@ -47,6 +52,7 @@ export default class SampleValidate {
color: this.sample.color.required(), color: this.sample.color.required(),
type: this.sample.type.required(), type: this.sample.type.required(),
batch: this.sample.batch.required(), batch: this.sample.batch.required(),
condition: this.sample.condition.required(),
material_id: IdValidate.get().required(), material_id: IdValidate.get().required(),
notes: this.sample.notes.required() notes: this.sample.notes.required()
}).validate(data); }).validate(data);
@ -56,6 +62,7 @@ export default class SampleValidate {
color: this.sample.color, color: this.sample.color,
type: this.sample.type, type: this.sample.type,
batch: this.sample.batch, batch: this.sample.batch,
condition: this.sample.condition,
material_id: IdValidate.get(), material_id: IdValidate.get(),
notes: this.sample.notes, notes: this.sample.notes,
}).validate(data); }).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); data = IdValidate.stringify(data);
const {value, error} = Joi.object({ let joiObject;
_id: IdValidate.get(), if (param === 'refs') {
number: this.sample.number, joiObject = {
color: this.sample.color, _id: IdValidate.get(),
type: this.sample.type, number: this.sample.number,
batch: this.sample.batch, color: this.sample.color,
material_id: IdValidate.get(), type: this.sample.type,
note_id: IdValidate.get().allow(null), batch: this.sample.batch,
user_id: IdValidate.get() condition: this.sample.condition,
}).validate(data, {stripUnknown: true}); 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; return error !== undefined? null : value;
} }
} }

View File

@ -9,11 +9,6 @@ export default class TemplateValidate {
version: Joi.number() version: Joi.number()
.min(1), .min(1),
number_prefix: Joi.string()
.pattern(/^[a-zA-Z]+$/)
.min(1)
.max(16),
parameters: Joi.array() parameters: Joi.array()
.min(1) .min(1)
.items( .items(
@ -43,63 +38,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 (param === 'new') {
if (template === 'treatment') { return Joi.object({
return Joi.object({ name: this.template.name.required(),
name: this.template.name.required(), parameters: this.template.parameters.required()
number_prefix: this.template.number_prefix.required(), }).validate(data);
parameters: this.template.parameters.required()
}).validate(data);
}
else {
return Joi.object({
name: this.template.name.required(),
parameters: this.template.parameters.required()
}).validate(data);
}
} }
else if (param === 'change') { else if (param === 'change') {
if (template === 'treatment') { return Joi.object({
return Joi.object({ name: this.template.name,
name: this.template.name, parameters: this.template.parameters
number_prefix: this.template.number_prefix, }).validate(data);
parameters: this.template.parameters
}).validate(data);
}
else {
return Joi.object({
name: this.template.name,
parameters: this.template.parameters
}).validate(data);
}
} }
else { else {
return{error: 'No parameter specified!', value: {}}; 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); data = IdValidate.stringify(data);
let joiObject; const {value, error} = Joi.object({
if (template === 'treatment') { // differentiate between measurement and treatment (has number_prefix) template _id: IdValidate.get(),
joiObject = { name: this.template.name,
_id: IdValidate.get(), version: this.template.version,
name: this.template.name, parameters: this.template.parameters
version: this.template.version, }).validate(data, {stripUnknown: true});
number_prefix: this.template.number_prefix,
parameters: this.template.parameters
};
}
else {
joiObject = {
_id: IdValidate.get(),
name: this.template.name,
version: this.template.version,
parameters: this.template.parameters
};
}
const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
return error !== undefined? null : value; 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 static isSpecialName (name) { // true if name belongs to special names
return this.specialUsernames.indexOf(name) > -1; return this.specialUsernames.indexOf(name) > -1;
} }
static username() {
return this.user.name;
}
} }

View File

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

View File

@ -10,6 +10,7 @@ export default class TestHelper {
user: {pass: 'Xyz890*)', key: '000000000000000000001001'}, user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'} johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
} }
public static res = { // default responses public static res = { // default responses
400: {status: 'Bad request'}, 400: {status: 'Bad request'},
401: {status: 'Unauthorized'}, 401: {status: 'Unauthorized'},