diff --git a/.gitignore b/.gitignore index 44fc308..960d4a2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +trybe.yml + # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml diff --git a/index.js b/index.js index a85ac1a..38ce752 100644 --- a/index.js +++ b/index.js @@ -2,22 +2,15 @@ const express = require('express'); const products = require('./controllers/products'); const sales = require('./controllers/sales'); -const PORT = '3000'; - const app = express(); app.use(express.json()); -// não remova esse endpoint, e para o avaliador funcionar -app.get('/', (_request, response) => { - response.send(); -}); - app.use('/products', products); app.use('/sales', sales); app.all('*', (req, res) => res.status(404).send('Router not found')); -app.listen(PORT, () => { +app.listen(process.env.PORT, () => { console.log('Online'); }); \ No newline at end of file diff --git a/models/connection.js b/models/connection.js index e691381..a6f35b2 100644 --- a/models/connection.js +++ b/models/connection.js @@ -6,7 +6,7 @@ const OPTIONS = { useUnifiedTopology: true, }; -const MONGO_DB_URL = process.env.DB_URL || 'mongodb://mongodb:27017/StoreManager'; +const MONGO_DB_URL = process.env.DB_URL; let db = null; diff --git a/test/bonus.test.js b/test/bonus.test.js new file mode 100644 index 0000000..22e6e4d --- /dev/null +++ b/test/bonus.test.js @@ -0,0 +1,61 @@ +const fs = require('fs').promises; +const util = require('util'); +const { exec: callbackExec } = require('child_process'); +const path = require('path'); + +const exec = util.promisify(callbackExec); + +const mongoDbUrl = 'mongodb://localhost:27017'; +const url = 'http://localhost:3000'; +const NPX_NYC_COMMAND = + (unit) => `npx nyc --all --include ${unit} --reporter json-summary mocha test/unit/${unit}.js --exit`; + +function readCoverageFile() { + const COVERAGE_FILE_PATH = path.join(__dirname, '..', 'coverage', 'coverage-summary.json'); + return fs.readFile(COVERAGE_FILE_PATH).then(JSON.parse); +} + +describe('11 - Escreva testes para seus models', () => { + beforeAll(async () => { + await exec(NPX_NYC_COMMAND('models')); + }); + + afterAll(async () => { + await exec('rm -rf coverage .nyc_output'); + }); + + it('Será validado que cobertura total das linhas dos arquivos na pasta `models` é maior ou igual a 80%', async () => { + const coverageResults = await readCoverageFile(); + expect(coverageResults.total.lines.pct).toBeGreaterThanOrEqual(80); + }); +}); + +describe('12 - Escreva testes para seus services', () => { + beforeAll(async () => { + await exec(NPX_NYC_COMMAND('services')); + }); + + afterAll(async () => { + await exec('rm -rf coverage .nyc_output'); + }); + + it('Será validado que cobertura total das linhas dos arquivos na pasta `services` é maior ou igual a 80%', async () => { + const coverageResults = await readCoverageFile(); + expect(coverageResults.total.lines.pct).toBeGreaterThanOrEqual(80); + }); +}); + +describe('13 - Escreva testes para seus controllers', () => { + beforeAll(async () => { + await exec(NPX_NYC_COMMAND('controllers')); + }); + + afterAll(async () => { + await exec('rm -rf coverage .nyc_output'); + }); + + it('Será validado que cobertura total das linhas dos arquivos na pasta `controllers` é maior ou igual a 80%', async () => { + const coverageResults = await readCoverageFile(); + expect(coverageResults.total.lines.pct).toBeGreaterThanOrEqual(80); + }); +}); diff --git a/test/products.test.js b/test/products.test.js new file mode 100644 index 0000000..63af4aa --- /dev/null +++ b/test/products.test.js @@ -0,0 +1,461 @@ +const frisby = require('frisby'); +const { MongoClient } = require('mongodb'); + +// const mongoDbUrl = 'mongodb://mongodb:27017/StoreManager'; +const mongoDbUrl = 'mongodb://localhost:27017/StoreManager'; +const url = 'http://localhost:3000'; +const invalidId = 99999; + +describe('1 - Crie um endpoint para o cadastro de produtos', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const myobj = { name: 'Martelo de Thor', quantity: 10 }; + await db.collection('products').insertOne(myobj); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que não é possível criar um produto com o nome menor que 5 caracteres', async () => { + await frisby + .post(`${url}/products/`, { + name: 'Rai', + quantity: 100, + }) + .expect('status', 422) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const error = body.err.code; + const { message } = body.err; + expect(error).toEqual('invalid_data'); + expect(message).toEqual('"name" length must be at least 5 characters long'); + }); + }); + + it('Será validado que não é possível criar um produto com o mesmo nomede outro já existente', async () => { + await frisby + .post(`${url}/products/`, { + name: 'Martelo de Thor', + quantity: 100, + }) + .expect('status', 422) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const error = body.err.code; + const { message } = body.err; + expect(error).toEqual('invalid_data'); + expect(message).toEqual('Product already exists'); + }); + }); + + it('Será validado que não é possível criar um produto com quantidade menor que zero', async () => { + await frisby + .post(`${url}/products`, { + name: 'Produto do Batista', + quantity: -1, + }) + .expect('status', 422) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const error = body.err.code; + const { message } = body.err; + expect(error).toEqual('invalid_data'); + expect(message).toEqual('"quantity" must be larger than or equal to 1'); + }); + }); + + it('Será validado que não é possível criar um produto com quantidade igual a zero', async () => { + await frisby + .post(`${url}/products`, { + name: 'Produto do Batista', + quantity: 0, + }) + .expect('status', 422) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const error = body.err.code; + const { message } = body.err; + expect(error).toEqual('invalid_data'); + expect(message).toEqual('"quantity" must be larger than or equal to 1'); + }); + }); + + it('Será validado que não é possível criar um produto com uma string no campo quantidade', async () => { + await frisby + .post(`${url}/products`, { + name: 'Produto do Batista', + quantity: 'string', + }) + .expect('status', 422) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const error = body.err.code; + const { message } = body.err; + expect(error).toEqual('invalid_data'); + expect(message).toEqual('"quantity" must be a number'); + }); + }); + + it('Será validado que é possível criar um produto com sucesso', async () => { + await frisby + .post(`${url}/products`, { + name: 'Arco do Gavião Arqueiro', + quantity: 1, + }) + .expect('status', 201) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const productName = body.name; + const quantityProduct = body.quantity; + expect(productName).toEqual('Arco do Gavião Arqueiro'); + expect(quantityProduct).toEqual(1); + expect(body).toHaveProperty('_id'); + }); + }); +}); + +describe('2 - Crie um endpoint para listar os produtos', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [{ name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que todos produtos estão sendo retornados', async () => { + await frisby + .get(`${url}/products`) + .expect('status', 200) + .then((res) => { + let { body } = res; + body = JSON.parse(body); + const firstProductName = body.products[0].name; + const firstQuantityProduct = body.products[0].quantity; + const secondProductName = body.products[1].name; + const secondQuantityProduct = body.products[1].quantity; + const thirdProductName = body.products[2].name; + const thirdQuantityProduct = body.products[2].quantity; + + expect(firstProductName).toEqual('Martelo de Thor'); + expect(firstQuantityProduct).toEqual(10); + expect(secondProductName).toEqual('Traje de encolhimento'); + expect(secondQuantityProduct).toEqual(20); + expect(thirdProductName).toEqual('Escudo do Capitão América'); + expect(thirdQuantityProduct).toEqual(30); + }); + }); + + it('Será validado que não é possível listar um produto que não existe', async () => { + await frisby.get(`${url}/products/${invalidId}`) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + const error = json.err.code; + const { message } = json.err; + expect(error).toEqual('invalid_data'); + expect(message).toEqual('Wrong id format'); + }); + }); + + it('Será validado que é possível listar um determinado produto', async () => { + let result; + + await frisby + .post(`${url}/products`, { + name: 'Armadura do Homem de Ferro', + quantity: 40, + }) + .expect('status', 201) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + responseProductId = result._id; + }); + + await frisby.get(`${url}/products/${responseProductId}`) + .expect('status', 200) + .then((secondResponse) => { + const { json } = secondResponse; + const productName = json.name; + const quantityProduct = json.quantity; + expect(productName).toEqual('Armadura do Homem de Ferro'); + expect(quantityProduct).toEqual(40); + }); + }); +}); + +describe('3 - Crie um endpoint para atualizar um produto', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const myobj = { name: 'Martelo de Thor', quantity: 10 }; + await db.collection('products').insertOne(myobj); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que não é possível atualizar um produto com o nome menor que 5 caracteres', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby.put(`${url}/products/${resultProductId}`, + { + name: 'Mar', + quantity: 10, + }) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toEqual('invalid_data'); + expect(json.err.message).toEqual('"name" length must be at least 5 characters long'); + }); + }); + + it('Será validado que não é possível atualizar um produto com quantidade menor que zero', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby.put(`${url}/products/${resultProductId}`, + { + name: 'Martelo de Thor', + quantity: -1, + }) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toEqual('invalid_data'); + expect(json.err.message).toEqual('"quantity" must be larger than or equal to 1'); + }); + }); + + it('Será validado que não é possível atualizar um produto com quantidade igual a zero', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby.put(`${url}/products/${resultProductId}`, + { + name: 'Martelo de Thor', + quantity: 0, + }) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toEqual('invalid_data'); + expect(json.err.message).toEqual('"quantity" must be larger than or equal to 1'); + }); + }); + + it('Será validado que não é possível atualizar um produto com uma string no campo quantidade', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby.put(`${url}/products/${resultProductId}`, + { + name: 'Martelo de Thor', + quantity: 'string', + }) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toEqual('invalid_data'); + expect(json.err.message).toEqual('"quantity" must be a number'); + }); + }); + + it('Será validado que é possível atualizar um produto com sucesso', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby.put(`${url}/products/${resultProductId}`, + { + name: 'Machado de Thor', + quantity: 20, + }) + .expect('status', 200) + .then((secondResponse) => { + const { json } = secondResponse; + const productName = json.name; + const quantityProduct = json.quantity; + expect(productName).toEqual('Machado de Thor'); + expect(quantityProduct).toEqual(20); + }); + }); +}); + +describe('4 - Crie um endpoint para deletar um produto', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const myobj = { name: 'Martelo de Thor', quantity: 10 }; + await db.collection('products').insertOne(myobj); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que não é possível deletar um produto com sucesso', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby.delete(`${url}/products/${resultProductId}`) + .expect('status', 200); + + await frisby.get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + expect(result.products.length).toBe(0); + }); + }); + + it('Será validado que não é possível deletar um produto que não existe', async () => { + await frisby + .delete(`${url}/products/${invalidId}`) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toEqual('invalid_data'); + expect(json.err.message).toEqual('Wrong id format'); + }); + }); +}); diff --git a/test/sales.test.js b/test/sales.test.js new file mode 100644 index 0000000..676fe28 --- /dev/null +++ b/test/sales.test.js @@ -0,0 +1,838 @@ +const frisby = require('frisby'); +const { MongoClient } = require('mongodb'); + +// const mongoDbUrl = 'mongodb://mongodb:27017/StoreManager'; +const mongoDbUrl = 'mongodb://localhost:27017/StoreManager'; +const url = 'http://localhost:3000'; +const invalidId = 99999; + +describe('5 - Crie um endpoint para cadastrar vendas', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [ + { name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }, + ]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que não é possível cadastrar compras com quantidade menor que zero', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: -1, + }, + ]) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toBe('invalid_data'); + expect(json.err.message).toBe('Wrong product ID or invalid quantity'); + }); + }); + + it('Será validado que não é possível cadastrar compras com quantidade igual a zero', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 0, + }, + ]) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toBe('invalid_data'); + expect(json.err.message).toBe('Wrong product ID or invalid quantity'); + }); + }); + + it('Será validado que não é possível cadastrar compras com uma string no campo quantidade', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 'String', + }, + ]) + .expect('status', 422) + .then((secondResponse) => { + const { json } = secondResponse; + expect(json.err.code).toBe('invalid_data'); + expect(json.err.message).toBe('Wrong product ID or invalid quantity'); + }); + }); + + it('Será validado que é possível criar uma compra com sucesso', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((secondResponse) => { + const { json } = secondResponse; + const idFirstItenSold = json.itensSold[0].productId; + const quantityFirstItenSold = json.itensSold[0].quantity; + expect(json).toHaveProperty('_id'); + expect(idFirstItenSold).toBe(resultProductId); + expect(quantityFirstItenSold).toBe(2); + }); + }); + + it('Será validado que é possível criar várias compras com sucesso', async () => { + let result; + let resultProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + { + productId: resultProductId, + quantity: 6, + }, + ]) + .expect('status', 200) + .then((secondResponse) => { + const { json } = secondResponse; + const idFirstItenSold = json.itensSold[0].productId; + const quantityFirstItenSold = json.itensSold[0].quantity; + const idSecondItenSold = json.itensSold[1].productId; + const quantitySecondItenSold = json.itensSold[1].quantity; + expect(json).toHaveProperty('_id'); + expect(idFirstItenSold).toBe(resultProductId); + expect(quantityFirstItenSold).toBe(2); + expect(idSecondItenSold).toBe(resultProductId); + expect(quantitySecondItenSold).toBe(6); + }); + }); +}); + +describe('6 - Crie um endpoint para listar as vendas', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [ + { name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }, + ]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que todas as vendas estão sendo retornadas', async () => { + let result; + let resultProductId; + let resultSales; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + { + productId: resultProductId, + quantity: 6, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby + .get(`${url}/sales/`) + .expect('status', 200) + .then((responseAll) => { + const { body } = responseAll; + const resultSalesAll = JSON.parse(body); + const idSales = resultSalesAll.sales[0]._id; + const idFirstProductSales = resultSalesAll.sales[0].itensSold[0].productId; + const quantityFirstProductSales = resultSalesAll.sales[0].itensSold[0].quantity; + const idSecondProductSales = resultSalesAll.sales[0].itensSold[1].productId; + const quantitySecondProductSales = resultSalesAll.sales[0].itensSold[1].quantity; + + expect(idSales).toBe(resultSalesId); + expect(idFirstProductSales).toBe(resultProductId); + expect(quantityFirstProductSales).toBe(2); + expect(idSecondProductSales).toBe(resultProductId); + expect(quantitySecondProductSales).toBe(6); + }); + }); + + it('Será validado que é possível listar uma determinada venda', async () => { + let result; + let resultSales; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: result.products[0]._id, + quantity: 2, + }, + { + productId: result.products[1]._id, + quantity: 6, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby + .get(`${url}/sales/`) + .expect('status', 200) + .then((responseOne) => { + const { body } = responseOne; + const responseAll = JSON.parse(body); + const idSales = responseAll.sales[0]._id; + const idFirstProductSales = responseAll.sales[0].itensSold[0].productId; + const quantityFirstProductSales = responseAll.sales[0].itensSold[0].quantity; + const idSecondProductSales = responseAll.sales[0].itensSold[1].productId; + const quantitySecondProductSales = responseAll.sales[0].itensSold[1].quantity; + + expect(idSales).toBe(resultSales._id); + expect(idFirstProductSales).toBe(result.products[0]._id); + expect(quantityFirstProductSales).toBe(2); + expect(idSecondProductSales).toBe(result.products[1]._id); + expect(quantitySecondProductSales).toBe(6); + }); + }); + + it('Será validado que não é possível listar uma venda inexistente', async () => { + await frisby + .get(`${url}/sales/9999`) + .expect('status', 404) + .then((responseOne) => { + const { body } = responseOne; + const responseError = JSON.parse(body); + expect(responseError.err.code).toEqual('not_found'); + expect(responseError.err.message).toEqual('Sale not found'); + }); + }); +}); + +describe('7 - Crie um endpoint para atualizar uma venda', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [ + { name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }, + ]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que não é possível atualizar vendas com quantidade menor que zero', async () => { + let result; + let resultProductId; + let resultSales; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby + .put(`${url}/sales/${resultSales._id}`, [ + { + productId: resultProductId, + quantity: -1, + }, + ]) + .expect('status', 422) + .then((responseEdit) => { + const { body } = responseEdit; + const responseEditBody = JSON.parse(body); + const error = responseEditBody.err.code; + const { message } = responseEditBody.err; + expect(error).toBe('invalid_data'); + expect(message).toBe('Wrong product ID or invalid quantity'); + }); + }); + + it('Será validado que não é possível atualizar vendas com quantidade igual a zero', async () => { + let result; + let resultProductId; + let resultSales; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby + .put(`${url}/sales/${resultSalesId}`, [ + { + productId: resultProductId, + quantity: 0, + }, + ]) + .expect('status', 422) + .then((responseEdit) => { + const { body } = responseEdit; + const responseEditBody = JSON.parse(body); + const error = responseEditBody.err.code; + const { message } = responseEditBody.err; + expect(error).toBe('invalid_data'); + expect(message).toBe('Wrong product ID or invalid quantity'); + }); + }); + + it('Será validado que não é possível atualizar vendas com uma string no campo quantidade', async () => { + let result; + let resultProductId; + let resultSales; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby + .put(`${url}/sales/${resultSalesId}`, [ + { + productId: resultProductId, + quantity: 'String', + }, + ]) + .expect('status', 422) + .then((responseEdit) => { + const { body } = responseEdit; + const responseEditBody = JSON.parse(body); + const error = responseEditBody.err.code; + const { message } = responseEditBody.err; + expect(error).toBe('invalid_data'); + expect(message).toBe('Wrong product ID or invalid quantity'); + }); + }); + + it('Será validado que é possível atualizar uma venda com sucesso', async () => { + let result; + let resultProductId; + let resultSales; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby + .put(`${url}/sales/${resultSalesId}`, [ + { + productId: resultProductId, + quantity: 5, + }, + ]) + .expect('status', 200) + .then((responseEdit) => { + const { body } = responseEdit; + const responseEditBody = JSON.parse(body); + const salesId = responseEditBody._id; + const idProductSales = responseEditBody.itensSold[0].productId; + const quantityProductSales = responseEditBody.itensSold[0].quantity; + expect(salesId).toBe(resultSalesId); + expect(idProductSales).toBe(resultSales.itensSold[0].productId); + expect(quantityProductSales).toBe(5); + }); + }); +}); + +describe('8 - Crie um endpoint para deletar uma venda', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [ + { name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }, + ]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que é possível deletar uma venda com sucesso', async () => { + let result; + let resultSales; + let resultProductId; + let resultSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + resultProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: resultProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + resultSalesId = resultSales._id; + }); + + await frisby.delete(`${url}/sales/${resultSalesId}`).expect('status', 200); + + await frisby + .get(`${url}/sales/${resultSalesId}`) + .expect('status', 404) + .expect((resultGet) => { + const { body } = resultGet; + const resultGetBody = JSON.parse(body); + const error = resultGetBody.err.code; + const { message } = resultGetBody.err; + expect(error).toBe('not_found'); + expect(message).toBe('Sale not found'); + }); + }); + + it('Será validado que não é possível deletar uma venda que não existe', async () => { + await frisby + .delete(`${url}/sales/${invalidId}`) + .expect('status', 422) + .expect((resultDelete) => { + const { body } = resultDelete; + const resultDeleteBody = JSON.parse(body); + const error = resultDeleteBody.err.code; + const { message } = resultDeleteBody.err; + expect(error).toBe('invalid_data'); + expect(message).toBe('Wrong sale ID format'); + }); + }); +}); + +describe('9 - Atualize a quantidade de produtos', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [ + { name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }, + ]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que é possível a quantidade do produto atualize ao fazer uma compra', async () => { + let result; + let responseProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + responseProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: responseProductId, + quantity: 2, + }, + ]) + .expect('status', 200); + + await frisby + .get(`${url}/products/${responseProductId}`) + .expect('status', 200) + .expect((responseProducts) => { + const { body } = responseProducts; + const resultProducts = JSON.parse(body); + const quantityProducts = resultProducts.quantity; + expect(quantityProducts).toBe(8); + }); + }); + + it('Será validado que é possível a quantidade do produto atualize ao deletar uma compra', async () => { + let result; + let resultSales; + let responseProductId; + let responseSalesId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + responseProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: responseProductId, + quantity: 2, + }, + ]) + .expect('status', 200) + .then((responseSales) => { + const { body } = responseSales; + resultSales = JSON.parse(body); + responseSalesId = resultSales._id; + }); + + await frisby.delete(`${url}/sales/${responseSalesId}`).expect('status', 200); + + await frisby + .get(`${url}/products/${responseProductId}`) + .expect('status', 200) + .expect((responseProducts) => { + const { body } = responseProducts; + const resultProducts = JSON.parse(body); + const quantityProducts = resultProducts.quantity; + expect(quantityProducts).toBe(10); + }); + }); +}); + +describe('10 - Valide a quantidade de produtos', () => { + let connection; + let db; + + beforeAll(async () => { + connection = await MongoClient.connect(mongoDbUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + db = connection.db('StoreManager'); + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + beforeEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + const products = [ + { name: 'Martelo de Thor', quantity: 10 }, + { name: 'Traje de encolhimento', quantity: 20 }, + { name: 'Escudo do Capitão América', quantity: 30 }, + ]; + await db.collection('products').insertMany(products); + }); + + afterEach(async () => { + await db.collection('products').deleteMany({}); + await db.collection('sales').deleteMany({}); + }); + + afterAll(async () => { + await connection.close(); + }); + + it('Será validado que o estoque do produto nunca fique com a quantidade menor que zero', async () => { + let result; + let responseProductId; + + await frisby + .get(`${url}/products/`) + .expect('status', 200) + .then((response) => { + const { body } = response; + result = JSON.parse(body); + responseProductId = result.products[0]._id; + }); + + await frisby + .post(`${url}/sales/`, [ + { + productId: responseProductId, + quantity: 100, + }, + ]) + .expect('status', 404) + .then((responseSales) => { + const { json } = responseSales; + expect(json.err.code).toBe('stock_problem'); + expect(json.err.message).toBe('Such amount is not permitted to sell'); + }); + }); +});