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
20 changes: 20 additions & 0 deletions backend/package-lock.json

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

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.0.1",
"@nestjs/event-emitter": "^3.1.0",
"@nestjs/jwt": "^11.0.2",
"@nestjs/mongoose": "^11.0.4",
"@nestjs/passport": "^11.0.5",
Expand Down
13 changes: 9 additions & 4 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ import { FileModule } from './modules/file/file.module';
import { FaqModule } from './modules/faq/faq.module';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { NotificationModule } from './modules/notification/notification.module';
import { EventEmitterModule } from '@nestjs/event-emitter';


@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(process.cwd(), 'uploads'),
serveRoot: '/uploads',
rootPath: join(process.cwd(), 'uploads'),
serveRoot: '/uploads',
}),
ConfigModule.forRoot({
isGlobal: true,
Expand All @@ -37,9 +40,11 @@ import { join } from 'path';
TicketModule,
EmailModule,
FileModule,
FaqModule
FaqModule,
NotificationModule,
EventEmitterModule.forRoot(),
],
controllers: [],
providers: [],
})
export class AppModule {}
export class AppModule {}
31 changes: 29 additions & 2 deletions backend/src/modules/Messages/presentation/message.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { MessageRepositoryMongodb } from '../infra/message.repository.mongodb';
import { ReceivedMessageNotificationEvent } from '../../../shared/events/received-message-notification.event';
import { NotificationType } from '../../notification/shared/enums/notification.enum';
import { EventEmitter2 } from '@nestjs/event-emitter';

@WebSocketGateway({ cors: true })
export class MessageGateway
Expand All @@ -19,7 +22,9 @@ export class MessageGateway
server: Server;

// Injeta o repositório
constructor(private readonly messageRepository: MessageRepositoryMongodb) {}
constructor(private readonly messageRepository: MessageRepositoryMongodb,
private readonly eventEmitter:EventEmitter2,
) {}

// Método disparado quando um usuário conecta
handleConnection(client: Socket) {
Expand Down Expand Up @@ -70,7 +75,29 @@ export class MessageGateway
fileIds: data.fileIds || [],
});

console.log(`Mensagem salva no banco com ID: ${mensagemSalva.id}`);
this.eventEmitter.emit(
NotificationType.NEW_MESSAGE,

new ReceivedMessageNotificationEvent(

data.receiverId,

mensagemSalva.chatId,

mensagemSalva.id,

mensagemSalva.senderId,

data.senderName,

mensagemSalva.content,

mensagemSalva.type || 'TEXT',

new Date(),
),
);
console.log(`Mensagem salva no banco com ID: ${mensagemSalva.id}`);

// Emite a mensagem para os clientes dentro da sala do chamado
this.server.to(data.chatId).emit('novaMensagem', mensagemSalva);
Expand Down
79 changes: 69 additions & 10 deletions backend/src/modules/chat/application/chat.file-message.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { EventEmitter2 } from '@nestjs/event-emitter';

import { ChatService } from './chat.service';
import { TicketSchemaClass } from '../../ticket/infra/schemas/ticket.mongo.schema';
import { User } from '../../user/user.schema';

import { NotFoundException } from '@nestjs/common';

import type { IChatRepository } from '../domain/chat.repository';
import type { IMessageRepository } from '../../Messages/domain/message.repository';

import { UserRole } from '../../shared/enums/user.enum';

const mockChatRepository: jest.Mocked<IChatRepository> = {
Expand All @@ -22,6 +27,14 @@ const mockMessageRepository: jest.Mocked<IMessageRepository> = {
findByChatId: jest.fn(),
};

const mockEventEmitter = {
emit: jest.fn(),
};

const mockUserModel = {
findById: jest.fn(),
};

const CHAT_ID = 'chat-001';
const CLIENT_ID = 'user-001';

Expand All @@ -42,19 +55,47 @@ describe('ChatService — envio de mensagem com arquivo', () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ChatService,
{ provide: 'IChatRepository', useValue: mockChatRepository },
{ provide: 'IMessageRepository', useValue: mockMessageRepository },
{ provide: getModelToken(TicketSchemaClass.name), useValue: {} },
{ provide: getModelToken(UserRole.ADMIN ? 'User' : 'User'), useValue: {} },

{
provide: 'IChatRepository',
useValue: mockChatRepository,
},

{
provide: 'IMessageRepository',
useValue: mockMessageRepository,
},

{
provide: getModelToken(TicketSchemaClass.name),
useValue: {},
},

{
provide: getModelToken(User.name),
useValue: mockUserModel,
},

{
provide: EventEmitter2,
useValue: mockEventEmitter,
},
],
}).compile();

service = module.get<ChatService>(ChatService);

jest.clearAllMocks();

mockUserModel.findById.mockResolvedValue({
_id: CLIENT_ID,
name: 'Gabriel',
});
});

it('should send message with attached file', async () => {
const FILE_ID = 'file-001';

const mockSavedMessage = {
id: 'msg-001',
chatId: CHAT_ID,
Expand All @@ -66,7 +107,10 @@ describe('ChatService — envio de mensagem com arquivo', () => {
};

mockChatRepository.findById.mockResolvedValue(mockChat as any);
mockMessageRepository.create.mockResolvedValue(mockSavedMessage as any);

mockMessageRepository.create.mockResolvedValue(
mockSavedMessage as any,
);

const result = await service.sendMessage(
CHAT_ID,
Expand All @@ -77,7 +121,10 @@ describe('ChatService — envio de mensagem com arquivo', () => {
);

expect(result).toEqual(mockSavedMessage);
expect(mockMessageRepository.create).toHaveBeenCalledWith({

expect(
mockMessageRepository.create,
).toHaveBeenCalledWith({
chatId: CHAT_ID,
senderId: CLIENT_ID,
content: 'Arquivo anexado',
Expand All @@ -86,6 +133,8 @@ describe('ChatService — envio de mensagem com arquivo', () => {
attachmentUrl: undefined,
type: 'TEXT',
});

expect(mockEventEmitter.emit).toHaveBeenCalled();
});

it('should send message without fileIds', async () => {
Expand All @@ -99,8 +148,13 @@ describe('ChatService — envio de mensagem com arquivo', () => {
createdAt: new Date(),
};

mockChatRepository.findById.mockResolvedValue(mockChat as any);
mockMessageRepository.create.mockResolvedValue(mockSavedMessage as any);
mockChatRepository.findById.mockResolvedValue(
mockChat as any,
);

mockMessageRepository.create.mockResolvedValue(
mockSavedMessage as any,
);

const result = await service.sendMessage(
CHAT_ID,
Expand All @@ -110,7 +164,10 @@ describe('ChatService — envio de mensagem com arquivo', () => {
);

expect(result).toEqual(mockSavedMessage);
expect(mockMessageRepository.create).toHaveBeenCalledWith({

expect(
mockMessageRepository.create,
).toHaveBeenCalledWith({
chatId: CHAT_ID,
senderId: CLIENT_ID,
content: 'Mensagem normal',
Expand All @@ -119,6 +176,8 @@ describe('ChatService — envio de mensagem com arquivo', () => {
attachmentUrl: undefined,
type: 'TEXT',
});

expect(mockEventEmitter.emit).toHaveBeenCalled();
});

it('should throw NotFoundException when chat does not exist', async () => {
Expand All @@ -131,7 +190,7 @@ describe('ChatService — envio de mensagem com arquivo', () => {
UserRole.CLIENT,
'Teste',
['file-001'],
)
),
).rejects.toThrow(NotFoundException);
});
});
Loading
Loading