added /materials route
This commit is contained in:
		
							
								
								
									
										387
									
								
								src/routes/material.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								src/routes/material.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,387 @@
 | 
			
		||||
import should from 'should/as-function';
 | 
			
		||||
import MaterialModel from '../models/material';
 | 
			
		||||
import TestHelper from "../helpers/test";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
describe('/material', () => {
 | 
			
		||||
  let server;
 | 
			
		||||
  before(done => TestHelper.before(done));
 | 
			
		||||
  beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
  afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
 | 
			
		||||
  describe('GET /materials', () => {
 | 
			
		||||
    it('returns all materials', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/materials',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.users.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('number');
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('works with an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/materials',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.users.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('number');
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/materials',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /material/{id}', () => {
 | 
			
		||||
    it('returns the right material', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        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}]}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns the right material for an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/material/100000000000000000000003',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        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('rejects an invalid id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/material/10000000000000000000000x',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unknown id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/material/100000000000000000000111',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('PUT /material/{id}', () => {
 | 
			
		||||
    it('returns the right material', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        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}]}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('changes the given properties', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        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}]}
 | 
			
		||||
        ,
 | 
			
		||||
      }).end(err => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).be.eql({_id: '100000000000000000000002', name: 'UltramidTKR4355G7', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], __v: 0}
 | 
			
		||||
          );
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects already existing material names', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {name: 'UltramidTKR4355G7'},
 | 
			
		||||
        res: {status: 'Material name already taken'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects wrong material properties', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an invalid id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/10000000000000000000000x',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {},
 | 
			
		||||
        res: {status: 'Invalid id'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000002',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from a read user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000002',
 | 
			
		||||
        auth: {basic: 'user'},
 | 
			
		||||
        httpStatus: 403,
 | 
			
		||||
        req: {}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 404 for an unknown material', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000111',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 404,
 | 
			
		||||
        req: {}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('DELETE /material/{id}', () => {
 | 
			
		||||
    it('deletes the material', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(0);
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects deleting a material referenced by samples');
 | 
			
		||||
    it('rejects an invalid id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/material/10000000000000000000000x',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        res: {status: 'Invalid id'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/material/100000000000000000000002',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from a read user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/material/100000000000000000000002',
 | 
			
		||||
        auth: {basic: 'user'},
 | 
			
		||||
        httpStatus: 403
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 404 for an unknown id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/material/100000000000000000000111',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /material/new', () => {
 | 
			
		||||
    it('returns the right material', 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: 5515798402}]}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        console.log(res.body);
 | 
			
		||||
        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', 5515798402);
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('stores the material', 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: []}
 | 
			
		||||
      }).end(err => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          console.log(data[0]);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', '__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].numbers).have.lengthOf(0);
 | 
			
		||||
          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}]},
 | 
			
		||||
        res: {status: 'Material name already taken'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects wrong material properties', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/material/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 'x', glass_fiber: 'x', carbon_fiber: 'x', numbers: [{colorxx: 'black', number: 'xxx'}]},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects incomplete material properties', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/material/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {name: 'Crastin CE 2510'},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/material/new',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from a read user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/material/new',
 | 
			
		||||
        auth: {basic: 'user'},
 | 
			
		||||
        httpStatus: 403,
 | 
			
		||||
        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/material/new',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: []}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										59
									
								
								src/routes/material.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/routes/material.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
import express from 'express';
 | 
			
		||||
 | 
			
		||||
import MaterialValidate from './validate/material';
 | 
			
		||||
import MaterialModel from '../models/material'
 | 
			
		||||
import IdValidate from './validate/id';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/materials', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.find({}).lean().exec((err, data) => {
 | 
			
		||||
    if(err) next(err);
 | 
			
		||||
    res.json(data.map(e => MaterialValidate.output(e)).filter(e => e !== null));  // 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;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.findById(req.params.id).lean().exec((err, data) => {
 | 
			
		||||
    if(err) next(err);
 | 
			
		||||
    console.log(data);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json(MaterialValidate.output(data));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/material/new', (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 !== undefined) {
 | 
			
		||||
    res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  MaterialModel.find({name: material.name}).lean().exec((err, data) => {
 | 
			
		||||
    if(err) next(err);
 | 
			
		||||
    if (data.length > 0) {
 | 
			
		||||
      res.status(400).json({status: 'Material name already taken'});
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    new MaterialModel(material).save((err, data) => {
 | 
			
		||||
      if(err) next(err);
 | 
			
		||||
      res.json(MaterialValidate.output(data.toObject()));
 | 
			
		||||
    });
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
@@ -48,6 +48,13 @@ describe('/user', () => {
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/users',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /user/{name}', () => {
 | 
			
		||||
@@ -119,6 +126,13 @@ describe('/user', () => {
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from an admin API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/user/janedoe',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('PUT /user/{name}', () => {
 | 
			
		||||
@@ -169,7 +183,7 @@ describe('/user', () => {
 | 
			
		||||
        req: {name: 'adminnew', email: 'adminnew@bosch.com', pass: 'Abc123##', location: 'Abt', device_name: 'test'}
 | 
			
		||||
      }).end(err => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          UserModel.find({name: 'adminnew'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
          UserModel.find({name: 'adminnew'}).lean().exec( (err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'device_name', 'key', '__v');
 | 
			
		||||
@@ -193,7 +207,7 @@ describe('/user', () => {
 | 
			
		||||
        req: {level: 'read'}
 | 
			
		||||
      }).end(err => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            should(data[0]).have.property('level', 'read');
 | 
			
		||||
@@ -211,7 +225,7 @@ describe('/user', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          should(res.body).be.eql({status: 'Invalid body format'});
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            should(data[0]).have.property('level', 'write');
 | 
			
		||||
@@ -229,7 +243,7 @@ describe('/user', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          should(res.body).be.eql({status: 'Username already taken'});
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            done();
 | 
			
		||||
@@ -312,6 +326,14 @@ describe('/user', () => {
 | 
			
		||||
        req: {}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'put',
 | 
			
		||||
        url: '/user/janedoe',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('DELETE /user/{name}', () => {
 | 
			
		||||
@@ -324,7 +346,7 @@ describe('/user', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
        UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(0);
 | 
			
		||||
          done();
 | 
			
		||||
@@ -340,7 +362,7 @@ describe('/user', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
        UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(0);
 | 
			
		||||
          done();
 | 
			
		||||
@@ -379,6 +401,40 @@ describe('/user', () => {
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/user/janedoe',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /user/key', () => {
 | 
			
		||||
    it('returns the right API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/user/key',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {key: TestHelper.auth.janedoe.key}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/user/key',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/user/key',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /user/new', () => {
 | 
			
		||||
@@ -410,7 +466,7 @@ describe('/user', () => {
 | 
			
		||||
        req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
 | 
			
		||||
      }).end(err => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          UserModel.find({name: 'johndoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
          UserModel.find({name: 'johndoe'}).lean().exec( (err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'device_name', 'key', '__v');
 | 
			
		||||
@@ -435,7 +491,7 @@ describe('/user', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          should(res.body).be.eql({status: 'Username already taken'});
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
 | 
			
		||||
          UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            done();
 | 
			
		||||
@@ -510,6 +566,14 @@ describe('/user', () => {
 | 
			
		||||
        req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/user/new',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /user/passreset', () => {
 | 
			
		||||
@@ -539,7 +603,7 @@ describe('/user', () => {
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('changes the user password', done => {
 | 
			
		||||
      UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data: any) => {
 | 
			
		||||
      UserModel.find({name: 'janedoe'}).lean().exec( (err, data: any) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const oldpass = data[0].pass;
 | 
			
		||||
        TestHelper.request(server, done, {
 | 
			
		||||
@@ -559,24 +623,4 @@ describe('/user', () => {
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /user/key', () => {
 | 
			
		||||
    it('returns the right API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/user/key',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {key: TestHelper.auth.janedoe.key}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/user/key',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
import express from 'express';
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
import bcrypt from 'bcryptjs';
 | 
			
		||||
 | 
			
		||||
import UserValidate from './validate/user';
 | 
			
		||||
import UserModel from '../models/user';
 | 
			
		||||
import mail from '../helpers/mail';
 | 
			
		||||
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
router.get('/users', (req, res) => {
 | 
			
		||||
  if (!req.auth(res, ['admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
@@ -168,4 +170,5 @@ router.post('/user/passreset', (req, res, next) => {
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
							
								
								
									
										17
									
								
								src/routes/validate/id.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/routes/validate/id.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
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 () {
 | 
			
		||||
    return this.id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static valid (id) {
 | 
			
		||||
    return this.id.validate(id).error === undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static parameter() {  // :id url parameter
 | 
			
		||||
    return ':id([0-9a-f]{24})';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								src/routes/validate/material.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/routes/validate/material.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
import joi from '@hapi/joi';
 | 
			
		||||
 | 
			
		||||
import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
export default class MaterialValidate {  // validate input for material
 | 
			
		||||
  private static material = {
 | 
			
		||||
    name: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    supplier: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    group: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    mineral: joi.number()
 | 
			
		||||
      .integer()
 | 
			
		||||
      .min(0)
 | 
			
		||||
      .max(100),
 | 
			
		||||
 | 
			
		||||
    glass_fiber: joi.number()
 | 
			
		||||
      .integer()
 | 
			
		||||
      .min(0)
 | 
			
		||||
      .max(100),
 | 
			
		||||
 | 
			
		||||
    carbon_fiber: joi.number()
 | 
			
		||||
      .integer()
 | 
			
		||||
      .min(0)
 | 
			
		||||
      .max(100),
 | 
			
		||||
 | 
			
		||||
    numbers: joi.array()
 | 
			
		||||
      .items(joi.object({
 | 
			
		||||
        color: joi.string()
 | 
			
		||||
          .max(128),
 | 
			
		||||
        number: joi.number()
 | 
			
		||||
          .min(0)
 | 
			
		||||
      }))
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate data, param: new(everything required)/change(available attributes are validated)
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return joi.object({
 | 
			
		||||
        name: this.material.name.required(),
 | 
			
		||||
        supplier: this.material.supplier.required(),
 | 
			
		||||
        group: this.material.group.required(),
 | 
			
		||||
        mineral: this.material.mineral.required(),
 | 
			
		||||
        glass_fiber: this.material.glass_fiber.required(),
 | 
			
		||||
        carbon_fiber: this.material.carbon_fiber.required(),
 | 
			
		||||
        numbers: this.material.numbers
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return joi.object({
 | 
			
		||||
        name: this.material.name,
 | 
			
		||||
        supplier: this.material.supplier,
 | 
			
		||||
        group: this.material.group,
 | 
			
		||||
        mineral: this.material.mineral,
 | 
			
		||||
        glass_fiber: this.material.glass_fiber,
 | 
			
		||||
        carbon_fiber: this.material.carbon_fiber,
 | 
			
		||||
        numbers: this.material.numbers
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
			
		||||
    data._id = data._id.toString();
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.material.name,
 | 
			
		||||
      supplier: this.material.supplier,
 | 
			
		||||
      group: this.material.group,
 | 
			
		||||
      mineral: this.material.mineral,
 | 
			
		||||
      glass_fiber: this.material.glass_fiber,
 | 
			
		||||
      carbon_fiber: this.material.carbon_fiber,
 | 
			
		||||
      numbers: this.material.numbers
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +1,34 @@
 | 
			
		||||
import joi from '@hapi/joi';
 | 
			
		||||
import globals from '../../globals';
 | 
			
		||||
 | 
			
		||||
import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
export default class UserValidate {  // validate input for user
 | 
			
		||||
  private static user = {
 | 
			
		||||
    _id: joi.any(),
 | 
			
		||||
    name: joi.string()
 | 
			
		||||
      .alphanum()
 | 
			
		||||
      .lowercase(),
 | 
			
		||||
      .lowercase()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    email: joi.string()
 | 
			
		||||
      .email({minDomainSegments: 2})
 | 
			
		||||
      .lowercase(),
 | 
			
		||||
      .lowercase()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    pass: joi.string()
 | 
			
		||||
      .pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$')),
 | 
			
		||||
      .pattern(new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&\'()*+,-.\\/:;<=>?@[\\]^_`{|}~])(?=\\S+$).{8,}$'))
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    level: joi.string()
 | 
			
		||||
      .valid(...globals.levels),
 | 
			
		||||
 | 
			
		||||
    location: joi.string()
 | 
			
		||||
      .alphanum(),
 | 
			
		||||
      .alphanum()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    device_name: joi.string()
 | 
			
		||||
      .allow('')
 | 
			
		||||
      .max(128),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  private static specialUsernames = ['admin', 'user', 'key', 'new', 'passreset'];  // names a user cannot take
 | 
			
		||||
@@ -63,14 +69,15 @@ export default class UserValidate {  // validate input for user
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
			
		||||
    data._id = data._id.toString();
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      _id: joi.any(),
 | 
			
		||||
      name: joi.string(),
 | 
			
		||||
      email: joi.string(),
 | 
			
		||||
      level: joi.string(),
 | 
			
		||||
      location: joi.string(),
 | 
			
		||||
      device_name: joi.string().allow('')
 | 
			
		||||
    }).validate(data, {stripUnknown: true})
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.user.name,
 | 
			
		||||
      email: this.user.email,
 | 
			
		||||
      level: this.user.level,
 | 
			
		||||
      location: this.user.location,
 | 
			
		||||
      device_name: this.user.device_name
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user