Este projeto consiste em desenvolver todas as camadas de uma API na arquitetura MSC (Models, Services e Controllers).
A API a ser construída trata-se de um sistema de gerenciamento de vendas, onde será possível criar, visualizar, deletar e atualizar produtos e vendas.
Será utilizardo o banco MySQL para a gestão de dados. Além disso, a API sserá RESTful.
Além disso a aplicação irá contar com testes unitários via Chai e Sinon
- Habilidades
- Dependências Necessárias
- Requisitos do projeto
- Linter
- Lista de requisitos
- 1 - Crie um endpoint para o cadastro de produtos
- 2 - Crie um endpoint para listar os produtos
- 3 - Crie um endpoint para atualizar um produto
- 4 - Crie um endpoint para deletar um produto
- 5 - Crie um endpoint para cadastrar vendas
- 6 - Crie um endpoint para listar as vendas
- 7 - Crie um endpoint para atualizar uma venda
- 8 - Escreva testes para cobrir 35% das camadas da sua aplicação
- 9 - Escreva testes para cobrir 40% das camadas da sua aplicação S - 10 - Crie um endpoint para deletar uma venda
- 11 - Atualize a quantidade de produtos
- 12 - Valide a quantidade de produtos
- 13 - Escreva testes para cobrir 50% das camadas da sua aplicação
- 14 - Escreva testes para cobrir 60% das camadas da sua aplicação
Nesse projeto, você será capaz de:
- Entender o funcionamento da camada de Model;
- Delegar responsabilidades específicas para essa camada;
- Conectar sua aplicação com diferentes bancos de dados;
- Estruturar uma aplicação em camadas;
- Delegar responsabilidades específicas para cada parte do seu app;
- Melhorar manutenibilidade e reusabilidade do seu código;
- Entender e aplicar os padrões REST;
- Escrever assinaturas para APIs intuitivas e facilmente entendíveis.
- Instale as dependências
npm install
Atenção
const connection = mysql.createPool({
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
});
Para os testes rodarem corretamente, na raiz do projeto renomeie o arquivo .env.example
para .env
com as variáveis de ambiente. Por exemplo, caso o seu usuário SQL seja nome
e a senha 1234
seu arquivo ficará desta forma:
MYSQL_HOST=localhost
MYSQL_USER=nome
MYSQL_PASSWORD=1234
PORT=3000
Nota: A variável PORT do arquivo .env
deve ser utilizada para a conexão com o servidor. É importante utilizar essa variável para os testes serem executados corretamente tanto na máquina local quanto no avaliador.
Com essas configurações, enquanto estiver na máquina local, o banco será executado normalmente via localhost (possibilitando os testes via npm test
).
Como o arquivo .env
não será enviado para o GitHub (não se preocupe com isso, pois já está configurado no .gitignore
), o avaliador utilizará as suas próprias variáveis de ambiente.
Na raiz do projeto existe o arquivo StoreManager.sql
que será usado para rodar os testes. Você pode importá-lo localmente para testar o comportamento da sua aplicação durante o desenvolvimento.
O banco terá três tabelas: products
, sales
e sales_products
.
A tabela products
tem o seguinte formato:
(O id será gerado automaticamente)
A tabela sales
tem o seguinte formato:
(O id e date são gerados automaticamente)
A tabela sales_products
, é a tabela que faz o relacionamento N:N
entre products
e sales
e tem o seguinte formato:
Usaremos o ESLint para fazer a análise estática do seu código.
Este projeto já vem com as dependências relacionadas ao linter configuradas no arquivos package.json
.
Para poder rodar os ESLint
em um projeto basta executar o comando npm install
dentro do projeto e depois npm run lint
. Se a análise do ESLint
encontrar problemas no seu código, tais problemas serão mostrados no seu terminal. Se não houver problema no seu código, nada será impresso no seu terminal.
Você pode também instalar o plugin do ESLint
no VSCode
, bastar ir em extensions e baixar o plugin ESLint
.
-
O endpoint deve ser acessível através do caminho (
/products
); -
Os produtos enviados devem ser salvos na tabela
products
do Banco de Dados; -
O endpoint deve receber a seguinte estrutura:
{
"name": "product_name",
"quantity": "product_quantity"
}
O que será validado
👉 Para o endpoint
POST /products
, o camponame
deve ser uma string com 5 ou mais caracteres e deve ser único.
-
Quando a requisição é feita sem o atributo
name
:{ "quantity": 100 }
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"name\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém o seguinte
body
:{ "name": "pro", "quantity": 100 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"name\" length must be at least 5 characters long" }
- sua API deve responder com status http
-
Quando a requisição é feita com o atributo
name
igual um já cadastrado:{ "name": "produto", "quantity": 100 }
- sua API deve responder com status http
409
e o seguintebody
:
{ "message": "Product already exists" }
- sua API deve responder com status http
👉 Para o endpoint
POST /products
, o campoquantity
deve ser um número inteiro maior que 0.
-
Quando a requisição é feita sem o atributo
quantity
:{ "name": "produto" }
- sua API deve responder com status http
400
e o seguintebody
:{ "message": "\"quantity\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém os seguintes
body
:{ "name": "produto", "quantity": "string" }
{ "name": "produto", "quantity": -1 }
{ "name": "produto", "quantity": 0 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
POST /products
, quando a requisição é feita corretamente, o produto deve ser cadastrado.
- Quando a requisição é feita e contém o seguinte
body
:{ "name": "produto", "quantity": 10 }
- sua API deve responder com status http
201
e o seguintebody
:
{ "id": 1, "name": "produto", "quantity": 10 }
- sua API deve responder com status http
-
O endpoint deve ser acessível através do caminho (
/products
) ou (/products/:id
); -
Através do caminho
/products
, todos os produtos devem ser retornados; -
Através do caminho
/products/:id
, apenas o produto com oid
presente na URL deve ser retornado;
O que será validado
👉 Para o endpoint
GET /products
, será validado que todos produtos estão sendo retornados.
- sua API deve responder com status http
200
e o seguintebody
:
[
{
"id": 1,
"name": "produto A",
"quantity": 10
},
{
"id": 2,
"name": "produto B",
"quantity": 20
}
]
👉 Para o endpoint
GET /products/:id
, será validado que é possível listar um determinado produto.
- sua API deve responder com status http
200
e o seguintebody
:{ "id": 1, "name": "produto A", "quantity": 10 }
👉 Para o endpoint
GET /products/:id
, será validado que não é possível listar um produto que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Product not found" }
-
O endpoint deve ser acessível através do caminho (
/products/:id
); -
O corpo da requisição deve seguir a mesma estrutura do método responsável por adicionar um produto;
-
Apenas o produto com o
id
presente na URL deve ser atualizado; -
O corpo da requisição deve receber a seguinte estrutura:
{
"name": "new_product_name",
"quantity": "new_product_quantity"
}
O que será validado
👉 Para o endpoint
PUT /products/:id
, o camponame
deve ser uma string com 5 ou mais caracteres e deve ser único.
- Quando a requisição é feita e contém o seguinte
body
:{ "name": "pro", "quantity": 15 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"name\" length must be at least 5 characters long" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /products/:id
, o campoquantity
deve ser um número inteiro maior que 0.
- Quando a requisição é feita e contém os seguintes
body
:{ "name": "produto", "quantity": "string" }
{ "name": "produto", "quantity": -1 }
{ "name": "produto", "quantity": 0 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /products/:id
, quando a requisição é feita corretamente, o produto deve ser alterado.
- Quando a requisição é feita e contém o seguinte
body
:{ "name": "produto", "quantity": 15 }
- sua API deve responder com status http
200
e o seguintebody
:
{ "id": 1, "name": "produto", "quantity": 15 }
- sua API deve responder com status http
👉 Para o endpoint
PUT /products/:id
, será validado que não é possível alterar um produto que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Product not found" }
-
O endpoint deve ser acessível através do caminho (
/products/:id
); -
Apenas o produto com o
id
presente na URL deve ser deletado;
O que será validado
👉 Para o endpoint
DELETE /products/:id
, será validado que é possível deletar um produto com sucesso.
- sua API deve responder com status http
200
e o seguintebody
:
{
"id": 1,
"name": "produto A",
"quantity": 10
}
👉 Para o endpoint
DELETE /products/:id
, será validado que não é possível deletar um produto que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Product not found" }
-
O endpoint deve ser acessível através do caminho (
/sales
); -
As vendas enviadas devem ser salvas na tabela
sales
esales_products
do Banco de dados; -
Deve ser possível cadastrar a venda de vários produtos através da uma mesma requisição;
-
O endpoint deve receber a seguinte estrutura:
[
{
"product_id": "product_id",
"quantity": "product_quantity",
}
]
O que será validado
👉 Para o endpoint
POST /sales
, o campoproduct_id
deve ser um id de um produto anteriormente cadastrado.
- Quando a requisição é feita sem o atributo
product_id
:[ { "quantity": 1 } ]
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"product_id\" is required" }
- sua API deve responder com status http
👉 Para o endpoint
POST /sales
, o campoquantity
deve ser um número inteiro maior que 0.
-
Quando a requisição é feita sem o atributo
quantity
:[ { "product_id": 1 } ]
- sua API deve responder com status http
400
e o seguintebody
:{ "message": "\"quantity\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém os seguintes
body
:[ { "product_id": 1, "quantity": -1 } ]
[ { "product_id": 1, "quantity": 0 } ]
[ { "product_id": 1, "quantity": "string" } ]
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
POST /sales
, quando a requisição é feita corretamente, o produto deve ser cadastrado.
- Quando a requisição é feita e contém o seguinte
body
:[ { "product_id": 1, "quantity": 3 } ]
- sua API deve responder com status http
201
e o seguintebody
:
{ "id": 1, "itemsSold": [ { "product_id": 1, "quantity": 3 } ] }
- sua API deve responder com status http
👉 Para o endpoint
POST /sales
, quando a requisição é feita corretamente, a venda deve ser cadastrada.
- Quando a requisição é feita e contém o seguinte
body
:[ { "product_id": 1, "quantity": 2 }, { "product_id": 2, "quantity": 5 } ]
- sua API deve responder com status http
201
e o seguintebody
:
{ "id": 1, "itemsSold": [ { "product_id": 1, "quantity": 2 }, { "product_id": 2, "quantity": 5 } ] }
- sua API deve responder com status http
-
O endpoint deve ser acessível através do caminho (
/sales
) ou (/sales/:id
); -
Através do caminho
/sales
, todas as vendas devem ser retornadas; -
Através do caminho
/sales/:id
, apenas a venda com oid
presente na URL deve ser retornada;
O que será validado
👉 Para o endpoint
GET /sales
, será validado que todas vendas estão sendo retornados.
- sua API deve responder com status http
200
e o seguintebody
:
[
{
"saleId": 1,
"date": "2021-09-09T04:54:29.000Z",
"product_id": 1,
"quantity": 2
},
{
"saleId": 1,
"date": "2021-09-09T04:54:54.000Z",
"product_id": 2,
"quantity": 2
}
]
👉 Para o endpoint
GET /sales/:id
, será validado que é possível listar uma determinada venda.
- sua API deve responder com status http
200
e o seguintebody
:[ { "date": "2021-09-09T04:54:29.000Z", "product_id": 1, "quantity": 2 }, { "date": "2021-09-09T04:54:54.000Z", "product_id": 2, "quantity": 2 } ]
👉 Para o endpoint
GET /sales/:id
, será validado que não é possível listar uma venda que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Sale not found" }
-
O endpoint deve ser acessível através do caminho (
/sales/:id
); -
quantity
deve ser um número inteiro maior que 0; -
Apenas a venda com o
id
presente na URL deve ser atualizada; -
O corpo da requisição deve receber a seguinte estrutura:
[
{
"product_id": "id_change",
"quantity": "new_quantity"
}
]
O que será validado
👉 Para o endpoint
PUT /sales/:id
, o campoproduct_id
deve ser um id de um produto anteriormente cadastrado.
- Quando a requisição é feita sem o atributo
product_id
:[ { "quantity": 10 } ]
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"product_id\" is required" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /sales/:id
, o campoquantity
deve ser um número inteiro maior que 0.
-
Quando a requisição é feita sem o atributo
quantity
:[ { "product_id": 1 } ]
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"quantity\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém os seguintes
body
:[ { "product_id": 1, "quantity": -1 } ]
[ { "product_id": 1, "quantity": 0 } ]
[ { "product_id": 1, "quantity": "string" } ]
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /sales/:id
, quando a requisição é feita corretamente, a venda deve ser alterada.
- Quando a requisição é feita e contém o seguinte
body
:[ { "product_id": 1, "quantity": 6 } ]
- sua API deve responder com status http
200
e o seguintebody
:
{ "saleId": 1, "itemUpdated": [ { "product_id": 1, "quantity": 6 } ] }
- sua API deve responder com status http
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui; -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que será validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 35%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que será validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 40%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
-
O endpoint deve ser acessível através do caminho (
/sales/:id
); -
Apenas a venda com o
id
presente na URL deve ser deletado;
O que será validado
👉 Para o endpoint
DELETE /sales/:id
, será validado que é possível deletar uma venda com sucesso.
- sua API deve responder com status http
200
e o seguintebody
:
[
{
"date": "2021-09-09T04:54:29.000Z",
"product_id": 1,
"quantity": 2
},
{
"date": "2021-09-09T04:54:54.000Z",
"product_id": 2,
"quantity": 2
}
]
👉 Para o endpoint
DELETE /sales/:id
, será validado que não é possível deletar uma venda que não existe.
- sua API deve responder com status http
404
e o seguintebody
:
{ "message": "Sale not found" }
-
Ao realizar uma venda, atualizá-la ou deletá-la, você deve também atualizar a quantidade do produto em questão presente na tabela responsável pelos produtos;
- Exemplo 1: suponha que haja um produto chamado Bola de Futebol e a sua propriedade
quantity
tenha o valor 10. Caso seja feita uma venda com 8 unidades desse produto, a quantidade do produto deve ser atualizada para 2 , pois 10 - 8 = 2; - Exemplo 2: Suponha que esta venda tenha sido deletada, logo estas 8 unidades devem voltar ao
quantity
e seu valor voltará a 10, pois 2 + 8 = 10;
- Exemplo 1: suponha que haja um produto chamado Bola de Futebol e a sua propriedade
O que será validado
👉 Será validado que ao fazer uma determinada venda, a quantidade do produto deverá ser atualizada também na tabela responsável pelos produtos.
👉 Será validado que ao deletar uma determinada venda, a quantidade do produto deverá ser atualizada também na tabela responsável pelos produtos;.
-
Um produto nunca deve ter a quantidade em estoque menor que 0;
-
Quando uma venda for realizada, garanta que a quantidade sendo vendida está disponível no estoque
O que será validado
👉 Para o endpoint
POST /sales
, será validado que a quantidade de produtos em estoque nunca seja menor que 0 (zero).
- Quando a requisição é feita com uma quantidade superior a quantidade em estoque:
[ { "product_id": 1, "quantity": 100 } ]
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "Such amount is not permitted to sell" }
- sua API deve responder com status http
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui; -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que será validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 50%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui; -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que será validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 60%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.