From 2796e7b718322543fec54ad8214c7d74368b135b Mon Sep 17 00:00:00 2001 From: ViniElias Date: Tue, 12 May 2026 20:22:16 -0300 Subject: [PATCH 1/4] Fix: filtros de chamados agora associam a categoria ao atendente corretamente --- .../useCases/readAll/readAll.usecase.ts | 12 +++++++- .../repository/ticket.repository.interface.ts | 6 +++- .../repositories/ticket.mongodb.repository.ts | 28 ++++++++++++++++++- .../controllers/ticket.controller.ts | 15 ++++++---- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/backend/src/modules/ticket/application/useCases/readAll/readAll.usecase.ts b/backend/src/modules/ticket/application/useCases/readAll/readAll.usecase.ts index 8462df7..5d84392 100644 --- a/backend/src/modules/ticket/application/useCases/readAll/readAll.usecase.ts +++ b/backend/src/modules/ticket/application/useCases/readAll/readAll.usecase.ts @@ -30,6 +30,10 @@ export class ReadAllTicketUseCase { userId: string; categories?: string[]; role: UserRole; + search?: string; + status?: TicketStatus; + escalationLevel?: number; + onlyMine?: boolean; }): Promise { const filters = input.role === UserRole.CLIENT @@ -38,7 +42,13 @@ export class ReadAllTicketUseCase { ? { agentId: input.userId, categories: input.categories } : undefined; - const foundedTickets = await this.repository.readAll({ ...filters }); + const foundedTickets = await this.repository.readAll({ + ...filters, + search: input.search, + status: input.status, + escalationLevel: input.escalationLevel, + onlyMine: input.onlyMine, + }); const convertedTickets = foundedTickets.map((t: Ticket) => { const primitive = t.toPrimitives(); diff --git a/backend/src/modules/ticket/domain/repository/ticket.repository.interface.ts b/backend/src/modules/ticket/domain/repository/ticket.repository.interface.ts index a62ab13..1420269 100644 --- a/backend/src/modules/ticket/domain/repository/ticket.repository.interface.ts +++ b/backend/src/modules/ticket/domain/repository/ticket.repository.interface.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { Ticket } from '../entities/ticket.entity'; +import { Ticket, TicketStatus } from '../entities/ticket.entity'; @Injectable() export abstract class ITicketRepository { @@ -9,6 +9,10 @@ export abstract class ITicketRepository { clientId?: string; agentId?: string; categories?: string[]; + search?: string; + status?: TicketStatus; + escalationLevel?: number; + onlyMine?: boolean; }): Promise; abstract readById(id: string): Promise; abstract delete(id: string): Promise; diff --git a/backend/src/modules/ticket/infra/repositories/ticket.mongodb.repository.ts b/backend/src/modules/ticket/infra/repositories/ticket.mongodb.repository.ts index bdf6325..a17faa9 100644 --- a/backend/src/modules/ticket/infra/repositories/ticket.mongodb.repository.ts +++ b/backend/src/modules/ticket/infra/repositories/ticket.mongodb.repository.ts @@ -1,5 +1,5 @@ import { Model, QueryFilter } from 'mongoose'; -import { Ticket } from '../../domain/entities/ticket.entity'; +import { Ticket, TicketStatus } from '../../domain/entities/ticket.entity'; import { ITicketRepository } from '../../domain/repository/ticket.repository.interface'; import { TicketLean, TicketSchemaClass } from '../schemas/ticket.mongo.schema'; import { InjectModel } from '@nestjs/mongoose'; @@ -42,6 +42,10 @@ export class TicketMongoRepository extends ITicketRepository { clientId?: string; agentId?: string; categories?: string[]; + search?: string; + status?: TicketStatus; + escalationLevel?: number; + onlyMine?: boolean; }): Promise { let query: QueryFilter = {}; @@ -56,6 +60,28 @@ export class TicketMongoRepository extends ITicketRepository { query = { clientId: filters.clientId }; } + if (filters?.search) { + query = { + ...query, + $or: [ + { title: { $regex: filters.search, $options: 'i' } }, + { description: { $regex: filters.search, $options: 'i' } }, + ], + }; + } + + if (filters?.status) { + query = { ...query, status: filters.status }; + } + + if (filters?.escalationLevel) { + query = { ...query, escalationLevel: filters.escalationLevel }; + } + + if (filters?.onlyMine && filters?.agentId) { + query = { ...query, agentId: filters.agentId }; + } + const tickets = await this.ticketModel.find(query).exec(); return tickets.map((t) => TicketMapper.toDomain(t)); } diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts index 2811ac3..1809393 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts @@ -79,15 +79,20 @@ export class TicketController { @ApiOperation({ summary: 'Retorna todos os tickets' }) @UseGuards(JwtGuard, RolesGuard) @Roles(UserRole.ADMIN, UserRole.SUPPORT, UserRole.CLIENT) - @ApiResponse({ - status: 200, - description: 'Todos os tickets retornados com sucesso.', - }) - async getAll(@Request() req: any) { + @ApiQuery({ name: 'search', required: false, type: String }) + @ApiQuery({ name: 'status', required: false, enum: TicketStatus }) + @ApiQuery({ name: 'escalationLevel', required: false, type: Number }) + @ApiQuery({ name: 'onlyMine', required: false, type: Boolean }) + @ApiResponse({ status: 200, description: 'Todos os tickets retornados com sucesso.' }) + async getAll(@Request() req: any, @Query() query: any) { const response = await this.readAllUseCase.execute({ userId: req.user.id, categories: req.user.categories ?? undefined, role: req.user.role, + search: query.search, + status: query.status as TicketStatus, + escalationLevel: query.escalationLevel ? Number(query.escalationLevel) : undefined, + onlyMine: query.onlyMine === 'true', }); return response; From 9d3e869bb11f3b49f051f5e48f6005ef706f0b6e Mon Sep 17 00:00:00 2001 From: Vinicius Elias <95414991+ViniElias@users.noreply.github.com> Date: Wed, 13 May 2026 22:04:13 -0300 Subject: [PATCH 2/4] =?UTF-8?q?Refact:=20adi=C3=A7=C3=A3o=20de=20bloco=20t?= =?UTF-8?q?ry-finally?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/ticket.controller.spec.ts | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts index 880101a..54d09a8 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts @@ -564,42 +564,44 @@ describe('TicketController', () => { expect(getHistoryFilteredUseCase.execute).not.toHaveBeenCalled(); }); - it('GET /tickets should return tickets filtered by agentId when role is SUPPORT', async () => { - const agentId = randomUUID(); - const categories = [randomUUID()]; - const primitives = ticket.toPrimitives(); - - const moduleFixture = await Test.createTestingModule({ - controllers: [TicketController], - providers: [ - { provide: CreateTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: ReadAllTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: ReadByIdTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: GetHistoryTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: EscalateTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: NewAgentTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: DeleteTicketUseCase, useValue: { execute: jest.fn() } }, - { provide: GetHistoryFilteredUseCase, useValue: { execute: jest.fn() } }, - { provide: CloseTicketUseCase, useValue: { execute: jest.fn() } }, - ], +it('GET /tickets should return tickets filtered by agentId when role is SUPPORT', async () => { + const agentId = randomUUID(); + const categories = [randomUUID()]; + const primitives = ticket.toPrimitives(); + + const moduleFixture = await Test.createTestingModule({ + controllers: [TicketController], + providers: [ + { provide: CreateTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: ReadAllTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: ReadByIdTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: GetHistoryTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: EscalateTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: NewAgentTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: DeleteTicketUseCase, useValue: { execute: jest.fn() } }, + { provide: GetHistoryFilteredUseCase, useValue: { execute: jest.fn() } }, + { provide: CloseTicketUseCase, useValue: { execute: jest.fn() } }, + ], + }) + .overrideGuard(JwtGuard) + .useValue({ + canActivate: (context: ExecutionContext) => { + const req = context.switchToHttp().getRequest(); + req.user = { + id: agentId, + role: UserRole.SUPPORT, + categories: categories, + }; + return true; + }, }) - .overrideGuard(JwtGuard) - .useValue({ - canActivate: (context: ExecutionContext) => { - const req = context.switchToHttp().getRequest(); - req.user = { - id: agentId, - role: UserRole.SUPPORT, - categories: categories, // consistente com o JwtStrategy - }; - return true; - }, - }) - .overrideGuard(RolesGuard) - .useValue({ canActivate: () => true }) - .compile(); + .overrideGuard(RolesGuard) + .useValue({ canActivate: () => true }) + .compile(); - const isolatedApp = moduleFixture.createNestApplication(); + const isolatedApp = moduleFixture.createNestApplication(); + + try { await isolatedApp.init(); const localReadAllUseCase = moduleFixture.get(ReadAllTicketUseCase); @@ -635,7 +637,7 @@ describe('TicketController', () => { categories: categories, role: UserRole.SUPPORT, }); - + } finally { await isolatedApp.close(); - }); + } }); From b08f45470ae22765c705a5474e567d9f8584696e Mon Sep 17 00:00:00 2001 From: Vinicius Elias <95414991+ViniElias@users.noreply.github.com> Date: Wed, 13 May 2026 22:10:09 -0300 Subject: [PATCH 3/4] Fix: fechamento incorreto de chaves --- .../ticket/presentation/controllers/ticket.controller.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts index 54d09a8..6ce7d2b 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts @@ -641,3 +641,4 @@ it('GET /tickets should return tickets filtered by agentId when role is SUPPORT' await isolatedApp.close(); } }); +}); From 0397a85a96e778f7a284bb702bff58e9cbc90686 Mon Sep 17 00:00:00 2001 From: Vinicius Elias <95414991+ViniElias@users.noreply.github.com> Date: Wed, 13 May 2026 22:22:43 -0300 Subject: [PATCH 4/4] Fix: expect com assinatura antiga --- .../controllers/ticket.controller.spec.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts index 6ce7d2b..93279ca 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts @@ -602,8 +602,18 @@ it('GET /tickets should return tickets filtered by agentId when role is SUPPORT' const isolatedApp = moduleFixture.createNestApplication(); try { + isolatedApp.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }), + ); + await isolatedApp.init(); + const isolatedHttpServer = isolatedApp.getHttpServer() as import('http').Server; + const localReadAllUseCase = moduleFixture.get(ReadAllTicketUseCase); jest.spyOn(localReadAllUseCase, 'execute').mockResolvedValue([ @@ -623,7 +633,7 @@ it('GET /tickets should return tickets filtered by agentId when role is SUPPORT' }, ]); - const response = await request(isolatedApp.getHttpServer()) + const response = await request(isolatedHttpServer) .get('/tickets') .expect(200); @@ -636,6 +646,10 @@ it('GET /tickets should return tickets filtered by agentId when role is SUPPORT' userId: agentId, categories: categories, role: UserRole.SUPPORT, + search: undefined, + status: undefined, + escalationLevel: undefined, + onlyMine: false }); } finally { await isolatedApp.close();