Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export class ReadAllTicketUseCase {
userId: string;
categories?: string[];
role: UserRole;
search?: string;
status?: TicketStatus;
escalationLevel?: number;
onlyMine?: boolean;
}): Promise<ReadAllTicketOutput[]> {
const filters =
input.role === UserRole.CLIENT
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -9,6 +9,10 @@ export abstract class ITicketRepository {
clientId?: string;
agentId?: string;
categories?: string[];
search?: string;
status?: TicketStatus;
escalationLevel?: number;
onlyMine?: boolean;
}): Promise<Ticket[]>;
abstract readById(id: string): Promise<Ticket | null>;
abstract delete(id: string): Promise<boolean>;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -42,6 +42,10 @@ export class TicketMongoRepository extends ITicketRepository {
clientId?: string;
agentId?: string;
categories?: string[];
search?: string;
status?: TicketStatus;
escalationLevel?: number;
onlyMine?: boolean;
}): Promise<Ticket[]> {
let query: QueryFilter<TicketSchemaClass> = {};

Expand All @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,44 +564,56 @@ 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();

const isolatedApp = moduleFixture.createNestApplication();
.overrideGuard(RolesGuard)
.useValue({ canActivate: () => true })
.compile();

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([
Expand All @@ -621,7 +633,7 @@ describe('TicketController', () => {
},
]);

const response = await request(isolatedApp.getHttpServer())
const response = await request(isolatedHttpServer)
.get('/tickets')
.expect(200);

Expand All @@ -634,8 +646,13 @@ describe('TicketController', () => {
userId: agentId,
categories: categories,
role: UserRole.SUPPORT,
search: undefined,
status: undefined,
escalationLevel: undefined,
onlyMine: false
});

} finally {
await isolatedApp.close();
});
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading