From f61f487d83e58baad819609bf1dd25a377b0a0fd Mon Sep 17 00:00:00 2001 From: josuemc Date: Sun, 23 Aug 2026 13:37:01 -0300 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20489-listar=20e=20buscar=20tipos=20d?= =?UTF-8?q?e=20vegeta=C3=A7=C3=B5es=20(n=C3=A3o=20finalizada)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/application/create-app.ts | 4 +- .../vegetacao/BuscarVegetacaoController.ts | 41 +++++++++ .../vegetacao/ListaVegetacoesController.ts | 50 ++++++++++ src/application/vegetacao/index.ts | 35 +++++++ .../vegetacao/BuscarVegetacaoPorIdUseCase.ts | 20 ++++ .../vegetacao/ListaVegetacoesUseCase.ts | 20 ++++ src/domain/vegetacao/Vegetacao.ts | 24 +++++ src/domain/vegetacao/VegetacaoCollection.ts | 18 ++++ .../VegetacaoCollectionKnexAdapter.ts | 46 ++++++++++ .../vegetacao/lista-vegetacoes.test.ts | 91 +++++++++++++++++++ 10 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 src/application/vegetacao/BuscarVegetacaoController.ts create mode 100644 src/application/vegetacao/ListaVegetacoesController.ts create mode 100644 src/application/vegetacao/index.ts create mode 100644 src/domain/vegetacao/BuscarVegetacaoPorIdUseCase.ts create mode 100644 src/domain/vegetacao/ListaVegetacoesUseCase.ts create mode 100644 src/domain/vegetacao/Vegetacao.ts create mode 100644 src/domain/vegetacao/VegetacaoCollection.ts create mode 100644 src/infrastructure/VegetacaoCollectionKnexAdapter.ts create mode 100644 test/integration/vegetacao/lista-vegetacoes.test.ts diff --git a/src/application/create-app.ts b/src/application/create-app.ts index 0f53e372..f43fd845 100644 --- a/src/application/create-app.ts +++ b/src/application/create-app.ts @@ -14,6 +14,7 @@ import legacyErrors from '../middlewares/erros-middleware' import { generatePreview, reportPreview } from '../reports/controller' import { routes as createEstadoRoutes } from './estado' import { routes as createPaisRoutes } from './pais' +import { routes as createVegetacaoRoutes } from './vegetacao' interface CorsParameters { origins: string[] @@ -55,7 +56,8 @@ export function createApp({ }: Parameters) { const routes: Route[] = [ ...createPaisRoutes(knex), - ...createEstadoRoutes(knex) + ...createEstadoRoutes(knex), + ...createVegetacaoRoutes(knex) ] const application = new ExpressApplication({ logger }) diff --git a/src/application/vegetacao/BuscarVegetacaoController.ts b/src/application/vegetacao/BuscarVegetacaoController.ts new file mode 100644 index 00000000..69376b07 --- /dev/null +++ b/src/application/vegetacao/BuscarVegetacaoController.ts @@ -0,0 +1,41 @@ +import { BuscarVegetacaoPorIdUseCase } from '@/domain/vegetacao/BuscarVegetacaoPorIdUseCase' +import { + HttpRequest, HttpResponse, StatusCode +} from '@/library/http/common' +import { BadRequestError } from '@/library/http/error/BadRequestError' +import { HttpError } from '@/library/http/error/HttpError' +import { InternalServerError } from '@/library/http/error/InternalServerError' +import { NotFoundError } from '@/library/http/error/NotFoundError' +import { NextHandler, RequestHandler } from '@/library/http/Server' + +interface Dependencies { + buscarVegetacaoPorIdUseCase: BuscarVegetacaoPorIdUseCase +} + +export class BuscarVegetacaoController implements RequestHandler { + private readonly buscarVegetacaoPorIdUseCase: BuscarVegetacaoPorIdUseCase + + constructor(dependencies: Dependencies) { + this.buscarVegetacaoPorIdUseCase = dependencies.buscarVegetacaoPorIdUseCase + } + + async handle(request: HttpRequest, _next: NextHandler): Promise { + const { vegetacaoId } = request.params as { vegetacaoId?: string } + + if (vegetacaoId === undefined || vegetacaoId === null || vegetacaoId === '' || !/^\d+$/.test(vegetacaoId)) { + return new BadRequestError({ message: 'vegetacaoId inválido' }) + } + + const result = await this.buscarVegetacaoPorIdUseCase.execute({ id: Number(vegetacaoId) }) + + if (result.left()) { + return new InternalServerError({ message: result.value.message }) + } + + if (!result.value) { + return new NotFoundError({ message: 'Vegetação não encontrada' }) + } + + return { statusCode: StatusCode.Ok, body: result.value } + } +} diff --git a/src/application/vegetacao/ListaVegetacoesController.ts b/src/application/vegetacao/ListaVegetacoesController.ts new file mode 100644 index 00000000..189c7afb --- /dev/null +++ b/src/application/vegetacao/ListaVegetacoesController.ts @@ -0,0 +1,50 @@ +import { ListaVegetacoesUseCase } from '@/domain/vegetacao/ListaVegetacoesUseCase' +import { + HttpRequest, HttpResponse, StatusCode +} from '@/library/http/common' +import { HttpError } from '@/library/http/error/HttpError' +import { InternalServerError } from '@/library/http/error/InternalServerError' +import { NextHandler, RequestHandler } from '@/library/http/Server' + +interface Dependencies { + listaVegetacoesUseCase: ListaVegetacoesUseCase +} + +export class ListaVegetacoesController implements RequestHandler { + private readonly listaVegetacoesUseCase: ListaVegetacoesUseCase + + constructor(dependencies: Dependencies) { + this.listaVegetacoesUseCase = dependencies.listaVegetacoesUseCase + } + + async handle(request: HttpRequest, _next: NextHandler): Promise { + const { nome, order } = request.params as { + nome?: string + order?: string + } + + const result = await this.listaVegetacoesUseCase.execute({ + nome, + order: parseOrder(order) + }) + + if (result.left()) { + return new InternalServerError({ message: result.value.message }) + } + + return { statusCode: StatusCode.Ok, body: result.value } + } +} + +function parseOrder(order?: string): { column: 'id' | 'nome'; direction: 'asc' | 'desc' } | undefined { + if (!order) return undefined + + const [column, direction] = order.split(':') + const normalizedColumn = column === 'nome' || column === 'id' ? column : 'id' + const normalizedDirection = direction === 'asc' || direction === 'desc' ? direction : 'desc' + + return { + column: normalizedColumn, + direction: normalizedDirection + } +} diff --git a/src/application/vegetacao/index.ts b/src/application/vegetacao/index.ts new file mode 100644 index 00000000..e591fe33 --- /dev/null +++ b/src/application/vegetacao/index.ts @@ -0,0 +1,35 @@ +import { type Knex } from 'knex' + +import { BuscarVegetacaoPorIdUseCase } from '@/domain/vegetacao/BuscarVegetacaoPorIdUseCase' +import { ListaVegetacoesUseCase } from '@/domain/vegetacao/ListaVegetacoesUseCase' +import { VegetacaoCollectionKnexAdapter } from '@/infrastructure/VegetacaoCollectionKnexAdapter' +import { Method } from '@/library/http/common' +import { Route } from '@/library/http/Router' + +import { BuscarVegetacaoController } from './BuscarVegetacaoController' +import { ListaVegetacoesController } from './ListaVegetacoesController' + +export function routes(knex: Knex): Route[] { + const vegetacaoCollection = new VegetacaoCollectionKnexAdapter({ knex }) + + return [ + { + handlers: [ + new ListaVegetacoesController({ + listaVegetacoesUseCase: new ListaVegetacoesUseCase({ vegetacaoCollection }) + }) + ], + method: Method.Get, + path: '/v2/vegetacoes' + }, + { + handlers: [ + new BuscarVegetacaoController({ + buscarVegetacaoPorIdUseCase: new BuscarVegetacaoPorIdUseCase({ vegetacaoCollection }) + }) + ], + method: Method.Get, + path: '/v2/vegetacoes/:vegetacaoId' + } + ] +} diff --git a/src/domain/vegetacao/BuscarVegetacaoPorIdUseCase.ts b/src/domain/vegetacao/BuscarVegetacaoPorIdUseCase.ts new file mode 100644 index 00000000..2a855098 --- /dev/null +++ b/src/domain/vegetacao/BuscarVegetacaoPorIdUseCase.ts @@ -0,0 +1,20 @@ +import { Either } from '@/library/either/Either' + +import { Attributes } from './Vegetacao' +import { VegetacaoCollection } from './VegetacaoCollection' + +interface Dependencies { + vegetacaoCollection: VegetacaoCollection +} + +export class BuscarVegetacaoPorIdUseCase { + private readonly vegetacaoCollection: VegetacaoCollection + + constructor(dependencies: Dependencies) { + this.vegetacaoCollection = dependencies.vegetacaoCollection + } + + execute({ id }: { id: number }): Promise> { + return this.vegetacaoCollection.findById(id) + } +} diff --git a/src/domain/vegetacao/ListaVegetacoesUseCase.ts b/src/domain/vegetacao/ListaVegetacoesUseCase.ts new file mode 100644 index 00000000..42165b2d --- /dev/null +++ b/src/domain/vegetacao/ListaVegetacoesUseCase.ts @@ -0,0 +1,20 @@ +import { Either } from '@/library/either/Either' + +import { Attributes } from './Vegetacao' +import { VegetacaoCollection, VegetacaoFilters } from './VegetacaoCollection' + +interface Dependencies { + vegetacaoCollection: VegetacaoCollection +} + +export class ListaVegetacoesUseCase { + private readonly vegetacaoCollection: VegetacaoCollection + + constructor(dependencies: Dependencies) { + this.vegetacaoCollection = dependencies.vegetacaoCollection + } + + execute(filters: VegetacaoFilters): Promise> { + return this.vegetacaoCollection.findAll(filters) + } +} diff --git a/src/domain/vegetacao/Vegetacao.ts b/src/domain/vegetacao/Vegetacao.ts new file mode 100644 index 00000000..824e17e7 --- /dev/null +++ b/src/domain/vegetacao/Vegetacao.ts @@ -0,0 +1,24 @@ +import { Either } from '@/library/either/Either' + +export interface Attributes { + id: number + nome: string +} + +export class Vegetacao { + readonly id: number + readonly nome: string + + private constructor(attributes: Attributes) { + this.id = attributes.id + this.nome = attributes.nome + } + + static create(attributes: Attributes): Either { + if (!attributes.nome.trim()) { + return Either.left(new Error('Nome da vegetação não pode ser vazio')) + } + + return Either.right(new Vegetacao(attributes)) + } +} diff --git a/src/domain/vegetacao/VegetacaoCollection.ts b/src/domain/vegetacao/VegetacaoCollection.ts new file mode 100644 index 00000000..13ab350d --- /dev/null +++ b/src/domain/vegetacao/VegetacaoCollection.ts @@ -0,0 +1,18 @@ +import { Either } from '@/library/either/Either' + +import { Attributes } from './Vegetacao' + +export interface VegetacaoOrder { + column: 'id' | 'nome' + direction: 'asc' | 'desc' +} + +export interface VegetacaoFilters { + nome?: string + order?: VegetacaoOrder +} + +export interface VegetacaoCollection { + findAll(filters: VegetacaoFilters): Promise> + findById(id: number): Promise> +} diff --git a/src/infrastructure/VegetacaoCollectionKnexAdapter.ts b/src/infrastructure/VegetacaoCollectionKnexAdapter.ts new file mode 100644 index 00000000..42de847d --- /dev/null +++ b/src/infrastructure/VegetacaoCollectionKnexAdapter.ts @@ -0,0 +1,46 @@ +import { Knex } from 'knex' + +import { Attributes } from '@/domain/vegetacao/Vegetacao' +import { VegetacaoCollection, VegetacaoFilters } from '@/domain/vegetacao/VegetacaoCollection' +import { Either } from '@/library/either/Either' + +import { CollectionError } from './error/CollectionError' + +interface Dependencies { + knex: Knex +} + +export class VegetacaoCollectionKnexAdapter implements VegetacaoCollection { + private readonly knex: Knex + + constructor(dependencies: Dependencies) { + this.knex = dependencies.knex + } + + async findAll(filters: VegetacaoFilters): Promise> { + try { + const query = this.knex('vegetacoes') + .select(['id', 'nome']) + + if (filters.nome) { + query.whereILike('nome', `%${filters.nome}%`) + } + + const order = filters.order ?? { column: 'id', direction: 'desc' } + query.orderBy(order.column, order.direction) + + return Either.right(await query) + } catch (error) { + return Either.left(new CollectionError({ message: 'Failed to list vegetações', cause: error })) + } + } + + async findById(id: number): Promise> { + try { + const vegetacao = await this.knex('vegetacoes').select(['id', 'nome']).where({ id }).first() + return Either.right(vegetacao ?? null) + } catch (error) { + return Either.left(new CollectionError({ message: 'Failed to find vegetação', cause: error })) + } + } +} diff --git a/test/integration/vegetacao/lista-vegetacoes.test.ts b/test/integration/vegetacao/lista-vegetacoes.test.ts new file mode 100644 index 00000000..f613c92b --- /dev/null +++ b/test/integration/vegetacao/lista-vegetacoes.test.ts @@ -0,0 +1,91 @@ +import { + afterAll, describe, expect, test +} from 'vitest' + +import { createTestApp } from '../setup/app-factory' + +type Vegetacao = { id: number; nome: string } + +const returning = ['id', 'nome'] as const + +describe('GET /api/v2/vegetacoes', () => { + const { agent, knex } = createTestApp() + + afterAll(() => knex.destroy()) + + test('retorna a lista ordenada por id decrescente como padrão', async () => { + const nomes = ['XVEG Mata Atlântica', 'XVEG Restinga', 'XVEG Campo'] + + const inserted = await knex('vegetacoes') + .insert(nomes.map(nome => ({ nome }))) + .returning(returning) + + try { + const response = await agent.get('/api/v2/vegetacoes').expect(200) + const expected = [...inserted].sort((a, b) => b.id - a.id) + expect(response.body).toEqual(expected) + } finally { + await knex('vegetacoes').whereIn('nome', nomes).delete() + } + }) + + test('filtra por nome sem diferenciar maiúsculas e minúsculas', async () => { + const nomes = ['XVEG Floresta', 'XVEG Cerrado', 'XVEG Outros'] + const inserted = await knex('vegetacoes') + .insert(nomes.map(nome => ({ nome }))) + .returning(returning) + + try { + const response = await agent.get('/api/v2/vegetacoes?nome=floresta').expect(200) + expect(response.body).toEqual(inserted.filter(item => item.nome === 'XVEG Floresta')) + } finally { + await knex('vegetacoes').whereIn('nome', nomes).delete() + } + }) + + test('aceita ordenação customizada por nome e id', async () => { + const nomes = ['XVEG Z', 'XVEG A', 'XVEG M'] + const inserted = await knex('vegetacoes') + .insert(nomes.map(nome => ({ nome }))) + .returning(returning) + + try { + const byNameAsc = await agent.get('/api/v2/vegetacoes?order=nome:asc').expect(200) + expect(byNameAsc.body).toEqual([...inserted].sort((a, b) => a.nome.localeCompare(b.nome))) + + const byIdAsc = await agent.get('/api/v2/vegetacoes?order=id:asc').expect(200) + expect(byIdAsc.body).toEqual([...inserted].sort((a, b) => a.id - b.id)) + } finally { + await knex('vegetacoes').whereIn('nome', nomes).delete() + } + }) +}) + +describe('GET /api/v2/vegetacoes/:vegetacaoId', () => { + const { agent, knex } = createTestApp() + + afterAll(() => knex.destroy()) + + test('retorna o registro encontrado', async () => { + const [vegetacao] = await knex('vegetacoes') + .insert({ nome: 'XVEG Vegetação Encontrada' }) + .returning(returning) + + try { + const response = await agent.get(`/api/v2/vegetacoes/${vegetacao.id}`).expect(200) + expect(response.body).toEqual({ id: vegetacao.id, nome: vegetacao.nome }) + } finally { + await knex('vegetacoes').where({ id: vegetacao.id }).delete() + } + }) + + test('retorna 404 para id inexistente', async () => { + const response = await agent.get('/api/v2/vegetacoes/999999').expect(404) + expect(response.body.error.message).toMatch(/não encontrado|not found|not found/i) + }) + + test('retorna 400 para id inválido', async () => { + const response = await agent.get('/api/v2/vegetacoes/abc').expect(400) + expect(response.body.error.message).toMatch(/inválido|invalid/i) + }) +}) From 71cfaefef5e84c5be43dd3f42bdea2aaf884f8ea Mon Sep 17 00:00:00 2001 From: josuemc Date: Wed, 26 Aug 2026 11:01:03 -0300 Subject: [PATCH 2/4] Fix: test e yarn lint --- .../vegetacao/lista-vegetacoes.test.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/test/integration/vegetacao/lista-vegetacoes.test.ts b/test/integration/vegetacao/lista-vegetacoes.test.ts index f613c92b..24081f05 100644 --- a/test/integration/vegetacao/lista-vegetacoes.test.ts +++ b/test/integration/vegetacao/lista-vegetacoes.test.ts @@ -14,7 +14,11 @@ describe('GET /api/v2/vegetacoes', () => { afterAll(() => knex.destroy()) test('retorna a lista ordenada por id decrescente como padrão', async () => { - const nomes = ['XVEG Mata Atlântica', 'XVEG Restinga', 'XVEG Campo'] + const nomes = [ + 'XVEG Mata Atlântica', + 'XVEG Restinga', + 'XVEG Campo' + ] const inserted = await knex('vegetacoes') .insert(nomes.map(nome => ({ nome }))) @@ -30,7 +34,11 @@ describe('GET /api/v2/vegetacoes', () => { }) test('filtra por nome sem diferenciar maiúsculas e minúsculas', async () => { - const nomes = ['XVEG Floresta', 'XVEG Cerrado', 'XVEG Outros'] + const nomes = [ + 'XVEG Floresta', + 'XVEG Cerrado', + 'XVEG Outros' + ] const inserted = await knex('vegetacoes') .insert(nomes.map(nome => ({ nome }))) .returning(returning) @@ -44,7 +52,11 @@ describe('GET /api/v2/vegetacoes', () => { }) test('aceita ordenação customizada por nome e id', async () => { - const nomes = ['XVEG Z', 'XVEG A', 'XVEG M'] + const nomes = [ + 'XVEG Z', + 'XVEG A', + 'XVEG M' + ] const inserted = await knex('vegetacoes') .insert(nomes.map(nome => ({ nome }))) .returning(returning) @@ -81,11 +93,15 @@ describe('GET /api/v2/vegetacoes/:vegetacaoId', () => { test('retorna 404 para id inexistente', async () => { const response = await agent.get('/api/v2/vegetacoes/999999').expect(404) - expect(response.body.error.message).toMatch(/não encontrado|not found|not found/i) + const body = response.body as { error: { message: string } } + + expect(body.error.message).toMatch(/não encontrado|not found/i) }) test('retorna 400 para id inválido', async () => { const response = await agent.get('/api/v2/vegetacoes/abc').expect(400) - expect(response.body.error.message).toMatch(/inválido|invalid/i) + const body = response.body as { error: { message: string } } + + expect(body.error.message).toMatch(/inválido|invalid/i) }) }) From 14c82eec6a2779d7861bad232ca5e04cdbedd178 Mon Sep 17 00:00:00 2001 From: josuemc Date: Wed, 26 Aug 2026 11:08:00 -0300 Subject: [PATCH 3/4] fix: test --- test/integration/vegetacao/lista-vegetacoes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/vegetacao/lista-vegetacoes.test.ts b/test/integration/vegetacao/lista-vegetacoes.test.ts index 24081f05..6221f271 100644 --- a/test/integration/vegetacao/lista-vegetacoes.test.ts +++ b/test/integration/vegetacao/lista-vegetacoes.test.ts @@ -95,7 +95,7 @@ describe('GET /api/v2/vegetacoes/:vegetacaoId', () => { const response = await agent.get('/api/v2/vegetacoes/999999').expect(404) const body = response.body as { error: { message: string } } - expect(body.error.message).toMatch(/não encontrado|not found/i) + expect(body.error.message).toMatch(/não encontrad[ao]|not found/i) }) test('retorna 400 para id inválido', async () => { From b4a00bf97401605ecbe6fccc4bf7365315a446c0 Mon Sep 17 00:00:00 2001 From: josuemc Date: Tue, 1 Sep 2026 12:45:53 -0300 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20Corre=C3=A7=C3=B5es=20requisitadas?= =?UTF-8?q?=20no=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vegetacao/ListaVegetacoesController.ts | 29 ++++++++--- .../vegetacao/lista-vegetacoes.test.ts | 52 +++++++++++++------ 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/application/vegetacao/ListaVegetacoesController.ts b/src/application/vegetacao/ListaVegetacoesController.ts index 189c7afb..18d05692 100644 --- a/src/application/vegetacao/ListaVegetacoesController.ts +++ b/src/application/vegetacao/ListaVegetacoesController.ts @@ -2,6 +2,7 @@ import { ListaVegetacoesUseCase } from '@/domain/vegetacao/ListaVegetacoesUseCas import { HttpRequest, HttpResponse, StatusCode } from '@/library/http/common' +import { BadRequestError } from '@/library/http/error/BadRequestError' import { HttpError } from '@/library/http/error/HttpError' import { InternalServerError } from '@/library/http/error/InternalServerError' import { NextHandler, RequestHandler } from '@/library/http/Server' @@ -23,9 +24,14 @@ export class ListaVegetacoesController implements RequestHandler { order?: string } + const parsedOrder = parseOrder(order) + if (parsedOrder instanceof Error) { + return new BadRequestError({ message: parsedOrder.message }) + } + const result = await this.listaVegetacoesUseCase.execute({ nome, - order: parseOrder(order) + order: parsedOrder }) if (result.left()) { @@ -36,15 +42,24 @@ export class ListaVegetacoesController implements RequestHandler { } } -function parseOrder(order?: string): { column: 'id' | 'nome'; direction: 'asc' | 'desc' } | undefined { +function parseOrder(order?: string): { column: 'id' | 'nome'; direction: 'asc' | 'desc' } | Error | undefined { if (!order) return undefined - const [column, direction] = order.split(':') - const normalizedColumn = column === 'nome' || column === 'id' ? column : 'id' - const normalizedDirection = direction === 'asc' || direction === 'desc' ? direction : 'desc' + const pieces = order.split(':') + if (pieces.length !== 2) { + return new Error('order inválido. Use o formato "id:asc", "id:desc", "nome:asc" ou "nome:desc"') + } + + const [rawColumn, rawDirection] = pieces + const column = rawColumn.trim().toLowerCase() + const direction = rawDirection.trim().toLowerCase() + + if ((column !== 'id' && column !== 'nome') || (direction !== 'asc' && direction !== 'desc')) { + return new Error('order inválido. Use o formato "id:asc", "id:desc", "nome:asc" ou "nome:desc"') + } return { - column: normalizedColumn, - direction: normalizedDirection + column, + direction } } diff --git a/test/integration/vegetacao/lista-vegetacoes.test.ts b/test/integration/vegetacao/lista-vegetacoes.test.ts index 6221f271..9f64d264 100644 --- a/test/integration/vegetacao/lista-vegetacoes.test.ts +++ b/test/integration/vegetacao/lista-vegetacoes.test.ts @@ -13,11 +13,12 @@ describe('GET /api/v2/vegetacoes', () => { afterAll(() => knex.destroy()) - test('retorna a lista ordenada por id decrescente como padrão', async () => { + test('retorna a lista ordenada por id decrescente como padrão dentro do prefixo do teste', async () => { + const prefix = 'XVEG' const nomes = [ - 'XVEG Mata Atlântica', - 'XVEG Restinga', - 'XVEG Campo' + `${prefix} Mata Atlântica`, + `${prefix} Restinga`, + `${prefix} Campo` ] const inserted = await knex('vegetacoes') @@ -25,7 +26,7 @@ describe('GET /api/v2/vegetacoes', () => { .returning(returning) try { - const response = await agent.get('/api/v2/vegetacoes').expect(200) + const response = await agent.get(`/api/v2/vegetacoes?nome=${prefix}`).expect(200) const expected = [...inserted].sort((a, b) => b.id - a.id) expect(response.body).toEqual(expected) } finally { @@ -34,43 +35,64 @@ describe('GET /api/v2/vegetacoes', () => { }) test('filtra por nome sem diferenciar maiúsculas e minúsculas', async () => { + const prefix = 'XVEG' const nomes = [ - 'XVEG Floresta', - 'XVEG Cerrado', - 'XVEG Outros' + `${prefix} Floresta`, + `${prefix} Cerrado`, + `${prefix} Outros` ] const inserted = await knex('vegetacoes') .insert(nomes.map(nome => ({ nome }))) .returning(returning) try { - const response = await agent.get('/api/v2/vegetacoes?nome=floresta').expect(200) - expect(response.body).toEqual(inserted.filter(item => item.nome === 'XVEG Floresta')) + const response = await agent.get(`/api/v2/vegetacoes?nome=${prefix} floresta`).expect(200) + expect(response.body).toEqual(inserted.filter(item => item.nome === `${prefix} Floresta`)) } finally { await knex('vegetacoes').whereIn('nome', nomes).delete() } }) test('aceita ordenação customizada por nome e id', async () => { + const prefix = 'XVEG' const nomes = [ - 'XVEG Z', - 'XVEG A', - 'XVEG M' + `${prefix} Z`, + `${prefix} A`, + `${prefix} M` ] const inserted = await knex('vegetacoes') .insert(nomes.map(nome => ({ nome }))) .returning(returning) try { - const byNameAsc = await agent.get('/api/v2/vegetacoes?order=nome:asc').expect(200) + const byNameAsc = await agent.get(`/api/v2/vegetacoes?nome=${prefix}&order=nome:asc`).expect(200) expect(byNameAsc.body).toEqual([...inserted].sort((a, b) => a.nome.localeCompare(b.nome))) - const byIdAsc = await agent.get('/api/v2/vegetacoes?order=id:asc').expect(200) + const byIdAsc = await agent.get(`/api/v2/vegetacoes?nome=${prefix}&order=id:asc`).expect(200) expect(byIdAsc.body).toEqual([...inserted].sort((a, b) => a.id - b.id)) } finally { await knex('vegetacoes').whereIn('nome', nomes).delete() } }) + + test('retorna 400 quando a ordenação é inválida', async () => { + const prefix = 'XVEG' + const nomes = [ + `${prefix} Z`, + `${prefix} A`, + `${prefix} M` + ] + + await knex('vegetacoes').insert(nomes.map(nome => ({ nome }))) + + try { + const response = await agent.get(`/api/v2/vegetacoes?nome=${prefix}&order=foo:bar`).expect(400) + const body = response.body as { error: { message: string } } + expect(body.error.message).toMatch(/inválido|invalid/i) + } finally { + await knex('vegetacoes').whereIn('nome', nomes).delete() + } + }) }) describe('GET /api/v2/vegetacoes/:vegetacaoId', () => {