Initial commit

This commit is contained in:
Jean Paulo Silva Vasconcelos 2021-10-15 17:58:04 -03:00
commit 3c97a29342
51 changed files with 26677 additions and 0 deletions

17
.editorconfig Normal file
View file

@ -0,0 +1,17 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,md}]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[*.js]
indent_style = space
indent_size = 2

2
.eslintignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
test

6
.eslintrc.json Normal file
View file

@ -0,0 +1,6 @@
{
"extends": "trybe-backend",
"rules": {
"sonarjs/no-duplicate-string": ["error", 5]
}
}

11
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,11 @@
# This is a comment.
# Each line is a file pattern followed by one or more owners.
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# @global-owner1 and @global-owner2 will be requested for
# review when someone opens a pull request.
# * @global-owner1 @global-owner2
* @tryber/pr-locker

105
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,105 @@
on:
workflow_dispatch:
inputs:
dispatch_token:
description: 'Token that authorize the dispatch'
required: true
head_sha:
description: 'Head commit SHA that dispatched the workflow'
required: true
pr_author_username:
description: 'Pull Request author username'
required: true
pr_number:
description: 'Pull Request number that dispatched the workflow'
required: true
jobs:
evaluator:
runs-on: self-hosted
name: Evaluator
services:
mongodb:
image: mongo
ports:
- "27017:27017"
options: -v ${{ github.workspace }}:/github/workspace
steps:
- name: Fetch project repository
uses: actions/checkout@v2
- name: Fetch Blocked Files Checkout action
uses: actions/checkout@v2
with:
repository: betrybe/blocked-files-checkout-action
ref: v2
token: ${{ secrets.GIT_HUB_PAT }}
path: .github/actions/blocked-files-checkout
- name: Fetch ESLint evaluator
uses: actions/checkout@v2
with:
repository: betrybe/eslint-linter-action
ref: v3
token: ${{ secrets.GIT_HUB_PAT }}
path: .github/actions/eslint-evaluator
- name: Fetch Stylelint evaluator
uses: actions/checkout@v2
with:
repository: betrybe/stylelint-linter-action
ref: v2.1
token: ${{ secrets.GIT_HUB_PAT }}
path: .github/actions/stylelint-evaluator
- name: Fetch Jest evaluator
uses: actions/checkout@v2
with:
repository: betrybe/jest-evaluator-action
ref: v9
token: ${{ secrets.GIT_HUB_PAT }}
path: .github/actions/jest-evaluator
- name: Fetch Store evaluation
uses: actions/checkout@v2
with:
repository: betrybe/store-evaluation-action
ref: v2
token: ${{ secrets.GIT_HUB_PAT }}
path: .github/actions/store-evaluation
- name: Setup NodeJS
uses: actions/setup-node@v1.4.4
with:
node-version: '12'
- name: Restore protected files
uses: ./.github/actions/blocked-files-checkout
with:
restore_branch: 'master'
- name: Run ESLint evaluator
uses: ./.github/actions/eslint-evaluator
with:
token: ${{ secrets.GITHUB_TOKEN }}
pr_number: ${{ github.event.inputs.pr_number }}
- name: Run StyleLint evaluator
uses: ./.github/actions/stylelint-evaluator
with:
token: ${{ secrets.GITHUB_TOKEN }}
pr_number: ${{ github.event.inputs.pr_number }}
- name: Run Jest evaluation
id: evaluator
uses: ./.github/actions/jest-evaluator
with:
pr_author_username: ${{ github.event.inputs.pr_author_username }}
npm-start: true
- name: Run Store evaluation
uses: ./.github/actions/store-evaluation
with:
evaluation-data: ${{ steps.evaluator.outputs.result }}
environment: staging
pr-number: ${{ github.event.inputs.pr_number }}

291
.gitignore vendored Normal file
View file

@ -0,0 +1,291 @@
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,linux,visualstudiocode,intellij
# Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,windows,linux,visualstudiocode,intellij
### Intellij ###
# 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
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/**/sonarlint/
# SonarQube Plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/node,macos,windows,linux,visualstudiocode,intellij

56
.trybe/requirements.json Normal file
View file

@ -0,0 +1,56 @@
{
"requirements": [
{
"description": "1 - Crie um endpoint para o cadastro de produtos",
"bonus": false
},
{
"description": "2 - Crie um endpoint para listar os produtos",
"bonus": false
},
{
"description": "3 - Crie um endpoint para atualizar um produto",
"bonus": false
},
{
"description": "4 - Crie um endpoint para deletar um produto",
"bonus": false
},
{
"description": "5 - Crie um endpoint para cadastrar vendas",
"bonus": false
},
{
"description": "6 - Crie um endpoint para listar as vendas",
"bonus": false
},
{
"description": "7 - Crie um endpoint para atualizar uma venda",
"bonus": false
},
{
"description": "8 - Crie um endpoint para deletar uma venda",
"bonus": false
},
{
"description": "9 - Atualize a quantidade de produtos",
"bonus": false
},
{
"description": "10 - Valide a quantidade de produtos",
"bonus": false
},
{
"description": "11 - Escreva testes para seus models",
"bonus": true
},
{
"description": "12 - Escreva testes para seus services",
"bonus": true
},
{
"description": "13 - Escreva testes para seus controllers",
"bonus": true
}
]
}

759
README.md Normal file
View file

@ -0,0 +1,759 @@
### Termos e acordos
Ao iniciar este projeto, você concorda com as diretrizes do Código de Ética e Conduta e do Manual da Pessoa Estudante da Trybe
# Boas vindas ao repositório do projeto Store Manager!
Você já usa o GitHub diariamente para desenvolver os exercícios, certo? Agora, para desenvolver os projetos, você deverá seguir as instruções a seguir. Fique atento a cada passo, e se tiver qualquer dúvida, nos envie por Slack! #vqv 🚀
Aqui você vai encontrar os detalhes de como estruturar o desenvolvimento do seu projeto a partir deste repositório, utilizando uma branch específica e um Pull Request para colocar seus códigos.
---
# Sumário
- [Habilidades](#habilidades)
- [Entregáveis](#entregáveis)
- [O que deverá ser desenvolvido](#o-que-deverá-ser-desenvolvido)
- [Desenvolvimento](#desenvolvimento)
- [Data de entrega](#data-de-entrega)
- [Instruções para entregar seu projeto](#instruções-para-entregar-seu-projeto)
- [Antes de começar a desenvolver](#antes-de-começar-a-desenvolver)
- [Durante o desenvolvimento](#durante-o-desenvolvimento)
- [Como desenvolver](#como-desenvolver)
- [Padrões e conexões](#padrões-e-conexões)
- [Conexão com o Banco](#conexão-com-o-banco)
- [Tabelas](#tabelas)
- [Requisitos do projeto](#requisitos-do-projeto)
- [Linter](#linter)
- [Lista de requisitos](#lista-de-requisitos)
`Obrigatórios`
- [1 - Crie um endpoint para o cadastro de produtos](#1---crie-um-endpoint-para-o-cadastro-de-produtos)
- [2 - Crie um endpoint para listar os produtos](#2---crie-um-endpoint-para-listar-os-produtos)
- [3 - Crie um endpoint para atualizar um produto](#3---crie-um-endpoint-para-atualizar-um-produto)
- [4 - Crie um endpoint para deletar um produto](#4---crie-um-endpoint-para-deletar-um-produto)
- [5 - Crie um endpoint para cadastrar vendas](#5---crie-um-endpoint-para-cadastrar-vendas)
- [6 - Crie um endpoint para listar as vendas](#6---crie-um-endpoint-para-listar-as-vendas)
- [7 - Crie um endpoint para atualizar uma venda](#7---crie-um-endpoint-para-atualizar-uma-venda)
- [8 - Crie um endpoint para deletar uma venda](#8---crie-um-endpoint-para-deletar-uma-venda)
- [9 - Atualize a quantidade de produtos](#9---atualize-a-quantidade-de-produtos)
- [10 - Valide a quantidade de produtos](#10---valide-a-quantidade-de-produtos)
`Bônus`
- [11 - Escreva testes para seus models](#11---escreva-testes-para-seus-models)
- [12 - Escreva testes para seus services](#12---escreva-testes-para-seus-services)
- [13 - Escreva testes para seus controllers](#13---escreva-testes-para-seus-controllers)
- [Depois de terminar o desenvolvimento](#depois-de-terminar-o-desenvolvimento)
- [Revisando um pull request](#revisando-um-pull-request)
- [Avisos Finais](#avisos-finais)
---
# Habilidades
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.
# Entregáveis
Para entregar o seu projeto você deverá criar um Pull Request neste repositório.
Lembre-se que você pode consultar nosso conteúdo sobre
[Git & GitHub](https://course.betrybe.com/intro/git/) sempre que precisar!
---
## O que deverá ser desenvolvido
Você vai desenvolver sua primeira API utilizando a arquitetura MSC!
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.
---
## Desenvolvimento
Você vai desenvolver todas as camadas da API (Models, Services caso necessário, e Controllers).
Através dessa aplicação, será possível realizar as operações básicas que se pode fazer em um determinado banco de dados: Criação, Leitura, Atualização e Exclusão (ou `CRUD`, para as pessoas mais mais íntimas 😜).
Você deve utilizar o banco MongoDB para a gestão de dados. Além disso, a API deve ser RESTful.
⚠️ **Dicas Importantes** ⚠️:
- Deve ser possível que a pessoa usuária, independente de cadastramento ou login, possa adicionar, ler, deletar e atualizar produtos no seu estoque. O usuário deve poder também enviar vendas para o sistema. Essas vendas devem validar se o produto em questão existe. Deve, também, ser possível ler, deletar e atualizar vendas.
- Para **todos os endpoints** garanta que:
- Caso o recurso não seja encontrado, sua API retorne o status HTTP adequado com o body `{ message: '<recurso> não encontrado' }`.
- Em caso de erro, sua API retorne o status HTTP adequado com o body `{ err: { message: <mensagem de erro>, code: <código do erro> } }`.
- O código do erro deve ser determinado por você e deve seguir o mesmo padrão para toda a aplicação. Por exemplo: `'not_found'`, `'invalid_data'` e afins.
- Em caso de dados inválidos, sua API retorne o status HTTP adequado, com o body `{ err: { message: 'Dados inválidos', code: <código do erro> } }`.
- Todos os retornos de erro devem seguir o mesmo formato. Para erros que requerem dados adicionais (por exemplo, para informar quais campos estão incorretos) utilize a propriedade `data` dentro do objeto `err`.
- Para gerar os objetos de erro personalizados, você pode utilizar uma biblioteca de erros, como o [`boom`](https://www.npmjs.com/package/@hapi/boom).
- Você pode utilizar middlewares e objetos de erro personalizados para que não tenha que repetir a lógica de tratamento de erro em vários lugares. Não se esqueça também do [express-rescue](https://www.npmjs.com/package/express-rescue), ele pode facilitar muito o trabalho de tratar erros.
- Quando estiver na dúvida sobre qual status HTTP utilizar, você pode consultar sites como o [httpstatuses.com](https://httpstatuses.com/), [restapitutorial.com](https://www.restapitutorial.com/httpstatuscodes.html) ou a [documentação sobre o assunto no MDN](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status). Com o tempo, você vai lembrar com facilidade o significado dos códigos mais comuns.
- Para realizar a validação dos dados, você pode utilizar middlewares como [`Joi`](https://www.npmjs.com/package/@hapi/joi) ou o [`Expresso Validator`](https://www.npmjs.com/package/@expresso/validator). Caso prefira, você também pode realizar a validação de forma manual.
---
## Data de Entrega
- Serão `X` dias de projeto.
- Data de entrega para avaliação final do projeto: `DD/MM/YYYY - 14:00h`.
---
# Instruções para entregar seu projeto
## Antes de começar a desenvolver
1. Clone o repositório
- `git clone https://github.com/tryber/sd-0x-store-manager.git`.
- Entre na pasta do repositório que você acabou de clonar:
- `cd sd-0x-store-manager`
2. Instale as dependências [**Caso existam**]
- `npm install`
Atenção :warning: Não rode o comando npm audit fix! Ele atualiza várias dependências do projeto, e essa atualização gera conflitos com o avaliador.
3. Crie uma branch a partir da branch `master`
- Verifique que você está na branch `master`
- Exemplo: `git branch`
- Se não estiver, mude para a branch `master`
- Exemplo: `git checkout master`
- Agora crie uma branch à qual você vai submeter os `commits` do seu projeto
- Você deve criar uma branch no seguinte formato: `nome-de-usuario-nome-do-projeto`
- Exemplo: `git checkout -b joaozinho-sd-0x-store-manager`
4. Adicione as mudanças ao _stage_ do Git e faça um `commit`
- Verifique que as mudanças ainda não estão no _stage_
- Exemplo: `git status` (deve aparecer listada a pasta _joaozinho_ em vermelho)
- Adicione o novo arquivo ao _stage_ do Git
- Exemplo:
- `git add .` (adicionando todas as mudanças - _que estavam em vermelho_ - ao stage do Git)
- `git status` (deve aparecer listado o arquivo _joaozinho/README.md_ em verde)
- Faça o `commit` inicial
- Exemplo:
- `git commit -m 'iniciando o projeto x'` (fazendo o primeiro commit)
- `git status` (deve aparecer uma mensagem tipo _nothing to commit_ )
5. Adicione a sua branch com o novo `commit` ao repositório remoto
- Usando o exemplo anterior: `git push -u origin joaozinho-sd-0x-store-manager`
6. Crie um novo `Pull Request` _(PR)_
- Vá até a página de _Pull Requests_ do [repositório no GitHub](https://github.com/tryber/sd-0x-store-manager/pulls)
- Clique no botão verde _"New pull request"_
- Clique na caixa de seleção _"Compare"_ e escolha a sua branch **com atenção**
- Clique no botão verde _"Create pull request"_
- Adicione uma descrição para o _Pull Request_ e clique no botão verde _"Create pull request"_
- **Não se preocupe em preencher mais nada por enquanto!**
- Volte até a [página de _Pull Requests_ do repositório](https://github.com/tryber/sd-0x-store-manager/pulls) e confira que o seu _Pull Request_ está criado
---
## Durante o desenvolvimento
* ⚠ **PULL REQUESTS COM ISSUES NO LINTER NÃO SERÃO AVALIADAS, ATENTE-SE PARA RESOLVÊ-LAS ANTES DE FINALIZAR O DESENVOLVIMENTO!**
* Faça `commits` das alterações que você fizer no código regularmente
* Lembre-se de sempre após um (ou alguns) `commits` atualizar o repositório remoto
* Os comandos que você utilizará com mais frequência são:
1. `git status` _(para verificar o que está em vermelho - fora do stage - e o que está em verde - no stage)_
2. `git add` _(para adicionar arquivos ao stage do Git)_
3. `git commit` _(para criar um commit com os arquivos que estão no stage do Git)_
5. `git push -u nome-da-branch` _(para enviar o commit para o repositório remoto na primeira vez que fizer o `push` de uma nova branch)_
4. `git push` _(para enviar o commit para o repositório remoto após o passo anterior)_
---
# Como desenvolver
## Padrões e conexões
## ⚠️ Leia-os atentamente e siga à risca o que for pedido. ⚠️
### Todos os seus endpoints devem estar no padrão REST
- Use os verbos HTTP adequados para cada operação.
- Agrupe e padronize suas URL em cada recurso.
- Garanta que seus endpoints sempre retornem uma resposta, havendo sucesso nas operações ou não.
- Retorne os códigos de status corretos (recurso criado, erro de validação, autorização, etc).
### Cada camada da sua API deve estar em sua respectiva pasta
- Models devem estar na pasta `models`, **na raiz do projeto**
- Services devem estar na pasta `services`, **na raiz do projeto**
- Controllers devem estar na pasta `controllers`, **na raiz do projeto**
### Arquivo index.js
Há um arquivo `index.js` no repositório. Não remova, nele, o seguinte trecho de código:
```javascript
app.get('/', (request, response) => {
response.send();
});
```
Isso está configurado para o avaliador funcionar.
### Conexão com o Banco:
A conexão do banco local deverá conter os seguintes parâmetros:
```javascript
const MONGO_DB_URL = 'mongodb://localhost:27017/StoreManager';
const DB_NAME = 'StoreManager';
```
Para o avaliador funcionar altere a conexão do banco para:
```javascript
const MONGO_DB_URL = 'mongodb://mongodb:27017/StoreManager';
const DB_NAME = 'StoreManager';
```
### Tabelas
O banco terá duas tabelas: produtos e vendas
A tabela de produtos deverá ter o seguinte nome: `products`
Os campos da tabela `products` terão esse formato:
```json
{ "name": "Produto Silva", "quantity": 10 }
```
A resposta do insert que deve retornar após a criação é parecida essa:
```json
{ "_id": ObjectId("5f43cbf4c45ff5104986e81d"), "name": "Produto Silva", "quantity": 10 }
```
(O \_id será gerado automaticamente)
A tabela de vendas deverá ter o seguinte nome: `sales`
Os campos da tabela `sales` terão esse formato:
```json
{ "itensSold": [{ "productId": "5f43cbf4c45ff5104986e81d", "quantity": 2 }] }
```
A resposta do insert que deve retornar após a criação é parecida essa:
```json
{
"_id": ObjectId("5f43cc53c45ff5104986e81e"),
"itensSold": [{ "productId": "5f43cbf4c45ff5104986e81d", "quantity": 2 }]
}
```
(O \_id será gerado automaticamente)
# Requisitos do projeto
## Linter
Usaremos o [ESLint](https://eslint.org/) 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`](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint).
---
## Testes
Usaremos o [Jest]() e o [Frisby] para fazer os testes de api.
Este projeto já vem configurado e com suas dependências
Para poder executar os testes basta executar comando `npm tests` e o resultado será igual o abaixo:
![Testes](./public/testejestfrisby.png)
## Dica: desativando testes
Especialmente no início, quando a maioria dos testes está falhando, a saída após executar os testes é bastante poluída. Você pode desabilitar temporariamente um teste utilizando a função `skip` junto à função `it`. Como o nome indica, esta função "pula" um teste:
```js
it.skip('it includes the text `Movie Cards Library` inside a h1 tag', () => {
wrapper = shallow(<Header />);
expect(wrapper.find('header h1').text()).toBe('Movie Cards Library');
});
```
Na saída da execução dos testes, você verá um <img src="./public/orange-circle.png" width="15px"> indicando que o teste está sendo pulado:
![image](./public/skipped-test.png)
Uma estratégia é pular todos os testes no início e ir implementando um teste de cada vez, removendo dele a função `skip`.
⚠️ Lembre-se de não entregar o projeto com nenhum teste ignorado. Testes ignorados serão tratados como testes falhando.
## Lista de requisitos
### 1 - Crie um endpoint para o cadastro de produtos
- O endpoint deve ser acessível através do caminho (`/products`);
- Os produtos enviados devem ser salvos em uma **collection** do MongoDB;
- O endpoint deve receber a seguinte estrutura:
```json
{
"name": "product_name",
"quantity": "product_quantity"
}
```
O retorno da API de um produto cadastrado com sucesso deverá ser:
```json
{
"_id": "5f43a7ca92d58904914656b6",
"name": "Produto do Batista",
"quantity": 100
}
```
#### Requisição de Cadastro de Produtos:
O projeto deve rodar na porta `http://localhost:3000`
![Criar produtos](./public/criarProdutos.png)
#### Observações Técnicas:
- `name` deve ser uma _string_ com mais de 5 caracteres e deve ser único;
- `quantity` deve ser um número inteiro maior que 0;
- Cada produto deve ter um id que seja único e gerado no momento em que o recurso for criado. Você pode utilizar o ID gerado pelo MongoDB
- A resposta do endpoint em caso de sucesso deve ser o produto criado.
**O que será verificado:**
- Será validado que não é possível criar um produto com o nome menor que 5 caracteres
- Se o produto tiver o nome menor que cinco caracteres o resultado retornado deverá ser conforme exibido abaixo, com um status http `422`:
![Nome menor que 5](./public/nomeMenorQue5.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que não é possível criar um produto com o mesmo nome de outro já existente
- Se o produto tiver o mesmo nome o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Mesmo nome](./public/mesmonome.png)
- Será validado que não é possível criar um produto com quantidade menor que zero
- Se o produto tiver uma quantidade menor que zero o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Menor que 0](./public/menorque0.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que não é possível criar um produto com quantidade igual a zero
- Se o produto tiver uma quantidade igual a zero o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Igual a zero](./public/igualazero.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que não é possível criar um produto com uma string no campo quantidade
- Se o produto tiver uma quantidade com o valor em string o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Quantidade como string](./public/quantidadecomostring.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que é possível criar um produto com sucesso
- Se o produto for cadastrado com sucesso o resultado retornado deverá ser conforme exibido abaixo, com status http `201`:
![Criar produtos](./public/criarProdutos.png)
### 2 - Crie um endpoint para listar os produtos
- 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 o `id` presente na URL deve ser retornado;
**O que será verificado:**
- Será validado que todos produtos estão sendo retornados
- Se a lista retornar com sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Lista de produtos](./public/listadeprodutos.png)
- Será validado que é possível listar um determinado produto
- Se a lista retornar com sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Listar um produto](./public/produtoespecifico.png)
- Será validado que não é possível listar um produto que não existe
- Se a lista retornar com falha, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Produto não existe](./public/produtonaoexiste.png)
### 3 - Crie um endpoint para atualizar um produto
- 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 que será verificado:**
- Será validado que não é possível atualizar um produto com o nome menor que 5 caracteres
- Se o produto tiver o nome menor que cinco caracteres, o resultado retornado deverá ser conforme exibido abaixo, com status `422`:
![Atualizar com nome menor que cinco](./public/atualizarcomnomemenorque5.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que não é possível atualizar um produto com quantidade menor que zero
- Se o produto tiver o quantidade menor que zero, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Atualizar menor que zero](./public/atualizarmenorque0.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que não é possível atualizar um produto com quantidade igual a zero
- Se o produto tiver o quantidade igual a zero, o resultado mostrado deverá ser conforme exibido abaixo, com status http `422`:
![Atualizar igual a zero](./public/atualizarigual0.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que não é possível atualizar um produto com uma string no campo quantidade
- Se o produto tiver o quantidade como string, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Atualizar com string](./public/atualizarcomostring.png)
(As contrabarras `\` estão escapando as aspas de dentro da string)
- Será validado que é possível atualizar um produto com sucesso]**
- Se o produto atualizado com sucesso, o resultado mostrretornadoado deverá ser conforme exibido abaixo, com status http `200`:
![Atualizado com sucesso](./public/atualizarcomsucesso.png)
### 4 - Crie um endpoint para deletar um produto
- 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á verificado:**
- Será validado que é possível deletar um produto com sucesso
- Se o produto deletado com sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Deletar um produto](./public/deletarumproduto.png)
- Será validado que não é possível deletar um produto que não existe
- Se o produto não for deletado com sucesso, o resultado retornado deverá ser esse e com status http `422`:
![Deletar um produto que não existe](./public/deletarumprodutoquenaoexiste.png)
### 5 - Crie um endpoint para cadastrar vendas
- O endpoint deve ser acessível através do caminho (`/sales`);
- As vendas enviadas devem ser salvas em uma `collection` do MongoDB;
- 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:
```json
[
{
"productId": "product_id",
"quantity": "product_quantity",
},
...
]
```
O retorno de uma venda cadastrada com sucesso deverá ser:
```json
{
"_id": "5f43ba333200020b101fe4a0",
"itensSold": [
{
"productId": "5f43ba273200020b101fe49f",
"quantity": 2
}
]
}
```
#### Observações Técnicas:
- O `productId` devem ser igual ao `id` de um produto anteriormente cadastrado;
- `quantity` deve ser um número inteiro maior que 0;
- Cada venda deve ter um id que seja único e gerado no momento em que o recurso for criado;
- A resposta do endpoint em caso de sucesso deve ser a(s) venda(s) criada(s).
**O que será verificado:**
- Será validado que não é possível cadastrar vendas com quantidade menor que zero
- Se a venda tiver uma quantidade menor que zero, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Vendas menor que zero](./public/comprasmenorquezero.png)
- Será validado que não é possível cadastrar vendas com quantidade igual a zero
- Se a venda tiver uma quantidade igual a zero, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Vendas igual a zero](./public/comprasigualazero.png)
- Será validado que não é possível cadastrar vendas com uma string no campo quantidade
- Se a venda tiver uma quantidade com valor, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Vendas com string](./public/comprascomstring.png)
- Será validado que é possível criar uma venda com sucesso
- Se a venda foi feita com sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Cadastro de venda com sucesso](./public/cadastrodevendacomsucesso.png)
- Será validado que é possível criar várias vendas com sucesso
- Se as vendas foi feita com sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Cadastrar varias compras](./public/variascompras.png)
### 6 - Crie um endpoint para listar as vendas
- 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 o `id` presente na URL deve ser retornada;
**O que será verificado:**
- Será validado que todas as vendas estão sendo retornadas
- Se todas vendas estão sendo listadas, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Listar todas as vendas](./public/todasvendas.png)
- Será validado que é possível listar uma determinada venda
- Se a venda esta sendo listada, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Listar uma venda](./public/listaumavenda.png)
- Será validado que não é possível listar uma venda inexistente
- Se a venda não esta sendo listada, o resultado retornado deverá ser conforme exibido abaixo, com status http `404`:
![Listar uma venda que não existe](./public/vendanaoexiste.png)
### 7 - Crie um endpoint para atualizar uma venda
- O endpoint deve ser acessível através do caminho (`/sales/:id`);
- O corpo da requisição deve receber a seguinte estrutura:
```json
[
{
"productId": "5f3ff849d94d4a17da707008",
"quantity": 3
}
]
```
- `quantity` deve ser um número inteiro maior que 0;
- Apenas a venda com o `id` presente na URL deve ser atualizada;
**O que será verificado:**
- Será validado que não é possível atualizar vendas com quantidade menor que zero
- Se a venda tiver uma quantidade menor que zero, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Atualizar venda menor que zero](./public/atualizarvendamenorquezero.png)
- Será validado que não é possível atualizar vendas com quantidade igual a zero
- Se a venda tiver uma quantidade igual a zero, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Atualizar venda igual zero](./public/atualizarvendaigualzero.png)
- Será validado que não é possível atualizar vendas com uma string no campo quantidade
- Se a venda tiver uma quantidade do tipo string, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Atualizar venda com string](./public/atualizarvendacomstring.png)
- Será validado que é possível atualizar uma vendas com sucesso
- Se a venda for atualizada com sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200`:
![Atualizar uma venda com sucesso](./public/atualizarvendacomsucesso.png)
### 8 - Crie um endpoint para deletar uma venda
- 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á verificado:**
- Será validado que é possível deletar uma venda com sucesso
- Se a venda foi deletada sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `200` e será verificado depois que a venda não existe, com um GET nesse `id`, e este deverá retornar status http `404`, como é validado no requisito 6:
![Deletar uma venda com sucesso](./public/deletarumavendacomsucesso.png)
- Será validado que não é possível deletar uma venda que não existe
- Se a venda não foi deletada sucesso, o resultado retornado deverá ser conforme exibido abaixo, com status http `422`:
![Deletar uma venda que não existe](./public/deletarumavendaquenaoexiste.png)
### 9 - Atualize a quantidade de produtos
- Ao realizar uma venda, atualizá-la ou deletá-la, você deve também atualizar a quantidade do produto em questão presente na `collection` responsável pelos produtos;
- Por exemplo: 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;
**O que será verificado:**
- Será validado que é possível a quantidade do produto atualize ao fazer uma compra
- Ao fazer uma determinada venda, a quantidade do produto deverá ser atualizada.
- Será validado que é possível a quantidade do produto atualize ao deletar uma compra
- Ao fazer deletar uma determinada venda, a quantidade do produto deverá ser atualizada para a quantidade que tinha antes de ter feito essa venda.
### 10 - Valide a quantidade de 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á verificado:**
- Será validado que o estoque do produto nunca fique com a quantidade menor que zero
- Um produto não poderá ficar com a quantidade menor que zero, o resultado retornado deverá ser conforme exibido abaixo, com status http `404`:
![Compra maior que a quantidade](./public/compramaiorqueaquantidade.png)
## Bônus
## 11 - Escreva testes para seus models
- Utilize o mocha, chai e sinon para escrever seus testes
- Coloque todos os testes de models no arquivo `test/unit/models.js`
- Será validado que cobertura total das linhas dos arquivos na pasta `models` é maior ou igual a 80%
## 12 - Escreva testes para seus services
- Utilize o mocha, chai e sinon para escrever seus testes
- Coloque todos os testes de services no arquivo `test/unit/services.js`
- Será validado que cobertura total das linhas dos arquivos na pasta `services` é maior ou igual a 80%
## 13 - Escreva testes para seus controllers
- Utilize o mocha, chai e sinon para escrever seus testes
- Coloque todos os testes de controllers no arquivo `test/unit/controllers.js`
- Será validado que cobertura total das linhas dos arquivos na pasta `controllers` é maior ou igual a 80%
---
## Depois de terminar o desenvolvimento
Para **"entregar"** seu projeto, siga os passos a seguir:
* Vá até a página **DO SEU** _Pull Request_, adicione a label de _"code-review"_ e marque seus colegas
* No menu à direita, clique no _link_ **"Labels"** e escolha a _label_ **code-review**
* No menu à direita, clique no _link_ **"Assignees"** e escolha **o seu usuário**
* No menu à direita, clique no _link_ **"Reviewers"** e digite `students`, selecione o time `tryber/students-sd-00`
Se ainda houver alguma dúvida sobre como entregar seu projeto, [aqui tem um video explicativo](https://vimeo.com/362189205).
⚠ Lembre-se que garantir que todas as _issues_ comentadas pelo **Lint** estão resolvidas! ⚠
---
## Revisando um pull request
À medida que você e as outras pessoas que estudam na Trybe forem entregando os projetos, vocês receberão um alerta via Slack para também fazer a revisão dos Pull Requests dos seus colegas. Fiquem atentos às mensagens do "Pull Reminders" no Slack!
Use o material que você já viu sobre [Code Review](https://course.betrybe.com/real-life-engineer/code-review/) para te ajudar a revisar os projetos que chegaram para você.
---
# Avisos finais
Ao finalizar e submeter o projeto, não se esqueça de avaliar sua experiência preenchendo o formulário. Leva menos de 3 minutos!
Link: [FORMULÁRIO DE AVALIAÇÃO DE PROJETO](https://be-trybe.typeform.com/to/ZTeR4IbH)
O avaliador automático não necessariamente avalia seu projeto na ordem em que os requisitos aparecem no readme. Isso acontece para deixar o processo de avaliação mais rápido. Então, não se assuste se isso acontecer, ok?

4
index.js Normal file
View file

@ -0,0 +1,4 @@
// não remova esse endpoint, e para o avaliador funcionar
app.get('/', (_request, response) => {
response.send();
});

24018
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

43
package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "sd-0x-store-manager",
"version": "1.0.0",
"description": "Simple CRUD to sales and products",
"main": "index.js",
"scripts": {
"test": "jest --runInBand",
"test:mocha": "nyc --all --include models --include services --include controllers mocha test/unit/*.js --exit",
"start": "nodemon index.js",
"lint": "eslint --no-inline-config --no-error-on-unmatched-pattern -c .eslintrc.json . --ext .js, .jsx"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tryber/sd-02-project-store-manager.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/tryber/sd-02-project-store-manager/issues"
},
"homepage": "https://github.com/tryber/sd-02-project-store-manager#readme",
"dependencies": {
"@hapi/boom": "^9.1.0",
"@hapi/joi": "^17.1.1",
"body-parser": "^1.19.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-rescue": "^1.1.26",
"faker": "^4.1.0",
"frisby": "^2.1.2",
"jest": "^26.4.1",
"mongodb": "^3.5.9",
"nodemon": "^2.0.4"
},
"devDependencies": {
"chai": "^4.3.4",
"eslint-config-trybe-backend": "^1.0.3",
"mocha": "^8.4.0",
"mongodb-memory-server": "^6.9.6",
"nyc": "^15.1.0",
"sinon": "^11.1.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
public/atualizarigual0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/comprascomstring.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/criarProdutos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
public/deletarumproduto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
public/igualazero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/listadeprodutos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
public/listarumproduto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
public/listaumavenda.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
public/menorque0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/mesmonome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
public/nomeMenorQue5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
public/orange-circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
public/produtonaoexiste.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
public/skipped-test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
public/testejestfrisby.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
public/todasvendas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
public/variascompras.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
public/vendanaoexiste.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

61
test/bonus.test.js Normal file
View file

@ -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);
});
});

460
test/products.test.js Normal file
View file

@ -0,0 +1,460 @@
const frisby = require('frisby');
const { MongoClient } = require('mongodb');
const mongoDbUrl = 'mongodb://mongodb: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');
});
});
});

837
test/sales.test.js Normal file
View file

@ -0,0 +1,837 @@
const frisby = require('frisby');
const { MongoClient } = require('mongodb');
const mongoDbUrl = 'mongodb://mongodb: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');
});
});
});

7
trybe.yml Normal file
View file

@ -0,0 +1,7 @@
ignore_files:
- test/bonus.test.js
- test/products.test.js
- test/sales.test.js
- .editorconfig
- .eslintignore
- .eslintrc.json