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
33 changes: 33 additions & 0 deletions backend/src/modules/chat/application/chat.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ describe('ChatService', () => {
expect(mockMessageRepository.create).toHaveBeenCalledTimes(1);
});

it('should throw ForbiddenException when SUPPORT tries to send message and is not a participant', async () => {
mockChatRepository.findById.mockResolvedValue(mockChat);

await expect(
service.sendMessage(CHAT_ID, OUTSIDER_ID, UserRole.SUPPORT, 'Intruso suporte!'),
).rejects.toThrow(ForbiddenException);

expect(mockMessageRepository.create).not.toHaveBeenCalled();
});

it('should throw ForbiddenException when sender is not a participant', async () => {
mockChatRepository.findById.mockResolvedValue(mockChat);

Expand Down Expand Up @@ -211,6 +221,29 @@ describe('ChatService', () => {
expect(mockMessageRepository.findByChatId).toHaveBeenCalledWith(CHAT_ID);
});

it('should allow ADMIN to view history even if not a participant', async () => {
mockChatRepository.findById.mockResolvedValue(mockChat);
mockMessageRepository.findByChatId.mockResolvedValue(mockMessages as any);

const result = await service.getChatHistory(
CHAT_ID,
OUTSIDER_ID,
UserRole.ADMIN,
);

expect(result).toEqual(mockMessages);
});

it('should throw ForbiddenException when SUPPORT tries to view history and is not a participant', async () => {
mockChatRepository.findById.mockResolvedValue(mockChat);

await expect(
service.getChatHistory(CHAT_ID, OUTSIDER_ID, UserRole.SUPPORT),
).rejects.toThrow(ForbiddenException);

expect(mockMessageRepository.findByChatId).not.toHaveBeenCalled();
});

it('should throw ForbiddenException when user is not a participant', async () => {
mockChatRepository.findById.mockResolvedValue(mockChat);

Expand Down
6 changes: 4 additions & 2 deletions backend/src/modules/chat/application/chat.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export class ChatService {
throw new NotFoundException('Chat not found');
}

if (senderRole !== UserRole.ADMIN && senderRole !== UserRole.SUPPORT) {
// ADMINs podem enviar mensagem em qualquer chat
if (senderRole !== UserRole.ADMIN) {
if (chat.clientId !== senderId && chat.agentId !== senderId) {
throw new ForbiddenException('You are not a participant of this chat');
}
Expand Down Expand Up @@ -83,7 +84,8 @@ export class ChatService {
throw new NotFoundException('Chat not found');
}

if (userRole !== UserRole.ADMIN && userRole !== UserRole.SUPPORT) {
// ADMINs podem ver histórico de qualquer chat
if (userRole !== UserRole.ADMIN) {
if (chat.clientId !== userId && chat.agentId !== userId) {
throw new ForbiddenException('You are not a participant of this chat');
}
Expand Down
25 changes: 25 additions & 0 deletions backend/src/modules/chat/presentation/chat.gateway.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,31 @@ describe('ChatGateway', () => {
mensagem: 'Você não é participante deste chat',
});
});

it('should emit error when non-participant support tries to join', async () => {
const client = createMockSocket();
client.data.user = { id: OUTSIDER_ID, email: 'suporte@e.com', role: 'support' };
mockChatService.isParticipant.mockResolvedValue(false);

await gateway.handleEntrarChat({ chatId: CHAT_ID }, client);

expect(client.join).not.toHaveBeenCalled();
expect(client.emit).toHaveBeenCalledWith('erro', {
mensagem: 'Você não é participante deste chat',
});
});

it('should emit error when user is not authenticated', async () => {
const client = createMockSocket();
// client.data.user is undefined

await gateway.handleEntrarChat({ chatId: CHAT_ID }, client);

expect(client.join).not.toHaveBeenCalled();
expect(client.emit).toHaveBeenCalledWith('erro', {
mensagem: 'Usuário não autenticado',
});
});
});

describe('enviarMensagem', () => {
Expand Down
6 changes: 4 additions & 2 deletions backend/src/modules/chat/presentation/chat.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {

const isParticipant = await this.chatService.isParticipant(data.chatId, user.id);

if (!isParticipant && user.role !== 'admin' && user.role !== 'support') {
client.emit('erro', { mensagem: 'Você não é participante deste chat' });
if (!isParticipant && user.role !== 'admin') {
client.emit('erro', {
mensagem: 'Você não é participante deste chat',
});
return;
}

Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

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

Loading