diff --git a/lib/routes/fruits.js b/lib/routes/fruits.js index 69b110fe..a715ebaf 100644 --- a/lib/routes/fruits.js +++ b/lib/routes/fruits.js @@ -10,22 +10,20 @@ router.get('/fruits/:id', (request, response) => { const {id} = request.params; fruits.find(id).then(result => { if (result.rowCount === 0) { - response.status = 404; + response.status(404); return response.send(`Item ${id} not found`); } return response.send(result.rows[0]); - }).catch((err) => { - response.status = 400; - response.send(err); + }).catch(() => { + response.sendStatus(400); }); }); router.get('/fruits', (request, response) => { fruits.findAll().then(results => { response.send(results.rows); - }).catch((err) => { - response.status = 400; - response.send(err); + }).catch(() => { + response.sendStatus(400); }); }); @@ -34,7 +32,7 @@ router.post('/fruits', validations.validateCreate, (request, response) => { return fruits.create(name, stock).then(() => { response.sendStatus(201); }).catch((err) => { - response.status = 400; + response.status(400); response.send(err); }); }); @@ -44,12 +42,12 @@ router.put('/fruits/:id', validations.validateUpdate, (request, response) => { const {id} = request.params; fruits.update({name, stock, id}).then((result) => { if (result.rowCount === 0) { - response.status = 404; + response.status(404); return response.send(`Unknown item ${id}`); } return response.sendStatus(204); }).catch((err) => { - response.status = 400; + response.status(400); response.send(err); }); }); @@ -58,12 +56,12 @@ router.delete('/fruits/:id', (request, response) => { const {id} = request.params; fruits.remove(id).then((result) => { if (result.rowCount === 0) { - response.status = 404; + response.status(404); return response.send(`Unknown item ${id}`); } return response.sendStatus(204); }).catch((err) => { - response.status = 400; + response.status(400); response.send(err); }); }); diff --git a/lib/validations/index.js b/lib/validations/index.js index 74d059f1..b84333a8 100644 --- a/lib/validations/index.js +++ b/lib/validations/index.js @@ -1,25 +1,21 @@ 'use strict'; function validateCreate (request, response, next) { - if (!request.body) { - response.status = 400; - return response.send('The body must not be null'); - } - + // No need to check for no body, express will make body an empty object const {name, stock, id} = request.body; if (!name) { - response.status = 400; + response.status(400); return response.send('The name must not be null'); } if (!stock) { - response.status = 400; + response.status(400); return response.send('The stock must not be greater or equal to 0'); } if (id) { - response.status = 400; + response.status(400); return response.send('The created item already contains an id'); } @@ -27,25 +23,21 @@ function validateCreate (request, response, next) { } function validateUpdate (request, response, next) { - if (!request.body) { - response.status = 400; - return response.send('The body must not be null'); - } - + // No need to check for no body, express will make body an empty object const {name, stock, id} = request.body; if (!name) { - response.status = 400; + response.status(400); return response.send('The name must not be null'); } if (!stock) { - response.status = 400; + response.status(400); return response.send('The stock must not be greater or equal to 0'); } if (id && id !== request.params.id) { - response.status = 400; + response.status(400); return response.send('The id cannot be changed'); } diff --git a/test/fruits-api-test.js b/test/fruits-api-test.js new file mode 100644 index 00000000..a9a367ba --- /dev/null +++ b/test/fruits-api-test.js @@ -0,0 +1,54 @@ +'use strict'; + +const test = require('tape'); +const proxyquire = require('proxyquire'); + +const mockDb = { + query: () => { + return Promise.resolve(); + } +}; + +const fruits = proxyquire('../lib/api/fruits', { + '../db': mockDb +}); + +test('test api methods', (t) => { + t.equal(typeof fruits.find, 'function', 'find method should be a function'); + t.equal(typeof fruits.findAll, 'function', 'findAll method should be a function'); + t.equal(typeof fruits.create, 'function', 'create method should be a function'); + t.equal(typeof fruits.update, 'function', 'update method should be a function'); + t.equal(typeof fruits.remove, 'function', 'remove method should be a function'); + + t.end(); +}); + +test('test find all', (t) => { + const result = fruits.findAll(); + t.equal(result instanceof Promise, true, 'should return a promise'); + t.end(); +}); + +test('test find', (t) => { + const result = fruits.find('id'); + t.equal(result instanceof Promise, true, 'should return a promise'); + t.end(); +}); + +test('test create', (t) => { + const result = fruits.create('name', 'stock'); + t.equal(result instanceof Promise, true, 'should return a promise'); + t.end(); +}); + +test('test update', (t) => { + const result = fruits.update({name: 'name', stock: 'stock', id: 1}); + t.equal(result instanceof Promise, true, 'should return a promise'); + t.end(); +}); + +test('test remove', (t) => { + const result = fruits.remove('id'); + t.equal(result instanceof Promise, true, 'should return a promise'); + t.end(); +}); diff --git a/test/fruits-test.js b/test/fruits-test.js new file mode 100644 index 00000000..c873147d --- /dev/null +++ b/test/fruits-test.js @@ -0,0 +1,465 @@ +'use strict'; + +const test = require('tape'); +const supertest = require('supertest'); +const proxyquire = require('proxyquire'); + +const mockDb = { + init: () => { + return Promise.resolve(); + } +}; + +test('test GET all fruits', (t) => { + const mockApi = { + findAll: () => Promise.resolve({rows: [{id: 1}]}) + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .get('/api/fruits') + .expect('Content-Type', /json/) + .expect(200) + .then(response => { + t.equal(Array.isArray(response.body), true, 'should return an array'); + t.equal(response.body.length, 1, 'should have a body length of 1'); + t.end(); + }); +}); + +test('test GET all fruits - error', (t) => { + const mockApi = { + findAll: () => Promise.reject(new Error('error')) + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .get('/api/fruits') + .expect(400) + .then(response => { + t.end(); + }); +}); + +test('test GET fruit', (t) => { + const mockApi = { + find: (id) => { + t.equal(id, '1', 'id should be 1 from the request params'); + return Promise.resolve({rows: [{id: id}]}); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .get('/api/fruits/1') + .expect('Content-Type', /json/) + .expect(200) + .then(response => { + t.equal(Array.isArray(response.body), false, 'should not return an array'); + t.equal(response.body.id, '1', 'should have an id of 1'); + t.end(); + }); +}); + +test('test GET fruit - return 404', (t) => { + const mockApi = { + find: () => Promise.resolve({rowCount: 0}) + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .get('/api/fruits/1') + .expect(404) + .then(response => { + t.equal(response.text, 'Item 1 not found', 'shhould have a message about not found id'); + t.end(); + }); +}); + +test('test GET fruit - error', (t) => { + const mockApi = { + find: () => Promise.reject(new Error('error')) + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .get('/api/fruits/1') + .expect(400) + .then(response => { + t.end(); + }); +}); + +test('test POST fruit', (t) => { + const fruitData = { + name: 'Banana', + stock: 10 + }; + + const mockApi = { + create: (name, stock) => { + t.equal(name, fruitData.name, `respone.body.name should be ${fruitData.name}`); + t.equal(stock, fruitData.stock, `respone.body.stock should be ${fruitData.stock}`); + return Promise.resolve(); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .post('/api/fruits') + .send(fruitData) + .expect(201) + .then(response => { + t.end(); + }); +}); + +test('test POST fruit - error - no name', (t) => { + const app = proxyquire('../app', { + './lib/db': mockDb + }); + + supertest(app) + .post('/api/fruits') + .expect(400) + .then(response => { + t.equal(response.text, 'The name must not be null', 'has a need name message'); + t.end(); + }); +}); + +test('test POST fruit - error - no stock', (t) => { + const app = proxyquire('../app', { + './lib/db': mockDb + }); + + supertest(app) + .post('/api/fruits') + .send({name: 'Banana'}) + .expect(400) + .then(response => { + t.equal(response.text, 'The stock must not be greater or equal to 0', 'has a need stock message'); + t.end(); + }); +}); + +test('test POST fruit - error - id error', (t) => { + const app = proxyquire('../app', { + './lib/db': mockDb + }); + + supertest(app) + .post('/api/fruits') + .send({name: 'Banana', stock: 10, id: 22}) + .expect(400) + .then(response => { + t.equal(response.text, 'The created item already contains an id', 'has an id error message'); + t.end(); + }); +}); + +test('test POST fruit - error', (t) => { + const fruitData = { + name: 'Banana', + stock: 10 + }; + + const mockApi = { + create: (name, stock) => { + return Promise.reject(new Error('error')); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .post('/api/fruits') + .send(fruitData) + .expect(400) + .then(response => { + t.end(); + }); +}); + +test('test PUT fruit', (t) => { + const fruitData = { + name: 'Banana', + stock: 10, + id: '20' + }; + + const mockApi = { + update: (options) => { + t.equal(options.name, fruitData.name, `respone.body.name should be ${fruitData.name}`); + t.equal(options.stock, fruitData.stock, `respone.body.stock should be ${fruitData.stock}`); + t.equal(options.id, fruitData.id, `respone.body.stock should be ${fruitData.stock}`); + return Promise.resolve({rowCount: 1}); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .put('/api/fruits/20') + .send(fruitData) + .expect(204) + .then(response => { + t.end(); + }); +}); + +test('test PUT fruit - error - no name', (t) => { + const app = proxyquire('../app', { + './lib/db': mockDb + }); + + supertest(app) + .put('/api/fruits/20') + .expect(400) + .then(response => { + t.equal(response.text, 'The name must not be null', 'has a need name message'); + t.end(); + }); +}); + +test('test PUT fruit - error - no stock', (t) => { + const app = proxyquire('../app', { + './lib/db': mockDb + }); + + supertest(app) + .put('/api/fruits/20') + .send({name: 'name'}) + .expect(400) + .then(response => { + t.equal(response.text, 'The stock must not be greater or equal to 0', 'has a need stock message'); + t.end(); + }); +}); + +test('test PUT fruit - error - id error', (t) => { + const app = proxyquire('../app', { + './lib/db': mockDb + }); + + supertest(app) + .put('/api/fruits/20') + .send({name: 'Banana', stock: 10, id: '22'}) + .expect(400) + .then(response => { + t.equal(response.text, 'The id cannot be changed', 'id error message'); + t.end(); + }); +}); + +test('test PUT fruit - not found', (t) => { + const fruitData = { + name: 'Banana', + stock: 10, + id: '20' + }; + + const mockApi = { + update: (options) => { + return Promise.resolve({rowCount: 0}); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .put('/api/fruits/20') + .send(fruitData) + .expect(404) + .then(response => { + t.equal(response.text, 'Unknown item 20', 'has unknown update error'); + t.end(); + }); +}); + +test('test PUT fruit - error', (t) => { + const fruitData = { + name: 'Banana', + stock: 10, + id: '22' + }; + + const mockApi = { + update: (name, stock) => { + return Promise.reject(new Error('error')); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .put('/api/fruits/22') + .send(fruitData) + .expect(400) + .then(response => { + t.end(); + }); +}); + +test('test DELETE fruit', (t) => { + const mockApi = { + remove: (id) => { + t.equal(id, '1', 'id should be 1 from the request params'); + return Promise.resolve({rowCount: 1}); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .delete('/api/fruits/1') + .expect(204) + .then(response => { + t.end(); + }); +}); + +test('test DELETE fruit - error - not found', (t) => { + const mockApi = { + remove: (id) => { + return Promise.resolve({rowCount: 0}); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .delete('/api/fruits/1') + .expect(404) + .then(response => { + t.equal(response.text, 'Unknown item 1', 'has unkown error text'); + t.end(); + }); +}); + +test('test DELETE fruit - error', (t) => { + const mockApi = { + remove: (id) => { + return Promise.reject(new Error('error')); + } + }; + + // Mock the nested require + const routesStub = proxyquire('../lib/routes/fruits', { + '../api/fruits': mockApi + }); + + const app = proxyquire('../app', { + './lib/db': mockDb, + './lib/routes/fruits': routesStub + }); + + supertest(app) + .delete('/api/fruits/1') + .expect(400) + .then(response => { + t.end(); + }); +});