diff --git a/backend/src/modules/chat/application/chat.service.spec.ts b/backend/src/modules/chat/application/chat.service.spec.ts index c393379..1a71ac2 100644 --- a/backend/src/modules/chat/application/chat.service.spec.ts +++ b/backend/src/modules/chat/application/chat.service.spec.ts @@ -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); @@ -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); diff --git a/backend/src/modules/chat/application/chat.service.ts b/backend/src/modules/chat/application/chat.service.ts index 62a7190..9d2c591 100644 --- a/backend/src/modules/chat/application/chat.service.ts +++ b/backend/src/modules/chat/application/chat.service.ts @@ -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'); } @@ -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'); } diff --git a/backend/src/modules/chat/presentation/chat.gateway.spec.ts b/backend/src/modules/chat/presentation/chat.gateway.spec.ts index 34daa79..26305e5 100644 --- a/backend/src/modules/chat/presentation/chat.gateway.spec.ts +++ b/backend/src/modules/chat/presentation/chat.gateway.spec.ts @@ -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', () => { diff --git a/backend/src/modules/chat/presentation/chat.gateway.ts b/backend/src/modules/chat/presentation/chat.gateway.ts index 2ac4606..e782664 100644 --- a/backend/src/modules/chat/presentation/chat.gateway.ts +++ b/backend/src/modules/chat/presentation/chat.gateway.ts @@ -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; } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6872ff4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ProDesk-backend", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}