Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e1c8138
docs: adiciona especificação de autores
thiagomini Mar 21, 2026
493174a
docs: atualiza o arquivo de autores
thiagomini Mar 21, 2026
01ef07f
docs: corrige descricao do requisito
thiagomini Mar 23, 2026
111fdef
test: cria teste e2e
thiagomini Mar 23, 2026
363a42d
Merge branch 'main' of github.com:thiagomini/curso-testes into aula-02
thiagomini Mar 23, 2026
e9cb028
fix: valida dados ao cadastrar um autor
thiagomini Mar 24, 2026
305f1a3
test: cria testes e2e para buscar autor por id
thiagomini Mar 25, 2026
a0fae93
test: adiciona testes e2e para listar autores
thiagomini Mar 27, 2026
cfd8ecf
test: implementa teste caixa-cinza
thiagomini Mar 28, 2026
a726a8a
test: implementa testes unitarios de autores
thiagomini Mar 29, 2026
4b0202c
feat: implementa rota re registro de vendas
thiagomini Mar 29, 2026
a79a119
test: implemente testes unitarios para calcularValorVenda
thiagomini Mar 30, 2026
b5b282e
test: adiciona primeiro teste de integracao
thiagomini Apr 1, 2026
13d41b6
test: adiciona teste de integracao da controller de livros
thiagomini Apr 1, 2026
a08202f
test: reimplementa testes da livros controller
thiagomini Apr 2, 2026
3985745
refactor: utiliza livros service na controller
thiagomini Apr 3, 2026
3d66192
feat: cria metodo no service para buscar livro por id
thiagomini Apr 5, 2026
d7c2536
feat: implementa gateway de email
thiagomini Apr 5, 2026
f714b0b
test: cria teste do envio de email ao realizar uma venda
thiagomini Apr 5, 2026
a004efe
test: adiciona teste quando livro nao possui estoque
thiagomini Apr 6, 2026
6a94f6c
test: corrige testes e2e com stubs e mocks
thiagomini Apr 6, 2026
05a0790
ci: configura workflow para ci
F3rCar Jun 15, 2026
3f7f41c
Create node.js.yml
F3rCar Jun 15, 2026
2096ab5
ci: configura workflow para ci
F3rCar Jun 15, 2026
e8318ad
ci: configura workflow para ci
F3rCar Jun 15, 2026
72b5453
ci: configura workflow para ci agora vai
F3rCar Jun 15, 2026
fe07e00
Merge pull request #2 from F3rCar/aula-05
F3rCar Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Node.js CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
lint:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: 'npm'
- run: npm ci
- run: npm run lint
24 changes: 24 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
lint:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: 'npm'
- run: npm ci
- run: npm run lint
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
node_modules/
.env
.vscode
.env.*
!.env.example
.vscode
.eslintcache
33 changes: 33 additions & 0 deletions docs/autores.doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
- Contexto, Ação, Resultado Esperado

## Cadastrar um Autor

Rota: (POST) /autores

Cenários:

- Retorna os dados do autor cadastrado quando os dados são válidos (201).

- ❌ Retorna um erro ao tentar cadastrar um autor com dados inválidos (400).

## Recuperar um Autor

Rota: (GET) /autores/:id

Cenários:

- Retorna os dados de um autor existente (200).

- Retorna um erro quando o autor não existe (404).

## Listar Autores

Rota: (GET) /autores

Cenários:

- Retorna uma lista contendo os dados dos autores quando existe ao menos um autor cadastrado (200).

- Retorna uma lista vazia quando não existem autores cadastrados (200).

- (hipotético) Retorna um erro quando o usuário não está autenticado (403).
10 changes: 6 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,30 @@
"#controllers/*.js": "./src/controllers/*.js",
"#db/*.js": "./src/db/*.js",
"#models/*.js": "./src/models/*.js",
"#routes/*.js": "./src/routes/*.js"
"#domain/*.js": "./src/domain/*.js",
"#services/*.js": "./src/services/*.js",
"#routes/*.js": "./src/routes/*.js",
"#test/factories/*.js": "./test/factories/*.js",
"#test/utils/*.js": "./test/utils/*.js",
"#src/*.js": "./src/*.js"
},
"scripts": {
"start": "node --env-file=.env src/server.js",
"start:watch": "node --env-file=.env --watch src/server.js",
"test": "node --test",
"exec": "node --env-file=.env src/commands/execute.js"
"test:unit:all": "node --env-file=.env.test --test \"test/unit/**/*.spec.js\"",
"test:int:all": "node --env-file=.env.test --test --test-concurrency=1 \"test/int/**/*.int-spec.js\"",
"test:e2e": "node --env-file=.env.test --test --test-concurrency=1",
"test:e2e:all": "node --env-file=.env.test --test --test-concurrency=1 \"test/e2e/**/*.e2e-spec.js\"",
"exec": "node --env-file=.env src/commands/execute.js",
"lint": "eslint . --cache"
},
"packageManager": "npm@11.11.0+sha512.f36811c4aae1fde639527368ae44c571d050006a608d67a191f195a801a52637a312d259186254aa3a3799b05335b7390539cf28656d18f0591a1125ba35f973",
"devDependencies": {
"@eslint/js": "9.39.3",
"@types/supertest": "7.2.0",
"eslint": "9.39.3",
"globals": "17.4.0",
"supertest": "7.2.2"
"supertest": "^7.2.2"
},
"dependencies": {
"@types/express": "5.0.6",
Expand Down
22 changes: 22 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,32 @@ erDiagram

- POST /vendas → registra uma nova venda. Quando uma venda é feita, um email é enviado à editora.

Tipos de pagamento:

- CARTAO_CREDITO: 5% de taxa.
- CARTAO_DEBITO: 2% de taxa.
- BOLETO: sem taxa.
- DINHEIRO: sem taxa.
- PIX: 5% de desconto.

Request:

```json
{
"idLivro": 1,
"valor": 100,
"modoPagamento": "CARTAO_CREDITO"
}
```

Resposta:

```json
{
"id": 1,
"valor": 105,
"modoPagamento": "CARTAO_CREDITO",
"createdAt": "2026-01-01T12:00:00Z",
"updatedAt": "2026-01-01T12:00:00Z"
}
```
10 changes: 6 additions & 4 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import express from 'express';
import { routes } from './routes/index.js';

const app = express();
app.use(express.json());
export default function criarApp(dependencias) {
const app = express();
app.use(express.json());

routes(app);
routes(app, dependencias);

export default app;
return app;
}
11 changes: 10 additions & 1 deletion src/controllers/autores.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export class AutoresController {
try {
const resultado = await Autor.pegarPeloId(id);
if (!resultado) {
return res.status(404).json({ message: 'Autor não encontrado' });
return res
.status(404)
.json({ message: 'Autor não encontrado', type: 'NOT_FOUND' });
}
res.status(200).send(resultado);
} catch (err) {
Expand All @@ -29,6 +31,13 @@ export class AutoresController {

async cadastrarAutor(req, res) {
const { body } = req;
if (!body.nome || !body.nacionalidade) {
res.status(400).json({
message: 'Dados inválidos para cadastro de autor',
type: 'INVALID_DATA',
});
return;
}
const autor = new Autor(body);
try {
const resposta = await autor.salvar(autor);
Expand Down
13 changes: 9 additions & 4 deletions src/controllers/livros.controller.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import Livro from '#models/livro.js';
import { LivrosService } from '#services/livros.service.js';

export class LivrosController {
constructor(databaseConnection) {
Livro.configurarDB(databaseConnection);
this.livrosService = new LivrosService(databaseConnection);
}

async listarLivros(req, res) {
try {
const resultado = await Livro.pegarLivros();
res.status(200).send(resultado);
const resultado = await this.livrosService.listarLivros();
res.status(200).json(resultado);
} catch (err) {
console.error(err);
res.status(500).json(err.message);
}
}

async buscarLivroPorId(req, res) {
const { id } = req.params;
try {
const resultado = await Livro.pegarPeloId(id);
const resultado = await this.livrosService.buscarLivroPorId(id);
if (!resultado) {
return res.status(404).json({ message: 'Livro não encontrado' });
return res
.status(404)
.json({ message: 'Livro não encontrado', type: 'NOT_FOUND' });
}
res.status(200).send(resultado);
} catch (err) {
Expand Down
42 changes: 42 additions & 0 deletions src/controllers/vendas.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Venda from '#models/venda.js';

export class VendasController {
constructor(databaseConnection, vendasService) {
Venda.configurarDB(databaseConnection);
this.vendasService = vendasService;
}

async registrarVenda(req, res) {
const { idLivro, modoPagamento, valor } = req.body;

if (!idLivro || !modoPagamento || valor == null) {
return res.status(400).json({
message: 'Dados inválidos para registro de venda',
type: 'INVALID_DATA',
});
}

try {
const resultado = await this.vendasService.registrarVenda({
idLivro,
modoPagamento,
valor,
});

return res.status(201).json({
message: 'venda registrada',
content: {
id: resultado.id,
idLivro: resultado.livro_id,
valor: resultado.valor,
modoPagamento: resultado.tipo_pagamento,
createdAt: resultado.created_at,
updatedAt: resultado.updated_at,
},
});
} catch (err) {
console.error(err);
return res.status(500).json(err.message);
}
}
}
16 changes: 16 additions & 0 deletions src/domain/calcular-valor-venda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const taxas = {
CARTAO_CREDITO: 1.05,
CARTAO_DEBITO: 1.02,
BOLETO: 1,
DINHEIRO: 1,
PIX: 0.95,
};

export function calcularValorVenda(valor, modoPagamento) {
const multiplicador = taxas[modoPagamento];
if (multiplicador === undefined) {
throw new Error(`Modo de pagamento inválido: ${modoPagamento}`);
}

return Math.round(valor * multiplicador);
}
7 changes: 7 additions & 0 deletions src/gateways/email.gateway.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class EmailGateway {
async enviarEmail({ remetente, destinatario, assunto, mensagem }) {
console.log(
`Enviando email de ${remetente} para ${destinatario} com assunto "${assunto}" e mensagem: ${mensagem}`,
);
}
}
16 changes: 16 additions & 0 deletions src/gateways/estoque-api.gateway.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class EstoqueApiGateway {
async temEstoque(idLivro) {
// Simula uma chamada a um serviço externo
const apiEndpoint = `https://livraria.com/api/livros/${idLivro}/estoque`;

const resposta = await fetch(apiEndpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const sucesso = resposta.ok;

return sucesso;
}
}
2 changes: 1 addition & 1 deletion src/models/autor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Autor {
}

static async pegarAutores() {
return this.db.select('*').from('autores');
return this.db.select('*').from('autores').orderBy('nome', 'asc');
}

static async pegarPeloId(id) {
Expand Down
Loading