Feat: Cadastrar, Renomear e Remover tipos de vegetações - #525
JosueModesto wants to merge 5 commits into
Conversation
|
|
||
| return [ | ||
| { | ||
| handlers: [ |
There was a problem hiding this comment.
POST, PUT e DELETE montam só o controller em handlers. O legado POST /vegetacoes exige tokensMiddleware([CURADOR, OPERADOR]). País/estado v2 são só GET públicos e não justificam mutação anônima do catálogo. Os testes de escrita passam sem Authorization.
|
|
||
| if (result.left()) { | ||
| const message = result.value.message.toLowerCase() | ||
| if (message.includes('duplicate') || message.includes('unique') || message.includes('already') || message.includes('exist')) { |
There was a problem hiding this comment.
message.includes('exist') trata como conflito qualquer falha cujo texto contenha exist (por exemplo relation "vegetacoes" does not exist). O else devolve InternalServerError com result.value.message; no adapter o catch usa cause.message cru. O mesmo padrão está em RenomeiaVegetacaoController e RemoveVegetacaoController.
| } | ||
| }) | ||
|
|
||
| test('retorna 404 para id inexistente', async () => { |
There was a problem hiding this comment.
O PR pede nome vazio → 400, duplicidade → 409, vegetacaoId inválido → 400 e 404. Este arquivo só cobre sucesso, 404 e id abc. DELETE só cobre 204 e 409 em uso. No PUT, nome duplicado vira BadRequestError 400, não 409.
| if (result.left()) { | ||
| const message = result.value.message.toLowerCase() | ||
| if (message.includes('duplicate') || message.includes('unique') || message.includes('already') || message.includes('exist')) { | ||
| return new BadRequestError({ message: 'Já existe uma vegetação com esse nome' }) |
There was a problem hiding this comment.
Duplicidade no PUT retorna BadRequestError (400). O POST mapeia o mesmo caso para ConflictError (409). Vale alinhar os dois para 409.
| return Either.left(new CollectionError({ message: cause.message, cause })) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Unicidade é findByNome e depois insert/update. A tabela vegetacoes só tem PK, então duas requisições concorrentes com o mesmo nome passam no select e as duas gravam. O catch de unique/23505 não dispara sem constraint.
| expect(body.error.message).toMatch(/vazio|empty|obrigat/i) | ||
| }) | ||
|
|
||
| test('retorna 400 para nome duplicado', async () => { |
There was a problem hiding this comment.
O título do teste diz 400, mas o expect na linha 49 é 409.
| } | ||
|
|
||
| try { | ||
| const payload = jwt.verify(token, process.env.JWT_SECRET ?? 'test-secret') as { tipo_usuario_id?: unknown } |
There was a problem hiding this comment.
jwt.verify usa process.env.JWT_SECRET ?? 'test-secret'. O legado verifica com o secret de src/config/security.js, sem fallback. Sem JWT_SECRET, qualquer token assinado com test-secret passa. Os testes de integração assinam com o mesmo fallback; o secret de teste deve ficar no setup, não no handler de produção.
| updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL | ||
| ); | ||
|
|
||
| CREATE UNIQUE INDEX vegetacoes_nome_unique |
There was a problem hiding this comment.
O índice único em LOWER(nome) existe só no schema de integração. Não há migration em src/database e o model Sequelize de vegetacoes.nome não é unique. Em MySQL de produção o check-then-write continua sujeito a corrida e o 23505 do adapter não dispara.
| const message = typeof error === 'object' && error !== null && 'message' in error ? String((error as { message?: unknown }).message) : '' | ||
| const constraint = typeof error === 'object' && error !== null && 'constraint' in error ? String((error as { constraint?: unknown }).constraint) : '' | ||
|
|
||
| return code === '23505' |
There was a problem hiding this comment.
Unique e FK estão mapeados com códigos/textos Postgres (23505, 23503, violates foreign key constraint). O app default é MySQL (ER_DUP_ENTRY 1062, ER_ROW_IS_REFERENCED_2 1451, foreign key constraint fails). Delete em uso no MySQL pode virar 500 em vez de 409 se error.constraint não vier preenchido.
|
|
||
| if (!authorization || typeof authorization !== 'string' || !authorization.startsWith('Bearer ')) { | ||
| return new ForbiddenError({ message: 'Token de autenticação obrigatório' }) | ||
| } |
There was a problem hiding this comment.
Token ausente ou vazio retorna 403, papel diferente de 1|2 retorna 403, expirado ou inválido retorna 401. Os testes de POST/PUT/DELETE só enviam token de curador no caminho feliz; esses ramos novos não estão cobertos.
|
|
||
| const ALLOWED_TIPOS_USUARIOS = new Set([1, 2]) | ||
|
|
||
| export class RequireVegetacaoWriteAccess implements RequestHandler { |
There was a problem hiding this comment.
RequireVegetacaoWriteAccess está no infinitivo inglês. Tipos novos devem usar 3ª pessoa do singular (RequiresVegetacaoWriteAccess) ou um verbo finito em português, sem misturar idiomas no nome.
| } | ||
|
|
||
| const cause = error instanceof Error ? error : new Error(String(error)) | ||
| return Either.left(new CollectionError({ message: cause.message, cause })) |
There was a problem hiding this comment.
O catch genérico devolve cause.message. Essa string chega no InternalServerError do controller e pode expor SQL no JSON de 500.
Close #490
O que foi feito
Foi implementado o módulo de cadastro, atualização e remoção de tipos de vegetação na API v2, preservando a API legada sem alterações e seguindo o padrão de autenticação já utilizado pelo projeto.
Alterações principais
Cobertura de testes