Merge pull request #10 in ~VLE2FE/dfop-api from minor-features to develop
* commit 'aef275322955774e0c34c69ce3c4e7722aadcb9e': added TODOs, improved password validation refactored user.ts /materials/new|deleted sample number generation number prefixes are now not allowed to contain numbers material numbers defined as string, colors without numbers and numbers with leading zeros can be added material numbers defined as string, colors without numbers can be added number generation for condition done changed allowed characters for username
This commit is contained in:
commit
2829752d0c
13
.idea/libraries/dist.xml
Normal file
13
.idea/libraries/dist.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<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>
|
@ -4,7 +4,7 @@
|
||||
get:
|
||||
summary: condition by id
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
|
||||
tags:
|
||||
- /condition
|
||||
responses:
|
||||
@ -38,9 +38,6 @@
|
||||
allOf:
|
||||
- $ref: 'api.yaml#/components/schemas/_Id'
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
example: B1
|
||||
parameters:
|
||||
type: object
|
||||
responses:
|
||||
|
@ -2,7 +2,30 @@
|
||||
get:
|
||||
summary: lists all materials
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: returns only materials with status 10 # TODO: methods /materials/new|deleted
|
||||
x-doc: returns only materials with status 10
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
200:
|
||||
description: all material details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/Material'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
|
||||
/materials/{group}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Group'
|
||||
get:
|
||||
summary: lists all new/deleted materials
|
||||
description: 'Auth: basic, levels: maintain, admin'
|
||||
x-doc: returns materials with status 0/-1
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
@ -25,7 +48,7 @@
|
||||
get:
|
||||
summary: get material details
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
|
||||
tags:
|
||||
- /material
|
||||
responses:
|
||||
|
@ -4,7 +4,7 @@
|
||||
get:
|
||||
summary: measurement values by id
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
|
||||
tags:
|
||||
- /measurement
|
||||
responses:
|
||||
|
@ -5,10 +5,20 @@ Id:
|
||||
schema:
|
||||
type: string
|
||||
example: 5ea0450ed851c30a90e70894
|
||||
|
||||
Name:
|
||||
name: name
|
||||
description: has to be URL encoded
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
type: string
|
||||
|
||||
Group:
|
||||
name: group
|
||||
description: 'possible values: new, deleted'
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: deleted
|
@ -2,7 +2,7 @@
|
||||
get:
|
||||
summary: all samples in overview
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: returns only samples with status 10 # TODO: methods /samples/new|deleted
|
||||
x-doc: returns only samples with status 10
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
@ -18,13 +18,37 @@
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
|
||||
/samples{group}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Group'
|
||||
get:
|
||||
summary: all new/deleted samples in overview
|
||||
description: 'Auth: basic, levels: maintain, admin'
|
||||
x-doc: returns only samples with status 0/-1
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
200:
|
||||
description: samples overview
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'api.yaml#/components/schemas/SampleRefs'
|
||||
401:
|
||||
$ref: 'api.yaml#/components/responses/401'
|
||||
500:
|
||||
$ref: 'api.yaml#/components/responses/500'
|
||||
|
||||
/sample/{id}:
|
||||
parameters:
|
||||
- $ref: 'api.yaml#/components/parameters/Id'
|
||||
get:
|
||||
summary: TODO sample details
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO
|
||||
x-doc: status handling (accessible (only for maintain/admin))? # TODO after decision
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
@ -130,7 +154,7 @@
|
||||
get:
|
||||
summary: list all existing field names for custom notes fields
|
||||
description: 'Auth: all, levels: read, write, maintain, dev, admin'
|
||||
x-doc: integrity has to be ensured # TODO: implement mechanism to regularly check note_fields
|
||||
x-doc: integrity has to be ensured
|
||||
tags:
|
||||
- /sample
|
||||
responses:
|
||||
|
@ -16,6 +16,7 @@ SampleProperties:
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
readOnly: true
|
||||
example: Rng172
|
||||
type:
|
||||
type: string
|
||||
@ -111,7 +112,7 @@ Material:
|
||||
- $ref: 'api.yaml#/components/schemas/Color'
|
||||
properties:
|
||||
number:
|
||||
type: number
|
||||
type: string
|
||||
example: 5514263423
|
||||
|
||||
Condition:
|
||||
@ -122,6 +123,7 @@ Condition:
|
||||
$ref: 'api.yaml#/components/schemas/Id'
|
||||
number:
|
||||
type: string
|
||||
readOnly: true
|
||||
example: B1
|
||||
parameters:
|
||||
type: object
|
||||
|
12
src/api.ts
12
src/api.ts
@ -4,7 +4,7 @@ import oasParser from '@apidevtools/swagger-parser';
|
||||
|
||||
|
||||
// modifies the normal swagger-ui-express package
|
||||
// usage: app.use('/api', api.serve(), api.setup());
|
||||
// usage: app.use('/api-doc', api.serve(), api.setup());
|
||||
// the paths property can be split using allOf
|
||||
// further route documentation can be included in the x-doc property
|
||||
|
||||
@ -20,7 +20,7 @@ export default class api {
|
||||
apiDoc = doc;
|
||||
apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e)); // bundle routes
|
||||
apiDoc = this.resolveXDoc(apiDoc);
|
||||
oasParser.validate(apiDoc, (err, api) => {
|
||||
oasParser.validate(apiDoc, (err, api) => { // validate oas schema
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@ -35,8 +35,8 @@ export default class api {
|
||||
|
||||
private static resolveXDoc (doc) { // resolve x-doc properties recursively
|
||||
Object.keys(doc).forEach(key => {
|
||||
if (doc[key] !== null && doc[key].hasOwnProperty('x-doc')) {
|
||||
doc[key].description += this.addHtml(doc[key]['x-doc']);
|
||||
if (doc[key] !== null && doc[key].hasOwnProperty('x-doc')) { // add x-doc to description, is styled via css
|
||||
doc[key].description += '<details class="docs"><summary>docs</summary>' + doc[key]['x-doc'] + '</details>';
|
||||
}
|
||||
else if (typeof doc[key] === 'object' && doc[key] !== null) { // go deeper into recursion
|
||||
doc[key] = this.resolveXDoc(doc[key]);
|
||||
@ -44,8 +44,4 @@ export default class api {
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static addHtml (text) { // add docs HTML
|
||||
return '<details class="docs"><summary>docs</summary>' + text + '</details>';
|
||||
}
|
||||
}
|
10
src/db.ts
10
src/db.ts
@ -13,7 +13,7 @@ export default class db {
|
||||
mode: null,
|
||||
};
|
||||
|
||||
static connect (mode = '', done: Function = () => {}) { // set mode to test for unit/integration tests, otherwise skip parameter. done is also only needed for testing
|
||||
static connect (mode = '', done: Function = () => {}) { // set mode to test for unit/integration tests, otherwise skip parameters. done is also only needed for testing
|
||||
if (this.state.db) return done(); // db is already connected
|
||||
|
||||
// find right connection url
|
||||
@ -84,9 +84,9 @@ export default class db {
|
||||
}
|
||||
|
||||
static loadJson (json, done: Function = () => {}) { // insert given JSON data into db, uses core mongodb methods
|
||||
if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) {
|
||||
if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) { // no db connection or nothing to load
|
||||
return done();
|
||||
} // no db connection or nothing to load
|
||||
}
|
||||
|
||||
let loadCounter = 0; // count number of loaded collections to know when to return done()
|
||||
Object.keys(json.collections).forEach(collectionName => { // create each collection
|
||||
@ -103,10 +103,10 @@ export default class db {
|
||||
|
||||
private static oidResolve (object: any) { // resolve $oid fields to actual ObjectIds recursively
|
||||
Object.keys(object).forEach(key => {
|
||||
if (object[key] !== null && object[key].hasOwnProperty('$oid')) {
|
||||
if (object[key] !== null && object[key].hasOwnProperty('$oid')) { // found oid, replace
|
||||
object[key] = mongoose.Types.ObjectId(object[key].$oid);
|
||||
}
|
||||
else if (typeof object[key] === 'object' && object[key] !== null) {
|
||||
else if (typeof object[key] === 'object' && object[key] !== null) { // deeper into recursion
|
||||
object[key] = this.oidResolve(object[key]);
|
||||
}
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ import UserModel from '../models/user';
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
let givenMethod = ''; // authorization method given by client, basic taken preferred
|
||||
let user = {name: '', level: '', id: ''}; // user object
|
||||
let user = {name: '', level: '', id: '', location: ''}; // user object
|
||||
|
||||
// test authentications
|
||||
const userBasic = await basic(req, next);
|
||||
@ -46,7 +46,8 @@ module.exports = async (req, res, next) => {
|
||||
method: givenMethod,
|
||||
username: user.name,
|
||||
level: user.level,
|
||||
id: user.id
|
||||
id: user.id,
|
||||
location: user.location
|
||||
};
|
||||
|
||||
next();
|
||||
@ -62,8 +63,8 @@ function basic (req, next): any { // checks basic auth and returns changed user
|
||||
if (data.length === 1) { // one user found
|
||||
bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password
|
||||
if (err) return next(err);
|
||||
if (res === true) {
|
||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString()});
|
||||
if (res === true) { // password correct
|
||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString(), location: data[0].location});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
@ -83,11 +84,11 @@ function basic (req, next): any { // checks basic auth and returns changed user
|
||||
|
||||
function key (req, next): any { // checks API key and returns changed user object
|
||||
return new Promise(resolve => {
|
||||
if (req.query.key !== undefined) {
|
||||
if (req.query.key !== undefined) { // key available
|
||||
UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => { // find user
|
||||
if (err) return next(err);
|
||||
if (data.length === 1) { // one user found
|
||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString()});
|
||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString(), location: data[0].location});
|
||||
}
|
||||
else {
|
||||
resolve(null);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// sends an email
|
||||
// sends an email using the BIC service
|
||||
|
||||
export default (mailAddress, subject, content, f) => { // callback, executed empty or with error
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
22
src/index.ts
22
src/index.ts
@ -5,6 +5,12 @@ import mongoSanitize from 'mongo-sanitize';
|
||||
import api from './api';
|
||||
import db from './db';
|
||||
|
||||
// TODO: changelog
|
||||
// TODO: check executing index.js/move everything needed into dist
|
||||
// TODO: One condition per sample
|
||||
// TODO: validation: VZ, Humidity: min/max value, DPT: filename
|
||||
// TODO: condition values not needed on initial add
|
||||
// TODO: add multiple samples at once
|
||||
|
||||
// tell if server is running in debug or production environment
|
||||
console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
|
||||
@ -43,19 +49,19 @@ app.use((req, res, next) => { // no database connection error
|
||||
app.use(require('./helpers/authorize')); // handle authentication
|
||||
|
||||
// require routes
|
||||
app.use('/', require('./routes/root'));
|
||||
app.use('/', require('./routes/sample'));
|
||||
app.use('/', require('./routes/material'));
|
||||
app.use('/', require('./routes/template'));
|
||||
app.use('/', require('./routes/user'));
|
||||
app.use('/', require('./routes/condition'));
|
||||
app.use('/', require('./routes/measurement'));
|
||||
app.use('/api', require('./routes/root'));
|
||||
app.use('/api', require('./routes/sample'));
|
||||
app.use('/api', require('./routes/material'));
|
||||
app.use('/api', require('./routes/template'));
|
||||
app.use('/api', require('./routes/user'));
|
||||
app.use('/api', require('./routes/condition'));
|
||||
app.use('/api', require('./routes/measurement'));
|
||||
|
||||
// static files
|
||||
app.use('/static', express.static('static'));
|
||||
|
||||
// Swagger UI
|
||||
app.use('/api', api.serve(), api.setup());
|
||||
app.use('/api-doc', api.serve(), api.setup());
|
||||
|
||||
app.use((req, res) => { // 404 error handling
|
||||
res.status(404).json({status: 'Not found'});
|
||||
|
@ -9,9 +9,9 @@ const MaterialSchema = new mongoose.Schema({
|
||||
carbon_fiber: String,
|
||||
numbers: [{
|
||||
color: String,
|
||||
number: Number
|
||||
number: String
|
||||
}],
|
||||
status: Number
|
||||
});
|
||||
}, {minimize: false});
|
||||
|
||||
export default mongoose.model('material', MaterialSchema);
|
@ -2,6 +2,10 @@ 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;
|
||||
@ -16,7 +20,7 @@ describe('/condition', () => {
|
||||
url: '/condition/700000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
|
||||
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 => {
|
||||
@ -25,7 +29,7 @@ describe('/condition', () => {
|
||||
url: '/condition/700000000000000000000001',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', parameters: {material: 'copper', weeks: 3}, treatment_template: '200000000000000000000001'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid id', done => {
|
||||
@ -61,7 +65,7 @@ describe('/condition', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {},
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}}
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}}
|
||||
});
|
||||
});
|
||||
it('keeps unchanged properties', done => {
|
||||
@ -73,7 +77,7 @@ describe('/condition', () => {
|
||||
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: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}});
|
||||
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);
|
||||
@ -90,7 +94,7 @@ describe('/condition', () => {
|
||||
req: {parameters: {material: 'copper'}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 3}});
|
||||
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);
|
||||
@ -107,12 +111,12 @@ describe('/condition', () => {
|
||||
req: {parameters: {material: 'hot air', weeks: 10}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}});
|
||||
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', 'B1');
|
||||
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');
|
||||
@ -129,7 +133,17 @@ describe('/condition', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: {weeks: 8}},
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'copper', weeks: 8}}
|
||||
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 => {
|
||||
@ -198,7 +212,7 @@ describe('/condition', () => {
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {parameters: {material: 'hot air', weeks: 10}},
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'B1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}}
|
||||
res: {_id: '700000000000000000000001', sample_id: '400000000000000000000001', number: 'A1', treatment_template: '200000000000000000000001', parameters: {material: 'hot air', weeks: 10}}
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
@ -227,34 +241,41 @@ describe('/condition', () => {
|
||||
req: {parameters: {material: 'hot air', weeks: 10}}
|
||||
});
|
||||
});
|
||||
}); // TODO: how to deal with template changes? Template versioning?
|
||||
// TODO: rewrite delete methods -> set status for every database collection
|
||||
});
|
||||
|
||||
describe('DELETE /condition/{id}', () => {
|
||||
it('sets the status to deleted', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/condition/700000000000000000000002',
|
||||
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('700000000000000000000002').lean().exec((err, data: any) => {
|
||||
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('400000000000000000000002');
|
||||
should(data).have.property('number', 'B1');
|
||||
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', 'copper');
|
||||
should(data.parameters).have.property('weeks', 3);
|
||||
should(data.parameters).have.property('material', 'hot air');
|
||||
should(data.parameters).have.property('weeks', 5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects a deleting a condition referenced by measurements'); // TODO
|
||||
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',
|
||||
@ -266,7 +287,7 @@ describe('/condition', () => {
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/condition/700000000000000000000002',
|
||||
url: '/condition/700000000000000000000004',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401
|
||||
});
|
||||
@ -274,7 +295,7 @@ describe('/condition', () => {
|
||||
it('rejects requests from a read user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/condition/700000000000000000000002',
|
||||
url: '/condition/700000000000000000000004',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403
|
||||
});
|
||||
@ -290,7 +311,7 @@ describe('/condition', () => {
|
||||
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/700000000000000000000002',
|
||||
url: '/condition/700000000000000000000004',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
@ -302,7 +323,7 @@ describe('/condition', () => {
|
||||
it('returns 404 for an unknown id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/condition/00000000000w000000000002',
|
||||
url: '/condition/000000000000000000000002',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404
|
||||
});
|
||||
@ -310,26 +331,26 @@ describe('/condition', () => {
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
url: '/condition/700000000000000000000002',
|
||||
url: '/condition/700000000000000000000004',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /condition/new', () => { // TODO: sample number generation
|
||||
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', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
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', 'B2');
|
||||
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');
|
||||
@ -343,14 +364,37 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
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', 'B2');
|
||||
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');
|
||||
@ -366,7 +410,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '4000000000h0000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
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}/'}
|
||||
});
|
||||
});
|
||||
@ -376,7 +420,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '000000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
req: {sample_id: '000000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
res: {status: 'Sample id not available'}
|
||||
});
|
||||
});
|
||||
@ -386,7 +430,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000h00000000001'},
|
||||
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}/'}
|
||||
});
|
||||
});
|
||||
@ -396,18 +440,18 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '000000000000000000000001'},
|
||||
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '000000000000000000000001'},
|
||||
res: {status: 'Treatment template not available'}
|
||||
});
|
||||
});
|
||||
it('rejects a condition number already in use for this sample', done => {
|
||||
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: 'B1', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
res: {status: 'Condition number already taken'}
|
||||
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 => {
|
||||
@ -416,7 +460,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10, xx: 12}, treatment_template: '200000000000000000000001'},
|
||||
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'}
|
||||
});
|
||||
});
|
||||
@ -426,7 +470,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air'}, treatment_template: '200000000000000000000001'},
|
||||
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air'}, treatment_template: '200000000000000000000001'},
|
||||
res: {status: 'Invalid body format', details: '"weeks" is required'}
|
||||
});
|
||||
});
|
||||
@ -436,7 +480,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'xxx', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
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]'}
|
||||
});
|
||||
});
|
||||
@ -446,7 +490,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: -10}, treatment_template: '200000000000000000000001'},
|
||||
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'}
|
||||
});
|
||||
});
|
||||
@ -456,7 +500,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 11}, treatment_template: '200000000000000000000001'},
|
||||
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'}
|
||||
});
|
||||
});
|
||||
@ -466,7 +510,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
req: {parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'},
|
||||
res: {status: 'Invalid body format', details: '"sample_id" is required'}
|
||||
});
|
||||
});
|
||||
@ -476,27 +520,17 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}},
|
||||
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}},
|
||||
res: {status: 'Invalid body format', details: '"treatment_template" is required'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing number', 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: '"number" 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', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
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 => {
|
||||
@ -505,13 +539,13 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
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', 'B2');
|
||||
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');
|
||||
@ -525,7 +559,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a read user', done => {
|
||||
@ -534,7 +568,7 @@ describe('/condition', () => {
|
||||
url: '/condition/new',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
@ -542,7 +576,7 @@ describe('/condition', () => {
|
||||
method: 'post',
|
||||
url: '/condition/new',
|
||||
httpStatus: 401,
|
||||
req: {sample_id: '400000000000000000000002', number: 'B2', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
req: {sample_id: '400000000000000000000002', parameters: {material: 'hot air', weeks: 10}, treatment_template: '200000000000000000000001'}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
import _ from 'lodash';
|
||||
|
||||
import ConditionValidate from './validate/condition';
|
||||
@ -38,13 +37,14 @@ router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => {
|
||||
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)) {
|
||||
if (!_.isEqual(condition.parameters, data.parameters)) { // parameters did not change
|
||||
condition.status = 0;
|
||||
}
|
||||
}
|
||||
@ -79,10 +79,12 @@ router.post('/condition/new', async (req, res, next) => {
|
||||
if (error) return res400(error, res);
|
||||
|
||||
if (!await sampleIdCheck(condition, req, res, next)) return;
|
||||
if (!await numberCheck(condition, res, next)) return;
|
||||
if (!await treatmentCheck(condition, 'new', res, next)) return;
|
||||
const treatmentData = await treatmentCheck(condition, 'new', res, next)
|
||||
if (!treatmentData) return;
|
||||
|
||||
condition.status = 0;
|
||||
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()));
|
||||
@ -104,24 +106,28 @@ async function sampleIdCheck (condition, req, res, next) { // validate sample_i
|
||||
return true;
|
||||
}
|
||||
|
||||
async function numberCheck (condition, res, next) { // validate number, returns false if invalid
|
||||
const data = await ConditionModel.find({sample_id: new mongoose.Types.ObjectId(condition.sample_id), number: condition.number}).lean().exec().catch(err => {next(err); return false;}) as any;
|
||||
if (data.length) {
|
||||
res.status(400).json({status: 'Condition number already taken'});
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
const treatmentData = await TreatmentTemplateModel.findById(condition.treatment_template).lean().exec().catch(err => {next(err); return false;}) as any;
|
||||
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
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate parameters
|
||||
const {error, value: ignore} = ParametersValidate.input(condition.parameters, treatmentData.parameters, param);
|
||||
if (error) {res400(error, res); return false;}
|
||||
return true;
|
||||
return treatmentData;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import should from 'should/as-function';
|
||||
import _ from 'lodash';
|
||||
import MaterialModel from '../models/material';
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
// TODO: numbers with color only (no number)
|
||||
// TODO: deal with numbers with leading zeros
|
||||
// TODO: color name must be unique to get color number
|
||||
// TODO: separate supplier/ material name into own collections
|
||||
|
||||
describe('/material', () => {
|
||||
let server;
|
||||
@ -21,7 +22,6 @@ describe('/material', () => {
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
console.log(res.body);
|
||||
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
@ -35,7 +35,7 @@ describe('/material', () => {
|
||||
should(material.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color').be.type('string');
|
||||
should(number).have.property('number').be.type('number');
|
||||
should(number).have.property('number').be.type('string');
|
||||
});
|
||||
});
|
||||
done();
|
||||
@ -63,7 +63,7 @@ describe('/material', () => {
|
||||
should(material.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color').be.type('string');
|
||||
should(number).have.property('number').be.type('number');
|
||||
should(number).have.property('number').be.type('string');
|
||||
});
|
||||
});
|
||||
done();
|
||||
@ -78,6 +78,101 @@ describe('/material', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /materials/{group}', () => {
|
||||
it('returns all new materials', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
let asyncCounter = res.body.length;
|
||||
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 0).length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('name').be.type('string');
|
||||
should(material).have.property('supplier').be.type('string');
|
||||
should(material).have.property('group').be.type('string');
|
||||
should(material).have.property('mineral').be.type('number');
|
||||
should(material).have.property('glass_fiber').be.type('number');
|
||||
should(material).have.property('carbon_fiber').be.type('number');
|
||||
should(material.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color').be.type('string');
|
||||
should(number).have.property('number').be.type('string');
|
||||
});
|
||||
MaterialModel.findById(material._id).lean().exec((err, data) => {
|
||||
should(data).have.property('status', 0);
|
||||
if (--asyncCounter === 0) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('returns all deleted materials', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials/deleted',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
let asyncCounter = res.body.length;
|
||||
should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === -1).length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('name').be.type('string');
|
||||
should(material).have.property('supplier').be.type('string');
|
||||
should(material).have.property('group').be.type('string');
|
||||
should(material).have.property('mineral').be.type('number');
|
||||
should(material).have.property('glass_fiber').be.type('number');
|
||||
should(material).have.property('carbon_fiber').be.type('number');
|
||||
should(material.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color').be.type('string');
|
||||
should(number).have.property('number').be.type('string');
|
||||
});
|
||||
MaterialModel.findById(material._id).lean().exec((err, data) => {
|
||||
should(data).have.property('status', -1);
|
||||
if (--asyncCounter === 0) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects requests from a write user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials/deleted',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/materials/new',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /material/{id}', () => {
|
||||
it('returns the right material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
@ -85,7 +180,7 @@ describe('/material', () => {
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_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}]}
|
||||
res: {_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'}]}
|
||||
});
|
||||
});
|
||||
it('returns the right material for an API key', done => {
|
||||
@ -97,6 +192,15 @@ describe('/material', () => {
|
||||
res: {_id: '100000000000000000000003', name: 'PA GF 50 black (2706)', supplier: 'Akro-Plastic', group: 'PA66+PA6I/6T', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: []}
|
||||
});
|
||||
});
|
||||
it('returns a material with a color without number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/material/100000000000000000000007',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
res: {_id: '100000000000000000000007', name: 'Ultramid A4H', supplier: 'BASF', group: 'PA66', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: [{color: 'black', number: ''}]}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
@ -130,7 +234,7 @@ describe('/material', () => {
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {},
|
||||
res: {_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}]}
|
||||
res: {_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'}]}
|
||||
});
|
||||
});
|
||||
it('keeps unchanged properties', done => {
|
||||
@ -139,10 +243,10 @@ describe('/material', () => {
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {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}]}
|
||||
req: {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'}]}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
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) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.property('status', 10);
|
||||
@ -159,7 +263,7 @@ describe('/material', () => {
|
||||
req: {name: 'Stanyl TW 200 F8'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
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) => {
|
||||
if (err) return done(err);
|
||||
should(data).have.property('status', 10);
|
||||
@ -173,20 +277,30 @@ describe('/material', () => {
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}]}
|
||||
req: {name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]}
|
||||
,
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}]});
|
||||
should(res.body).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}]});
|
||||
MaterialModel.findById('100000000000000000000001').lean().exec((err, data:any) => {
|
||||
if (err) return done(err);
|
||||
data._id = data._id.toString();
|
||||
data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
|
||||
should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], status: 0, __v: 0});
|
||||
should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: 0, __v: 0});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('accepts a color without number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000007',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {numbers: [{color: 'black', number: ''}, {color: 'natural', number: ''}]},
|
||||
res: {_id: '100000000000000000000007', name: 'Ultramid A4H', supplier: 'BASF', group: 'PA66', mineral: 0, glass_fiber: 0, carbon_fiber: 0, numbers: [{color: 'black', number: ''}, {color: 'natural', number: ''}]}
|
||||
});
|
||||
})
|
||||
it('rejects already existing material names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
@ -233,20 +347,10 @@ describe('/material', () => {
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {numbers: [{colorxx: 'black', number: 55}]},
|
||||
req: {numbers: [{colorxx: 'black', number: '55'}]},
|
||||
res: {status: 'Invalid body format', details: '"numbers[0].color" is required'}
|
||||
});
|
||||
});
|
||||
it('rejects a wrong color number property', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/material/100000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {numbers: [{color: 'black', number: 'xxx'}]},
|
||||
res: {status: 'Invalid body format', details: '"numbers[0].number" must be a number'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid id', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
@ -307,7 +411,7 @@ describe('/material', () => {
|
||||
if (err) return done(err);
|
||||
data._id = data._id.toString();
|
||||
data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}});
|
||||
should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], status: -1, __v: 0}
|
||||
should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: '5514212901'}, {color: 'signalviolet', number: '5514612901'}], status: -1, __v: 0}
|
||||
);
|
||||
done();
|
||||
});
|
||||
@ -370,7 +474,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]}
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '05515798402'}]}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
@ -384,7 +488,7 @@ describe('/material', () => {
|
||||
should(res.body.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color', 'black');
|
||||
should(number).have.property('number', 5515798402);
|
||||
should(number).have.property('number', '05515798402');
|
||||
});
|
||||
done();
|
||||
});
|
||||
@ -415,13 +519,52 @@ describe('/material', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
it('accepts a color without number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: ''}]}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('name', 'Crastin CE 2510');
|
||||
should(res.body).have.property('supplier', 'Du Pont');
|
||||
should(res.body).have.property('group', 'PBT');
|
||||
should(res.body).have.property('mineral', 0);
|
||||
should(res.body).have.property('glass_fiber', 30);
|
||||
should(res.body).have.property('carbon_fiber', 0);
|
||||
should(res.body.numbers).matchEach(number => {
|
||||
should(number).have.only.keys('color', 'number');
|
||||
should(number).have.property('color', 'black');
|
||||
should(number).have.property('number', '');
|
||||
});
|
||||
MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
|
||||
if (err) return done (err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v');
|
||||
should(data[0]).have.property('_id');
|
||||
should(data[0]).have.property('name', 'Crastin CE 2510');
|
||||
should(data[0]).have.property('supplier', 'Du Pont');
|
||||
should(data[0]).have.property('group', 'PBT');
|
||||
should(data[0]).have.property('mineral', '0');
|
||||
should(data[0]).have.property('glass_fiber', '30');
|
||||
should(data[0]).have.property('carbon_fiber', '0');
|
||||
should(data[0]).have.property('status', 0);
|
||||
should(_.omit(data[0].numbers[0], '_id')).be.eql({color: 'black', number: ''});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('rejects already existing material names', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]},
|
||||
req: {name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: '5514263423'}]},
|
||||
res: {status: 'Material name already taken'}
|
||||
});
|
||||
});
|
||||
@ -431,7 +574,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||
req: {supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"name" is required'}
|
||||
});
|
||||
});
|
||||
@ -441,7 +584,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||
req: {name: 'Crastin CE 2510', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"supplier" is required'}
|
||||
});
|
||||
});
|
||||
@ -451,7 +594,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"group" is required'}
|
||||
});
|
||||
});
|
||||
@ -461,7 +604,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"mineral" is required'}
|
||||
});
|
||||
});
|
||||
@ -471,7 +614,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]},
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, carbon_fiber: 0, numbers: [{color: 'black', number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"glass_fiber" is required'}
|
||||
});
|
||||
});
|
||||
@ -481,7 +624,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, numbers: [{color: 'black', number: 5515798402}]},
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, numbers: [{color: 'black', number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"carbon_fiber" is required'}
|
||||
});
|
||||
});
|
||||
@ -501,7 +644,7 @@ describe('/material', () => {
|
||||
url: '/material/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{number: 5515798402}]},
|
||||
req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{number: '5515798402'}]},
|
||||
res: {status: 'Invalid body format', details: '"numbers[0].color" is required'}
|
||||
});
|
||||
});
|
||||
|
@ -21,6 +21,22 @@ router.get('/materials', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/materials/:group(new|deleted)', (req, res, next) => {
|
||||
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
|
||||
|
||||
let status;
|
||||
switch (req.params.group) {
|
||||
case 'new': status = 0;
|
||||
break;
|
||||
case 'deleted': status = -1;
|
||||
break;
|
||||
}
|
||||
MaterialModel.find({status: status}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
|
||||
|
||||
@ -51,7 +67,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
|
||||
// check for changes
|
||||
if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) {
|
||||
material.status = 0;
|
||||
material.status = 0; // set status to new
|
||||
}
|
||||
|
||||
await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
|
||||
@ -85,13 +101,12 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
|
||||
router.post('/material/new', async (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
// validate input
|
||||
const {error, value: material} = MaterialValidate.input(req.body, 'new');
|
||||
if (error) return res400(error, res);
|
||||
|
||||
if (!await nameCheck(material, res, next)) return;
|
||||
|
||||
material.status = 0;
|
||||
material.status = 0; // set status to new
|
||||
await new MaterialModel(material).save((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(MaterialValidate.output(data.toObject()));
|
||||
@ -103,7 +118,7 @@ module.exports = router;
|
||||
|
||||
|
||||
async function nameCheck (material, res, next) { // check if name was already taken
|
||||
const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => {next(err); return false;}) as any;
|
||||
const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
|
||||
if (materialData instanceof Error) return false;
|
||||
if (materialData) { // could not find material_id
|
||||
res.status(400).json({status: 'Material name already taken'});
|
||||
|
@ -2,6 +2,9 @@ import should from 'should/as-function';
|
||||
import MeasurementModel from '../models/measurement';
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
// TODO: allow empty values
|
||||
|
||||
|
||||
describe('/measurement', () => {
|
||||
let server;
|
||||
before(done => TestHelper.before(done));
|
||||
|
@ -36,16 +36,20 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
|
||||
if (!data) {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
|
||||
// add properties needed for conditionIdCheck
|
||||
measurement.measurement_template = data.measurement_template;
|
||||
measurement.condition_id = data.condition_id;
|
||||
if (!await conditionIdCheck(measurement, req, res, next)) return;
|
||||
|
||||
// check for changes
|
||||
if (measurement.values) {
|
||||
measurement.values = _.assign({}, data.values, measurement.values);
|
||||
if (!_.isEqual(measurement.values, data.values)) {
|
||||
measurement.status = 0;
|
||||
measurement.status = 0; // set status to new
|
||||
}
|
||||
}
|
||||
|
||||
if (!await templateCheck(measurement, 'change', res, next)) return;
|
||||
await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
@ -99,7 +103,7 @@ async function conditionIdCheck (measurement, req, res, next) { // validate con
|
||||
return true;
|
||||
}
|
||||
|
||||
async function templateCheck (measurement, param, res, next) { // validate measurement_template and values
|
||||
async function templateCheck (measurement, param, res, next) { // validate measurement_template and values, param for new/change
|
||||
const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template).lean().exec().catch(err => {next(err); return false;}) as any;
|
||||
if (!templateData) { // template not found
|
||||
res.status(400).json({status: 'Measurement template not available'});
|
||||
@ -108,7 +112,6 @@ async function templateCheck (measurement, param, res, next) { // validate meas
|
||||
|
||||
// validate values
|
||||
const {error, value: ignore} = ParametersValidate.input(measurement.values, templateData.parameters, param);
|
||||
console.log(error);
|
||||
if (error) {res400(error, res); return false;}
|
||||
return true;
|
||||
}
|
@ -3,8 +3,10 @@ import SampleModel from '../models/sample';
|
||||
import NoteModel from '../models/note';
|
||||
import NoteFieldModel from '../models/note_field';
|
||||
import TestHelper from "../test/helper";
|
||||
// TODO: generate sample number
|
||||
// TODO: think again which parameters are required at POST
|
||||
|
||||
// TODO: generate output for ML in format DPT -> data, implement filtering, field selection
|
||||
// TODO: write script for data import
|
||||
// TODO: delete everything (measurements, condition) with sample
|
||||
|
||||
describe('/sample', () => {
|
||||
let server;
|
||||
@ -23,16 +25,16 @@ describe('/sample', () => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
|
||||
should(res.body).matchEach(material => {
|
||||
should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(material).have.property('_id').be.type('string');
|
||||
should(material).have.property('number').be.type('string');
|
||||
should(material).have.property('type').be.type('string');
|
||||
should(material).have.property('color').be.type('string');
|
||||
should(material).have.property('batch').be.type('string');
|
||||
should(material).have.property('material_id').be.type('string');
|
||||
should(material).have.property('note_id');
|
||||
should(material).have.property('user_id').be.type('string');
|
||||
should(res.body).matchEach(sample => {
|
||||
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(sample).have.property('_id').be.type('string');
|
||||
should(sample).have.property('number').be.type('string');
|
||||
should(sample).have.property('type').be.type('string');
|
||||
should(sample).have.property('color').be.type('string');
|
||||
should(sample).have.property('batch').be.type('string');
|
||||
should(sample).have.property('material_id').be.type('string');
|
||||
should(sample).have.property('note_id');
|
||||
should(sample).have.property('user_id').be.type('string');
|
||||
});
|
||||
done();
|
||||
});
|
||||
@ -70,6 +72,94 @@ describe('/sample', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /samples/{group}', () => {
|
||||
it('returns all new samples', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples/new',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
let asyncCounter = res.body.length;
|
||||
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 0).length);
|
||||
should(res.body).matchEach(sample => {
|
||||
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(sample).have.property('_id').be.type('string');
|
||||
should(sample).have.property('number').be.type('string');
|
||||
should(sample).have.property('type').be.type('string');
|
||||
should(sample).have.property('color').be.type('string');
|
||||
should(sample).have.property('batch').be.type('string');
|
||||
should(sample).have.property('material_id').be.type('string');
|
||||
should(sample).have.property('note_id');
|
||||
should(sample).have.property('user_id').be.type('string');
|
||||
SampleModel.findById(sample._id).lean().exec((err, data) => {
|
||||
should(data).have.property('status', 0);
|
||||
if (--asyncCounter === 0) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns all deleted samples', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples/deleted',
|
||||
auth: {basic: 'admin'},
|
||||
httpStatus: 200
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
const json = require('../test/db.json');
|
||||
let asyncCounter = res.body.length;
|
||||
should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === -1).length);
|
||||
should(res.body).matchEach(sample => {
|
||||
should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(sample).have.property('_id').be.type('string');
|
||||
should(sample).have.property('number').be.type('string');
|
||||
should(sample).have.property('type').be.type('string');
|
||||
should(sample).have.property('color').be.type('string');
|
||||
should(sample).have.property('batch').be.type('string');
|
||||
should(sample).have.property('material_id').be.type('string');
|
||||
should(sample).have.property('note_id');
|
||||
should(sample).have.property('user_id').be.type('string');
|
||||
SampleModel.findById(sample._id).lean().exec((err, data) => {
|
||||
should(data).have.property('status', -1);
|
||||
if (--asyncCounter === 0) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects requests from a write user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 403
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples/new',
|
||||
auth: {key: 'admin'},
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'get',
|
||||
url: '/samples/new',
|
||||
httpStatus: 401
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /sample/{id}', () => {
|
||||
it('returns the right sample', done => {
|
||||
TestHelper.request(server, done, {
|
||||
@ -87,7 +177,7 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}}
|
||||
req: {type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done(err);
|
||||
should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'});
|
||||
@ -156,14 +246,14 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => {
|
||||
if (err) return done (err);
|
||||
should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
|
||||
should(data).have.property('_id');
|
||||
should(data).have.property('number', '10');
|
||||
should(data).have.property('number', '1');
|
||||
should(data).have.property('color', 'signalviolet');
|
||||
should(data).have.property('type', 'part');
|
||||
should(data).have.property('batch', '114531');
|
||||
@ -194,12 +284,10 @@ describe('/sample', () => {
|
||||
}).end(err => {
|
||||
if (err) return done(err);
|
||||
NoteFieldModel.findOne({name: 'not allowed for new applications'}).lean().exec((err, data) => {
|
||||
console.log(data);
|
||||
if (err) return done(err);
|
||||
should(data).have.property('qty', 1);
|
||||
NoteFieldModel.findOne({name: 'field1'}).lean().exec((err, data) => {
|
||||
if (err) return done(err);
|
||||
console.log(data);
|
||||
should(data).have.property('qty', 1);
|
||||
done();
|
||||
});
|
||||
@ -228,12 +316,11 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000002',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: '111'}
|
||||
req: {type: 'part'}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
NoteModel.findById(res.body.note_id).lean().exec((err, data) => {
|
||||
if (err) return done (err);
|
||||
console.log(data);
|
||||
should(data).not.be.null();
|
||||
should(data).have.property('comment', 'Stoff gesperrt');
|
||||
should(data).have.property('sample_references').have.lengthOf(0);
|
||||
@ -263,7 +350,7 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Color not available for material'}
|
||||
});
|
||||
});
|
||||
@ -273,18 +360,18 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Material not available'}
|
||||
});
|
||||
});
|
||||
it('rejects a sample number in use', done => {
|
||||
it('rejects a sample number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'put',
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '21', type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Sample number already taken'}
|
||||
req: {number: 25, type: 'part', color: 'signalviolet', batch: '114531', material_id: '100000000000000000000002', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"number" is not allowed'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid sample reference', done => {
|
||||
@ -293,7 +380,7 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '10', 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: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Sample reference not available'}
|
||||
});
|
||||
});
|
||||
@ -303,7 +390,7 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '10', 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: [{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}/'}
|
||||
});
|
||||
});
|
||||
@ -313,7 +400,7 @@ describe('/sample', () => {
|
||||
url: '/sample/10000000000h000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
});
|
||||
});
|
||||
it('rejects an API key', done => {
|
||||
@ -322,7 +409,7 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
});
|
||||
});
|
||||
it('rejects changes for samples from another user for a write user', done => {
|
||||
@ -350,7 +437,7 @@ describe('/sample', () => {
|
||||
url: '/sample/400000000000000000000001',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
});
|
||||
});
|
||||
it('returns 404 for an unknown sample', done => {
|
||||
@ -359,7 +446,7 @@ describe('/sample', () => {
|
||||
url: '/sample/000000000000000000000001',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 404,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
})
|
||||
it('rejects unauthorized requests', done => {
|
||||
@ -367,7 +454,7 @@ describe('/sample', () => {
|
||||
method: 'put',
|
||||
url: '/sample/400000000000000000000001',
|
||||
httpStatus: 401,
|
||||
req: {number: '10', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -448,7 +535,6 @@ describe('/sample', () => {
|
||||
setTimeout(() => { // background action takes some time before we can check
|
||||
NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
|
||||
if (err) return done(err);
|
||||
console.log(data);
|
||||
should(data).have.property('sample_references').with.lengthOf(1);
|
||||
should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
|
||||
should(data.sample_references[0]).have.property('relation', 'part to sample');
|
||||
@ -490,6 +576,7 @@ describe('/sample', () => {
|
||||
httpStatus: 404
|
||||
});
|
||||
});
|
||||
it('rejects deleting a sample referenced by conditions'); // TODO after decision
|
||||
it('rejects requests from a read user', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'delete',
|
||||
@ -530,12 +617,12 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('number', 'Rng172');
|
||||
should(res.body).have.property('number', 'Rng34');
|
||||
should(res.body).have.property('color', 'black');
|
||||
should(res.body).have.property('type', 'granulate');
|
||||
should(res.body).have.property('batch', '1560237365');
|
||||
@ -551,15 +638,15 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
}).end(err => {
|
||||
if (err) return done (err);
|
||||
SampleModel.find({number: 'Rng172'}).lean().exec((err, data: any) => {
|
||||
SampleModel.find({number: 'Rng34'}).lean().exec((err, data: any) => {
|
||||
if (err) return done (err);
|
||||
should(data).have.lengthOf(1);
|
||||
should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v');
|
||||
should(data[0]).have.property('_id');
|
||||
should(data[0]).have.property('number', 'Rng172');
|
||||
should(data[0]).have.property('number', 'Rng34');
|
||||
should(data[0]).have.property('color', 'black');
|
||||
should(data[0]).have.property('type', 'granulate');
|
||||
should(data[0]).have.property('batch', '1560237365');
|
||||
@ -586,7 +673,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 200,
|
||||
req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [], custom_fields: {field1: 'a', field2: 'b', 'not allowed for new applications': true}}}
|
||||
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [], custom_fields: {field1: 'a', field2: 'b', 'not allowed for new applications': true}}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
NoteModel.findById(res.body.note_id).lean().exec((err, data: any) => {
|
||||
@ -617,13 +704,34 @@ describe('/sample', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
it('stores a new sample location as 1', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'johnnydoe'},
|
||||
httpStatus: 200,
|
||||
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
}).end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
|
||||
should(res.body).have.property('_id').be.type('string');
|
||||
should(res.body).have.property('number', 'Fe1');
|
||||
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('material_id', '100000000000000000000001');
|
||||
should(res.body).have.property('note_id').be.type('string');
|
||||
should(res.body).have.property('user_id', '000000000000000000000004');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects a color not defined for the material', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Color not available for material'}
|
||||
});
|
||||
});
|
||||
@ -633,18 +741,18 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Material not available'}
|
||||
});
|
||||
});
|
||||
it('rejects a sample number in use', done => {
|
||||
it('rejects a sample number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: '1', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Sample number already taken'}
|
||||
req: {number: 'Rng34', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"number" is not allowed'}
|
||||
});
|
||||
});
|
||||
it('rejects an invalid sample reference', done => {
|
||||
@ -653,7 +761,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Sample reference not available'}
|
||||
});
|
||||
});
|
||||
@ -663,27 +771,17 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"color" is required'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing sample number', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"number" is required'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing type', done => {
|
||||
TestHelper.request(server, done, {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"type" is required'}
|
||||
});
|
||||
});
|
||||
@ -693,7 +791,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"batch" is required'}
|
||||
});
|
||||
});
|
||||
@ -703,7 +801,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
|
||||
res: {status: 'Invalid body format', details: '"material_id" is required'}
|
||||
});
|
||||
});
|
||||
@ -713,7 +811,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'janedoe'},
|
||||
httpStatus: 400,
|
||||
req: {number: 'Rng172', 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: [{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}/'}
|
||||
});
|
||||
});
|
||||
@ -723,7 +821,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {key: 'janedoe'},
|
||||
httpStatus: 401,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
});
|
||||
it('rejects requests from a read user', done => {
|
||||
@ -732,7 +830,7 @@ describe('/sample', () => {
|
||||
url: '/sample/new',
|
||||
auth: {basic: 'user'},
|
||||
httpStatus: 403,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
});
|
||||
it('rejects unauthorized requests', done => {
|
||||
@ -740,7 +838,7 @@ describe('/sample', () => {
|
||||
method: 'post',
|
||||
url: '/sample/new',
|
||||
httpStatus: 401,
|
||||
req: {number: 'Rng172', 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: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -22,6 +22,22 @@ router.get('/samples', (req, res, next) => {
|
||||
})
|
||||
});
|
||||
|
||||
router.get('/samples/:group(new|deleted)', (req, res, next) => {
|
||||
if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
|
||||
|
||||
let status;
|
||||
switch (req.params.group) {
|
||||
case 'new': status = 0;
|
||||
break;
|
||||
case 'deleted': status = -1;
|
||||
break;
|
||||
}
|
||||
SampleModel.find({status: status}).lean().exec((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
|
||||
})
|
||||
});
|
||||
|
||||
router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
@ -33,12 +49,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!sampleData) {
|
||||
return res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
|
||||
// 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 (sample.hasOwnProperty('number') && sample.number !== sampleData.number) {
|
||||
if (!await numberCheck(sample, res, next)) return;
|
||||
}
|
||||
if (sample.hasOwnProperty('material_id')) {
|
||||
if (!await materialCheck(sample, res, next)) return;
|
||||
}
|
||||
@ -51,12 +65,12 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (sampleData.note_id !== null) { // old notes data exists
|
||||
const data = await NoteModel.findById(sampleData.note_id).lean().exec().catch(err => {next(err);}) as any;
|
||||
if (data instanceof Error) return;
|
||||
newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);
|
||||
newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes); // check if notes were changed
|
||||
if (newNotes) {
|
||||
if (data.hasOwnProperty('custom_fields')) { // update note_fields
|
||||
customFieldsChange(Object.keys(data.custom_fields), -1);
|
||||
}
|
||||
NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => { // delete old notes
|
||||
await NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => { // delete old notes
|
||||
if (err) return console.error(err);
|
||||
});
|
||||
}
|
||||
@ -77,7 +91,8 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
|
||||
sample.status = 0;
|
||||
}
|
||||
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) => {
|
||||
if (err) return next(err);
|
||||
res.json(SampleValidate.output(data));
|
||||
});
|
||||
@ -93,12 +108,13 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
|
||||
if (!sampleData) {
|
||||
return res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { // set sample status
|
||||
await SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { // set sample status
|
||||
if (err) return next(err);
|
||||
if (sampleData.note_id !== null) {
|
||||
if (sampleData.note_id !== null) { // handle notes
|
||||
NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields
|
||||
if (err) return next(err);
|
||||
if (data.hasOwnProperty('custom_fields')) { // update note_fields
|
||||
@ -120,7 +136,6 @@ router.post('/sample/new', async (req, res, next) => {
|
||||
const {error, value: sample} = SampleValidate.input(req.body, 'new');
|
||||
if (error) return res400(error, res);
|
||||
|
||||
if (!await numberCheck(sample, res, next)) return;
|
||||
if (!await materialCheck(sample, res, next)) return;
|
||||
if (!await sampleRefCheck(sample, res, next)) return;
|
||||
|
||||
@ -128,13 +143,15 @@ router.post('/sample/new', async (req, res, next) => {
|
||||
customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
|
||||
}
|
||||
|
||||
sample.status = 0;
|
||||
new NoteModel(sample.notes).save((err, data) => {
|
||||
sample.status = 0; // set status to new
|
||||
sample.number = await numberGenerate(sample, req, res, next);
|
||||
if (!sample.number) return;
|
||||
|
||||
await new NoteModel(sample.notes).save((err, data) => { // save notes
|
||||
if (err) return next(err);
|
||||
delete sample.notes;
|
||||
sample.note_id = data._id;
|
||||
sample.user_id = req.authDetails.id;
|
||||
console.log(sample);
|
||||
new SampleModel(sample).save((err, data) => {
|
||||
if (err) return next(err);
|
||||
res.json(SampleValidate.output(data.toObject()));
|
||||
@ -155,17 +172,18 @@ router.get('/sample/notes/fields', (req, res, next) => {
|
||||
module.exports = router;
|
||||
|
||||
|
||||
async function numberCheck (sample, res, next) { // validate number, returns false if invalid
|
||||
const sampleData = await SampleModel.findOne({number: sample.number}).lean().exec().catch(err => {next(err); return false;});
|
||||
if (sampleData) { // found entry with sample number
|
||||
res.status(400).json({status: 'Sample number already taken'});
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
async function numberGenerate (sample, req, res, next) { // generate number, returns false on error
|
||||
const sampleData = await SampleModel
|
||||
.find({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
|
||||
.lean()
|
||||
.exec()
|
||||
.catch(err => next(err));
|
||||
if (sampleData instanceof Error) return false;
|
||||
return req.authDetails.location + (sampleData.length > 0 ? Number(sampleData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1);
|
||||
}
|
||||
|
||||
async function materialCheck (sample, res, next, id = sample.material_id) { // validate material_id and color, returns false if invalid
|
||||
const materialData = await MaterialModel.findById(id).lean().exec().catch(err => {next(err); return false;}) as any;
|
||||
const materialData = await MaterialModel.findById(id).lean().exec().catch(err => next(err)) as any;
|
||||
if (materialData instanceof Error) return false;
|
||||
if (!materialData) { // could not find material_id
|
||||
res.status(400).json({status: 'Material not available'});
|
||||
@ -181,7 +199,8 @@ async function materialCheck (sample, res, next, id = sample.material_id) { //
|
||||
function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference
|
||||
return new Promise(resolve => {
|
||||
if (sample.notes.sample_references.length > 0) { // there are sample_references
|
||||
let referencesCount = sample.notes.sample_references.length;
|
||||
let referencesCount = sample.notes.sample_references.length; // count to keep track of running async operations
|
||||
|
||||
sample.notes.sample_references.forEach(reference => {
|
||||
SampleModel.findById(reference.id).lean().exec((err, data) => {
|
||||
if (err) {next(err); resolve(false)}
|
||||
@ -190,7 +209,7 @@ function sampleRefCheck (sample, res, next) { // validate sample_references, re
|
||||
return resolve(false);
|
||||
}
|
||||
referencesCount --;
|
||||
if (referencesCount <= 0) {
|
||||
if (referencesCount <= 0) { // all async requests done
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
@ -202,7 +221,7 @@ function sampleRefCheck (sample, res, next) { // validate sample_references, re
|
||||
});
|
||||
}
|
||||
|
||||
function customFieldsChange (fields, amount) {
|
||||
function customFieldsChange (fields, amount) { // update custom_fields and respective quantities
|
||||
fields.forEach(field => {
|
||||
NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}}, {new: true}).lean().exec((err, data: any) => { // check if field exists
|
||||
if (err) return console.error(err);
|
||||
|
@ -4,7 +4,7 @@ import TemplateTreatmentModel from '../models/treatment_template';
|
||||
import TemplateMeasurementModel from '../models/measurement_template';
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
|
||||
// TODO: do not allow usage of old templates for new samples
|
||||
|
||||
describe('/template', () => {
|
||||
let server;
|
||||
@ -201,7 +201,6 @@ describe('/template', () => {
|
||||
httpStatus: 200,
|
||||
req: {parameters: [{name: 'time', range: {type: 'array'}}]}
|
||||
}).end((err, res) => {
|
||||
console.log(res.body);
|
||||
if (err) return done(err);
|
||||
should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]});
|
||||
done();
|
||||
@ -371,14 +370,14 @@ describe('/template', () => {
|
||||
res: {status: 'Invalid body format', details: '"parameters[0].name" is required'}
|
||||
});
|
||||
});
|
||||
it('rejects a missing number prefix', done => {
|
||||
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', parameters: [{name: 'time', range: {min: 1}}]},
|
||||
res: {status: 'Invalid body format', details: '"number_prefix" is required'}
|
||||
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 => {
|
||||
|
@ -7,14 +7,14 @@ import TemplateMeasurementModel from '../models/measurement_template';
|
||||
import res400 from './validate/res400';
|
||||
import IdValidate from './validate/id';
|
||||
|
||||
// TODO: remove f() for await
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/template/:collection(measurements|treatments)', (req, res, next) => {
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
|
||||
req.params.collection = req.params.collection.replace(/s$/g, '');
|
||||
req.params.collection = req.params.collection.replace(/s$/g, ''); // remove trailing s
|
||||
model(req).find({}).lean().exec((err, data) => {
|
||||
if (err) next (err);
|
||||
res.json(_.compact(data.map(e => TemplateValidate.output(e, req.params.collection)))); // validate all and filter null values from validation errors
|
||||
@ -52,8 +52,8 @@ router.put('/template/:collection(measurement|treatment)/' + IdValidate.paramete
|
||||
}
|
||||
|
||||
if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) { // data was changed
|
||||
template.version = templateData.version + 1;
|
||||
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
|
||||
template.version = templateData.version + 1; // increase version
|
||||
await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => { // save new template, fill with old properties
|
||||
if (err) next (err);
|
||||
res.json(TemplateValidate.output(data.toObject(), req.params.collection));
|
||||
});
|
||||
@ -73,7 +73,7 @@ router.post('/template/:collection(measurement|treatment)/new', async (req, res,
|
||||
if (!await numberPrefixCheck(template, req, res, next)) return;
|
||||
}
|
||||
|
||||
template.version = 1;
|
||||
template.version = 1; // set template version
|
||||
await new (model(req))(template).save((err, data) => {
|
||||
if (err) next (err);
|
||||
res.json(TemplateValidate.output(data.toObject(), req.params.collection));
|
||||
@ -84,7 +84,7 @@ router.post('/template/:collection(measurement|treatment)/new', async (req, res,
|
||||
module.exports = router;
|
||||
|
||||
|
||||
async function numberPrefixCheck (template, req, res, next) {
|
||||
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'});
|
||||
@ -93,6 +93,6 @@ async function numberPrefixCheck (template, req, res, next) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function model (req) {
|
||||
function model (req) { // return right template model
|
||||
return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel;
|
||||
}
|
@ -2,6 +2,7 @@ import should from 'should/as-function';
|
||||
import UserModel from '../models/user';
|
||||
import TestHelper from "../test/helper";
|
||||
|
||||
// TODO: reject usernames containing admin, etc.
|
||||
|
||||
describe('/user', () => {
|
||||
let server;
|
||||
|
@ -20,14 +20,10 @@ router.get('/users', (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
||||
req.params.username = req.params[0];
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
let username = req.authDetails.username;
|
||||
if (req.params.username !== undefined) {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
username = req.params.username;
|
||||
}
|
||||
|
||||
const username = getUsername(req, res);
|
||||
if (!username) return;
|
||||
UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
@ -39,14 +35,13 @@ router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // thi
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
|
||||
req.params.username = req.params[0];
|
||||
router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
let username = req.authDetails.username;
|
||||
if (req.params.username !== undefined) {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
username = req.params.username;
|
||||
}
|
||||
|
||||
const username = getUsername(req, res);
|
||||
if (!username) return;
|
||||
console.log(username);
|
||||
|
||||
const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
|
||||
if (error) return res400(error, res);
|
||||
|
||||
@ -56,45 +51,25 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // thi
|
||||
|
||||
// check that user does not already exist if new name was specified
|
||||
if (user.hasOwnProperty('name') && user.name !== username) {
|
||||
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data.length > 0 || UserValidate.isSpecialName(user.name)) {
|
||||
res.status(400).json({status: 'Username already taken'});
|
||||
return;
|
||||
}
|
||||
if (!await usernameCheck(user.name, res, next)) return;
|
||||
}
|
||||
|
||||
UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(UserValidate.output(data));
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
}
|
||||
await UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data) {
|
||||
res.json(UserValidate.output(data));
|
||||
}
|
||||
else {
|
||||
res.status(404).json({status: 'Not found'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
|
||||
req.params.username = req.params[0];
|
||||
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
|
||||
let username = req.authDetails.username;
|
||||
if (req.params.username !== undefined) {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
username = req.params.username;
|
||||
}
|
||||
|
||||
const username = getUsername(req, res);
|
||||
if (!username) return;
|
||||
|
||||
UserModel.findOneAndDelete({name: username}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
@ -116,7 +91,7 @@ router.get('/user/key', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/user/new', (req, res, next) => {
|
||||
router.post('/user/new', async (req, res, next) => {
|
||||
if (!req.auth(res, ['admin'], 'basic')) return;
|
||||
|
||||
// validate input
|
||||
@ -124,20 +99,14 @@ router.post('/user/new', (req, res, next) => {
|
||||
if (error) return res400(error, res);
|
||||
|
||||
// check that user does not already exist
|
||||
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
|
||||
if (err) return next(err);
|
||||
if (data.length > 0 || UserValidate.isSpecialName(user.name)) {
|
||||
res.status(400).json({status: 'Username already taken'});
|
||||
return;
|
||||
}
|
||||
if (!await usernameCheck(user.name, res, next)) return;
|
||||
|
||||
user.key = mongoose.Types.ObjectId(); // use object id as unique API key
|
||||
bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing
|
||||
user.pass = hash;
|
||||
new UserModel(user).save((err, data) => { // store user
|
||||
if (err) return next(err);
|
||||
res.json(UserValidate.output(data.toObject()));
|
||||
});
|
||||
user.key = mongoose.Types.ObjectId(); // use object id as unique API key
|
||||
bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing
|
||||
user.pass = hash;
|
||||
new UserModel(user).save((err, data) => { // store user
|
||||
if (err) return next(err);
|
||||
res.json(UserValidate.output(data.toObject()));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -147,11 +116,14 @@ router.post('/user/passreset', (req, res, next) => {
|
||||
UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
|
||||
if (err) return next(err);
|
||||
if (data.length === 1) { // it exists
|
||||
const newPass = Math.random().toString(36).substring(2);
|
||||
const newPass = Math.random().toString(36).substring(2); // generate temporary password
|
||||
bcrypt.hash(newPass, 10, (err, hash) => { // password hashing
|
||||
if (err) return next(err);
|
||||
|
||||
UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}, err => { // write new password
|
||||
if (err) return next(err);
|
||||
|
||||
// send email
|
||||
mail(data[0].email, 'Your new password for the DFOP database', 'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.<br><br>The DFOP team', err => {
|
||||
if (err) return next(err);
|
||||
res.json({status: 'OK'});
|
||||
@ -166,4 +138,27 @@ router.post('/user/passreset', (req, res, next) => {
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
function getUsername (req, res) { // returns username or false if action is not allowed
|
||||
req.params.username = req.params[0]; // because of path regex
|
||||
if (req.params.username !== undefined) { // different username than request user
|
||||
if (!req.auth(res, ['admin'], 'basic')) return false;
|
||||
return req.params.username;
|
||||
}
|
||||
else {
|
||||
return req.authDetails.username;
|
||||
}
|
||||
}
|
||||
|
||||
async function usernameCheck (name, res, next) { // check if username is already taken
|
||||
const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
|
||||
if (userData instanceof Error) return false;
|
||||
console.log(userData);
|
||||
console.log(UserValidate.isSpecialName(name));
|
||||
if (userData || UserValidate.isSpecialName(name)) {
|
||||
res.status(400).json({status: 'Username already taken'});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
@ -18,18 +18,16 @@ export default class ConditionValidate {
|
||||
)
|
||||
}
|
||||
|
||||
static input (data, param) {
|
||||
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(),
|
||||
number: this.condition.number.required(),
|
||||
parameters: this.condition.parameters.required(),
|
||||
treatment_template: IdValidate.get().required()
|
||||
}).validate(data);
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return Joi.object({
|
||||
number: this.condition.number,
|
||||
parameters: this.condition.parameters
|
||||
}).validate(data);
|
||||
}
|
||||
@ -38,7 +36,7 @@ export default class ConditionValidate {
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) {
|
||||
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(),
|
||||
|
@ -3,11 +3,11 @@ import Joi from '@hapi/joi';
|
||||
export default class IdValidate {
|
||||
private static id = Joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
|
||||
|
||||
static get () {
|
||||
static get () { // return joi validation
|
||||
return this.id;
|
||||
}
|
||||
|
||||
static valid (id) {
|
||||
static valid (id) { // validate id
|
||||
return this.id.validate(id).error === undefined;
|
||||
}
|
||||
|
||||
@ -15,11 +15,14 @@ export default class IdValidate {
|
||||
return ':id([0-9a-f]{24})';
|
||||
}
|
||||
|
||||
static stringify (data) {
|
||||
static stringify (data) { // convert all ObjectID objects to plain strings
|
||||
Object.keys(data).forEach(key => {
|
||||
if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
|
||||
if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') { // stringify id
|
||||
data[key] = data[key].toString();
|
||||
}
|
||||
else if (typeof data[key] === 'object' && data[key] !== null) { // deeper into recursion
|
||||
data[key] = this.stringify(data[key]);
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
@ -33,13 +33,14 @@ export default class MaterialValidate { // validate input for material
|
||||
color: joi.string()
|
||||
.max(128)
|
||||
.required(),
|
||||
number: joi.number()
|
||||
.min(0)
|
||||
number: joi.string()
|
||||
.max(128)
|
||||
.allow('')
|
||||
.required()
|
||||
}))
|
||||
};
|
||||
|
||||
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
||||
if (param === 'new') {
|
||||
return joi.object({
|
||||
name: this.material.name.required(),
|
||||
@ -67,7 +68,7 @@ export default class MaterialValidate { // validate input for material
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) { // validate output from database for needed properties, strip everything else
|
||||
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(),
|
||||
|
@ -15,7 +15,7 @@ export default class MeasurementValidate {
|
||||
)
|
||||
};
|
||||
|
||||
static input (data, param) {
|
||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
||||
if (param === 'new') {
|
||||
return Joi.object({
|
||||
condition_id: IdValidate.get().required(),
|
||||
@ -33,7 +33,7 @@ export default class MeasurementValidate {
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) {
|
||||
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(),
|
||||
|
@ -8,7 +8,7 @@ export default class NoteFieldValidate {
|
||||
qty: Joi.number()
|
||||
};
|
||||
|
||||
static output (data) {
|
||||
static output (data) { // validate output and strip unwanted properties, returns null if not valid
|
||||
const {value, error} = Joi.object({
|
||||
name: this.note_field.name,
|
||||
qty: this.note_field.qty
|
||||
|
@ -4,7 +4,7 @@ export default class ParametersValidate {
|
||||
static input (data, parameters, param) { // data to validate, parameters from template, param: 'new', 'change'
|
||||
let joiObject = {};
|
||||
parameters.forEach(parameter => {
|
||||
if (parameter.range.hasOwnProperty('values')) {
|
||||
if (parameter.range.hasOwnProperty('values')) { // append right validation method according to parameter
|
||||
joiObject[parameter.name] = Joi.alternatives()
|
||||
.try(Joi.string().max(128), Joi.number(), Joi.boolean())
|
||||
.valid(...parameter.range.values);
|
||||
|
@ -1,3 +1,5 @@
|
||||
// respond with 400 and include error details from the joi validation
|
||||
|
||||
export default function res400 (error, res) {
|
||||
res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
|
||||
}
|
@ -41,10 +41,9 @@ export default class SampleValidate {
|
||||
})
|
||||
};
|
||||
|
||||
static input (data, param) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
||||
if (param === 'new') {
|
||||
return Joi.object({
|
||||
number: this.sample.number.required(),
|
||||
color: this.sample.color.required(),
|
||||
type: this.sample.type.required(),
|
||||
batch: this.sample.batch.required(),
|
||||
@ -54,7 +53,6 @@ export default class SampleValidate {
|
||||
}
|
||||
else if (param === 'change') {
|
||||
return Joi.object({
|
||||
number: this.sample.number,
|
||||
color: this.sample.color,
|
||||
type: this.sample.type,
|
||||
batch: this.sample.batch,
|
||||
@ -67,7 +65,7 @@ export default class SampleValidate {
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) {
|
||||
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(),
|
||||
|
@ -1,35 +1,36 @@
|
||||
import joi from '@hapi/joi';
|
||||
import Joi from '@hapi/joi';
|
||||
import IdValidate from './id';
|
||||
|
||||
export default class TemplateValidate {
|
||||
private static template = {
|
||||
name: joi.string()
|
||||
name: Joi.string()
|
||||
.max(128),
|
||||
|
||||
version: joi.number()
|
||||
version: Joi.number()
|
||||
.min(1),
|
||||
|
||||
number_prefix: joi.string()
|
||||
number_prefix: Joi.string()
|
||||
.pattern(/^[a-zA-Z]+$/)
|
||||
.min(1)
|
||||
.max(16),
|
||||
|
||||
parameters: joi.array()
|
||||
parameters: Joi.array()
|
||||
.min(1)
|
||||
.items(
|
||||
joi.object({
|
||||
name: joi.string()
|
||||
Joi.object({
|
||||
name: Joi.string()
|
||||
.max(128)
|
||||
.required(),
|
||||
|
||||
range: joi.object({
|
||||
values: joi.array()
|
||||
range: Joi.object({
|
||||
values: Joi.array()
|
||||
.min(1),
|
||||
|
||||
min: joi.number(),
|
||||
min: Joi.number(),
|
||||
|
||||
max: joi.number(),
|
||||
max: Joi.number(),
|
||||
|
||||
type: joi.string()
|
||||
type: Joi.string()
|
||||
.valid('array')
|
||||
})
|
||||
.oxor('values', 'min')
|
||||
@ -42,17 +43,17 @@ export default class TemplateValidate {
|
||||
)
|
||||
};
|
||||
|
||||
static input (data, param, template) { // validate data, param: new(everything required)/change(available attributes are validated)
|
||||
static input (data, param, template) { // validate input, set param to 'new' to make all attributes required
|
||||
if (param === 'new') {
|
||||
if (template === 'treatment') {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.template.name.required(),
|
||||
number_prefix: this.template.number_prefix.required(),
|
||||
parameters: this.template.parameters.required()
|
||||
}).validate(data);
|
||||
}
|
||||
else {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.template.name.required(),
|
||||
parameters: this.template.parameters.required()
|
||||
}).validate(data);
|
||||
@ -60,14 +61,14 @@ export default class TemplateValidate {
|
||||
}
|
||||
else if (param === 'change') {
|
||||
if (template === 'treatment') {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.template.name,
|
||||
number_prefix: this.template.number_prefix,
|
||||
parameters: this.template.parameters
|
||||
}).validate(data);
|
||||
}
|
||||
else {
|
||||
return joi.object({
|
||||
return Joi.object({
|
||||
name: this.template.name,
|
||||
parameters: this.template.parameters
|
||||
}).validate(data);
|
||||
@ -78,10 +79,10 @@ export default class TemplateValidate {
|
||||
}
|
||||
}
|
||||
|
||||
static output (data, template) { // validate output from database for needed properties, strip everything else
|
||||
static output (data, template) { // validate output and strip unwanted properties, returns null if not valid
|
||||
data = IdValidate.stringify(data);
|
||||
let joiObject;
|
||||
if (template === 'treatment') {
|
||||
if (template === 'treatment') { // differentiate between measurement and treatment (has number_prefix) template
|
||||
joiObject = {
|
||||
_id: IdValidate.get(),
|
||||
name: this.template.name,
|
||||
@ -98,7 +99,7 @@ export default class TemplateValidate {
|
||||
parameters: this.template.parameters
|
||||
};
|
||||
}
|
||||
const {value, error} = joi.object(joiObject).validate(data, {stripUnknown: true});
|
||||
const {value, error} = Joi.object(joiObject).validate(data, {stripUnknown: true});
|
||||
return error !== undefined? null : value;
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@ import IdValidate from './id';
|
||||
|
||||
export default class UserValidate { // validate input for user
|
||||
private static user = {
|
||||
name: Joi.string() // TODO: check allowed characters
|
||||
.alphanum()
|
||||
name: Joi.string()
|
||||
.lowercase()
|
||||
.pattern(new RegExp('^[a-z0-9-_.]+$'))
|
||||
.max(128),
|
||||
|
||||
email: Joi.string()
|
||||
@ -16,7 +16,7 @@ export default class UserValidate { // validate input for user
|
||||
.max(128),
|
||||
|
||||
pass: Joi.string()
|
||||
.pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$'))
|
||||
.pattern(/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~])(?=\S+$)[a-zA-Z0-9!"#%&'()*+,\-.\/:;<=>?@[\]^_`{|}~]{8,}$/)
|
||||
.max(128),
|
||||
|
||||
level: Joi.string()
|
||||
@ -33,7 +33,7 @@ export default class UserValidate { // validate input for user
|
||||
|
||||
private static specialUsernames = ['admin', 'user', 'key', 'new', 'passreset']; // names a user cannot take
|
||||
|
||||
static input (data, param) {
|
||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
||||
if (param === 'new') {
|
||||
return Joi.object({
|
||||
name: this.user.name.required(),
|
||||
@ -68,7 +68,7 @@ export default class UserValidate { // validate input for user
|
||||
}
|
||||
}
|
||||
|
||||
static output (data) { // validate output from database for needed properties, strip everything else
|
||||
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(),
|
||||
|
@ -51,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"400000000000000000000005"},
|
||||
"number": "33",
|
||||
"number": "Rng33",
|
||||
"type": "granulate",
|
||||
"color": "black",
|
||||
"batch": "1653000308",
|
||||
@ -121,11 +121,11 @@
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5514263423
|
||||
"number": "5514263423"
|
||||
},
|
||||
{
|
||||
"color": "natural",
|
||||
"number": 5514263422
|
||||
"number": "5514263422"
|
||||
}
|
||||
],
|
||||
"status": 10,
|
||||
@ -142,11 +142,11 @@
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5514212901
|
||||
"number": "5514212901"
|
||||
},
|
||||
{
|
||||
"color": "signalviolet",
|
||||
"number": 5514612901
|
||||
"number": "5514612901"
|
||||
}
|
||||
],
|
||||
"status": 10,
|
||||
@ -176,7 +176,7 @@
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5513933405
|
||||
"number": "5513933405"
|
||||
}
|
||||
],
|
||||
"status": 10,
|
||||
@ -193,7 +193,7 @@
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": 5514262406
|
||||
"number": "5514262406"
|
||||
}
|
||||
],
|
||||
"status": 10,
|
||||
@ -210,18 +210,35 @@
|
||||
"numbers": [
|
||||
{
|
||||
"color": "natural",
|
||||
"number": 10000000
|
||||
"number": "10000000"
|
||||
}
|
||||
],
|
||||
"status": -1,
|
||||
"__v": 0
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"100000000000000000000007"},
|
||||
"name": "Ultramid A4H",
|
||||
"supplier": "BASF",
|
||||
"group": "PA66",
|
||||
"mineral": 0,
|
||||
"glass_fiber": 0,
|
||||
"carbon_fiber": 0,
|
||||
"numbers": [
|
||||
{
|
||||
"color": "black",
|
||||
"number": ""
|
||||
}
|
||||
],
|
||||
"status": 0,
|
||||
"__v": 0
|
||||
}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"_id": {"$oid":"700000000000000000000001"},
|
||||
"sample_id": {"$oid":"400000000000000000000001"},
|
||||
"number": "B1",
|
||||
"number": "A1",
|
||||
"parameters": {
|
||||
"material": "copper",
|
||||
"weeks": 3
|
||||
@ -233,7 +250,7 @@
|
||||
{
|
||||
"_id": {"$oid":"700000000000000000000002"},
|
||||
"sample_id": {"$oid":"400000000000000000000002"},
|
||||
"number": "B1",
|
||||
"number": "A1",
|
||||
"parameters": {
|
||||
"material": "copper",
|
||||
"weeks": 3
|
||||
@ -245,7 +262,7 @@
|
||||
{
|
||||
"_id": {"$oid":"700000000000000000000003"},
|
||||
"sample_id": {"$oid":"400000000000000000000004"},
|
||||
"number": "B1",
|
||||
"number": "A1",
|
||||
"parameters": {
|
||||
"material": "copper",
|
||||
"weeks": 3
|
||||
@ -257,7 +274,7 @@
|
||||
{
|
||||
"_id": {"$oid":"700000000000000000000004"},
|
||||
"sample_id": {"$oid":"400000000000000000000001"},
|
||||
"number": "B3",
|
||||
"number": "A2",
|
||||
"parameters": {
|
||||
"material": "hot air",
|
||||
"weeks": 5
|
||||
@ -429,6 +446,17 @@
|
||||
"device_name": "",
|
||||
"key": "000000000000000000001003",
|
||||
"__v": "0"
|
||||
},
|
||||
{
|
||||
"_id": {"$oid":"000000000000000000000004"},
|
||||
"email": "johnny.doe@bosch.com",
|
||||
"name": "johnnydoe",
|
||||
"pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
|
||||
"level": "write",
|
||||
"location": "Fe",
|
||||
"device_name": "Alpha I",
|
||||
"key": "000000000000000000001004",
|
||||
"__v": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,12 +4,13 @@ import db from "../db";
|
||||
|
||||
|
||||
export default class TestHelper {
|
||||
public static auth = {
|
||||
public static auth = { // test user credentials
|
||||
admin: {pass: 'Abc123!#', key: '000000000000000000001003'},
|
||||
janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002'},
|
||||
user: {pass: 'Xyz890*)', key: '000000000000000000001001'}
|
||||
user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
|
||||
johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
|
||||
}
|
||||
public static res = {
|
||||
public static res = { // default responses
|
||||
400: {status: 'Bad request'},
|
||||
401: {status: 'Unauthorized'},
|
||||
403: {status: 'Forbidden'},
|
||||
@ -39,10 +40,10 @@ export default class TestHelper {
|
||||
|
||||
static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res}
|
||||
let st = supertest(server);
|
||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {
|
||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
|
||||
options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
|
||||
}
|
||||
switch (options.method) {
|
||||
switch (options.method) { // http method
|
||||
case 'get':
|
||||
st = st.get(options.url)
|
||||
break;
|
||||
@ -56,10 +57,10 @@ export default class TestHelper {
|
||||
st = st.delete(options.url)
|
||||
break;
|
||||
}
|
||||
if (options.hasOwnProperty('req')) {
|
||||
if (options.hasOwnProperty('req')) { // request body
|
||||
st = st.send(options.req);
|
||||
}
|
||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {
|
||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) { // resolve basic auth
|
||||
if (this.auth.hasOwnProperty(options.auth.basic)) {
|
||||
st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
|
||||
}
|
||||
@ -69,21 +70,21 @@ export default class TestHelper {
|
||||
}
|
||||
st = st.expect('Content-type', /json/)
|
||||
.expect(options.httpStatus);
|
||||
if (options.hasOwnProperty('res')) {
|
||||
if (options.hasOwnProperty('res')) { // evaluate result
|
||||
return st.end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql(options.res);
|
||||
done();
|
||||
});
|
||||
}
|
||||
else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {
|
||||
else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) { // evaluate default results
|
||||
return st.end((err, res) => {
|
||||
if (err) return done (err);
|
||||
should(res.body).be.eql(this.res[options.httpStatus]);
|
||||
done();
|
||||
});
|
||||
}
|
||||
else {
|
||||
else { // return object to do .end() manually
|
||||
return st;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import db from '../db';
|
||||
|
||||
// script to load test db into dev db for a clean start
|
||||
|
||||
db.connect('dev', () => {
|
||||
console.info('dropping data...');
|
||||
db.drop(() => { // reset database
|
||||
|
Reference in New Issue
Block a user