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 @@ -6,6 +6,8 @@ import { ChatType } from '../../../chat/enum/chatMessageType.enum';
import { MessageEventType } from '../../../chat/enum/messageEventType.enum';
import ChatModule from '../modules/chat.module';
import { ObjectId } from 'mongodb';
import { ChatEmotion } from '../../../chat/enum/chatEmotion.enum';
import { ChatResponseType } from '../../../chat/enum/chatResponseType.enum';

class TestBaseChatService extends BaseChatService {
public broadcast = jest.fn();
Expand Down Expand Up @@ -63,7 +65,9 @@ describe('BaseChatService.handleNewMessage() test suite', () => {
const message = new CreateChatMessageDto({
type: ChatType.GLOBAL,
sender_id: new ObjectId().toString(),
content: 'Hello there!',
content: ChatResponseType.ONLINE,
responseType: ChatResponseType.ONLINE,
emotion: ChatEmotion.JOY,
});
(chatService.createChatMessage as jest.Mock).mockResolvedValue([
message,
Expand Down
78 changes: 42 additions & 36 deletions src/__tests__/chat/chatGateway/chatGateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,16 @@ describe('ChatGateway user initialization', () => {
const client = createClient();
client.user = { playerId: 'player-id', clanId: 'clan-id' } as any;
clanChatService.handleNewClanMessage.mockResolvedValue([
{ clan_id: 'clan-id' },
{
clan_id: 'clan-id',
responseType: ChatResponseType.YES,
emotion: ChatEmotion.JOY,
},
null,
]);

await gateway.handleClanMessage(
{
content: 'Hello!',
responseType: ChatResponseType.YES,
emotion: ChatEmotion.JOY,
},
Expand Down Expand Up @@ -121,44 +124,47 @@ describe('ChatGateway user initialization', () => {
null,
]);

await gateway.handleGlobalMessage({ content: 'Hello global!' }, client);
await gateway.handleGlobalMessage(
{
responseType: ChatResponseType.ONLINE,
emotion: ChatEmotion.JOY,
},
client,
);

expect(emitterService.EmitNewDailyTaskEvent).not.toHaveBeenCalled();
});

it('emits daily task events with undefined responseType and emotion for plain chat messages', async () => {
const client = createClient();
client.user = { playerId: 'player-id', clanId: 'clan-id' } as any;
clanChatService.handleNewClanMessage.mockResolvedValue([
{ clan_id: 'clan-id' },
null,
]);

await gateway.handleClanMessage({ content: 'Just plain chat' }, client);
it.each([
[
'clan messages',
(client: WebSocketUser) =>
gateway.handleClanMessage(
{ responseType: ChatResponseType.YES, emotion: ChatEmotion.JOY },
client,
),
clanChatService.handleNewClanMessage,
],
[
'global messages',
(client: WebSocketUser) =>
gateway.handleGlobalMessage(
{ responseType: ChatResponseType.YES, emotion: ChatEmotion.JOY },
client,
),
globalChatService.handleNewGlobalMessage,
],
])(
'does not emit a second error when %s are rejected by their service',
async (_, handler, serviceMethod) => {
const client = createClient();
client.user = { playerId: 'player-id', clanId: 'clan-id' } as any;
serviceMethod.mockResolvedValue(undefined);

expect(emitterService.EmitNewDailyTaskEvent).toHaveBeenNthCalledWith(
1,
'player-id',
ServerTaskName.FORM_AN_INNER_CONNECTION,
true,
{
clanId: 'clan-id',
responseType: undefined,
emotion: undefined,
},
);
expect(emitterService.EmitNewDailyTaskEvent).toHaveBeenNthCalledWith(
2,
'player-id',
ServerTaskName.PLAY_WITH_EMOTIONS,
true,
{
clanId: 'clan-id',
responseType: undefined,
emotion: undefined,
},
);
});
await expect(handler(client)).resolves.toBeUndefined();
expect(emitterService.EmitNewDailyTaskEvent).not.toHaveBeenCalled();
},
);

it.each([
[
Expand Down Expand Up @@ -191,4 +197,4 @@ describe('ChatGateway user initialization', () => {
expect(globalChatService.handleNewGlobalReaction).not.toHaveBeenCalled();
},
);
});
});
20 changes: 18 additions & 2 deletions src/__tests__/chat/chatService/createChatMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { ChatService } from '../../../chat/service/chat.service';
import ChatModule from '../modules/chat.module';
import ChatBuilderFactory from '../data/chatBuilderFactory';
import { ObjectId } from 'mongodb';
import { CreateChatMessageDto } from '../../../chat/dto/createMessage.dto';
import { ChatType } from '../../../chat/enum/chatMessageType.enum';
import { ChatResponseType } from '../../../chat/enum/chatResponseType.enum';
import { ChatEmotion } from '../../../chat/enum/chatEmotion.enum';

describe('ChatService.createChatMessage() test suite', () => {
let chatService: ChatService;
Expand All @@ -15,7 +19,13 @@ describe('ChatService.createChatMessage() test suite', () => {

it('Should save chat message to DB if input is valid', async () => {
const senderId = new ObjectId();
const chatToCreate = chatMessageBuilder.setSenderId(senderId).build();
const chatToCreate = new CreateChatMessageDto({
type: ChatType.GLOBAL,
sender_id: senderId,
content: ChatResponseType.ONLINE,
responseType: ChatResponseType.ONLINE,
emotion: ChatEmotion.JOY,
});
await chatService.createChatMessage(chatToCreate);

const dbResp = await chatModel.find({ content: chatToCreate.content });
Expand All @@ -27,7 +37,13 @@ describe('ChatService.createChatMessage() test suite', () => {

it('Should return saved chat message data if input is valid', async () => {
const senderId = new ObjectId();
const chatToCreate = chatMessageBuilder.setSenderId(senderId).build();
const chatToCreate = new CreateChatMessageDto({
type: ChatType.GLOBAL,
sender_id: senderId,
content: ChatResponseType.YES,
responseType: ChatResponseType.YES,
emotion: ChatEmotion.BLANK,
});
const [result, errors] = await chatService.createChatMessage(chatToCreate);

expect(errors).toBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ describe('ClanChatService.handleNewClanMessage() test suite', () => {

it('should call handleNewMessage with correct parameters and broadcast to clan room', async () => {
const client = createClient('clanA', 'player123');
const clientProvidedContent =
'Client-provided free text must not be stored';
const message: WsMessageBodyDto = {
content: 'Hello clan!',
feeling: 'happy',
content: clientProvidedContent,
responseType: ChatResponseType.YES,
emotion: ChatEmotion.JOY,
} as any;
Expand All @@ -45,8 +46,8 @@ describe('ClanChatService.handleNewClanMessage() test suite', () => {
expect(chatMessage.type).toBe(ChatType.CLAN);
expect(chatMessage.clan_id).toBe('clanA');
expect(chatMessage.sender_id).toBe('player123');
expect(chatMessage.content).toBe('Hello clan!');
expect(chatMessage.feeling).toBe('happy');
expect(chatMessage.content).toBe(ChatResponseType.YES);
expect(chatMessage.content).not.toBe(clientProvidedContent);
expect(chatMessage.responseType).toBe(ChatResponseType.YES);
expect(chatMessage.emotion).toBe(ChatEmotion.JOY);
expect(calledClient).toBe(client);
Expand All @@ -57,9 +58,9 @@ describe('ClanChatService.handleNewClanMessage() test suite', () => {
it('should not throw if clan room does not exist', async () => {
const client = createClient('clanB', 'playerX');
const message: WsMessageBodyDto = {
content: 'No one here',
feeling: 'sad',
} as any;
responseType: ChatResponseType.NEED_COMPANY,
emotion: ChatEmotion.SORROW,
};

await expect(
clanChatService.handleNewClanMessage(client, message),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { WebSocketUser } from '../../../chat/types/WsUser.type';
import ChatModule from '../modules/chat.module';
import { WsMessageBodyDto } from '../../../chat/dto/wsMessageBody.dto';
import { ChatType } from '../../../chat/enum/chatMessageType.enum';
import { ChatEmotion } from '../../../chat/enum/chatEmotion.enum';
import { ChatResponseType } from '../../../chat/enum/chatResponseType.enum';

describe('GlobalChatService.handleNewGlobalMessage() test suite', () => {
let globalChatService: GlobalChatService;
Expand All @@ -26,9 +28,12 @@ describe('GlobalChatService.handleNewGlobalMessage() test suite', () => {

it('should call handleNewMessage with correct parameters and broadcast to all connected users', async () => {
const client = createClient('player123');
const clientProvidedContent =
'Client-provided free text must not be stored';
const message: WsMessageBodyDto = {
content: 'Hello world!',
feeling: 'excited',
content: clientProvidedContent,
responseType: ChatResponseType.ONLINE,
emotion: ChatEmotion.JOY,
} as any;
globalChatService.handleJoinChat(client);

Expand All @@ -39,8 +44,10 @@ describe('GlobalChatService.handleNewGlobalMessage() test suite', () => {
mockHandleNewMessage.mock.calls[0];
expect(chatMessage.type).toBe(ChatType.GLOBAL);
expect(chatMessage.sender_id).toBe('player123');
expect(chatMessage.content).toBe('Hello world!');
expect(chatMessage.feeling).toBe('excited');
expect(chatMessage.content).toBe(ChatResponseType.ONLINE);
expect(chatMessage.content).not.toBe(clientProvidedContent);
expect(chatMessage.responseType).toBe(ChatResponseType.ONLINE);
expect(chatMessage.emotion).toBe(ChatEmotion.JOY);
expect(calledClient).toBe(client);
expect(chatType).toBe(ChatType.GLOBAL);
expect(recipients.has(client)).toBe(true);
Expand All @@ -49,9 +56,9 @@ describe('GlobalChatService.handleNewGlobalMessage() test suite', () => {
it('should not throw if there are no connected users', async () => {
const client = createClient('playerX');
const message: WsMessageBodyDto = {
content: 'Anyone here?',
feeling: 'lonely',
} as any;
responseType: ChatResponseType.LONELY,
emotion: ChatEmotion.SORROW,
};

await expect(
globalChatService.handleNewGlobalMessage(message, client),
Expand Down
44 changes: 44 additions & 0 deletions src/__tests__/chat/wsExceptionFilter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BadRequestException } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { GlobalWsExceptionFilter } from '../../chat/decorator/wsExceptionFilter.decorator';

describe('GlobalWsExceptionFilter', () => {
const client = { send: jest.fn() };
const host = {
switchToWs: () => ({ getClient: () => client }),
} as any;

beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(console, 'error').mockImplementation();
});

afterEach(() => {
jest.restoreAllMocks();
});

it('returns the validation response for a bad WebSocket message', () => {
const validationError = {
statusCode: 400,
error: 'Bad Request',
errors: [{ field: 'emotion', message: 'emotion must be an enum value' }],
};

new GlobalWsExceptionFilter().catch(
new BadRequestException(validationError),
host,
);

expect(client.send).toHaveBeenCalledWith(
JSON.stringify({ event: 'error', data: validationError }),
);
});

it('preserves explicit WebSocket errors', () => {
new GlobalWsExceptionFilter().catch(new WsException('Invalid event'), host);

expect(client.send).toHaveBeenCalledWith(
JSON.stringify({ event: 'error', data: 'Invalid event' }),
);
});
});
28 changes: 28 additions & 0 deletions src/__tests__/chat/wsMessageBody.dto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { WsMessageBodyDto } from '../../chat/dto/wsMessageBody.dto';
import { ChatEmotion } from '../../chat/enum/chatEmotion.enum';
import { ChatResponseType } from '../../chat/enum/chatResponseType.enum';

describe('WsMessageBodyDto', () => {
const validateMessage = (message: object) =>
validate(plainToInstance(WsMessageBodyDto, message));

it('accepts a predefined response with an allowed emotion', async () => {
await expect(
validateMessage({
responseType: ChatResponseType.NEED_COMPANY,
emotion: ChatEmotion.BLANK,
}),
).resolves.toHaveLength(0);
});

it.each([
['a missing response type', { emotion: ChatEmotion.JOY }],
['an invalid response type', { responseType: 'FreeText', emotion: 1 }],
['a missing emotion', { responseType: ChatResponseType.YES }],
['an invalid emotion', { responseType: ChatResponseType.YES, emotion: 99 }],
])('rejects %s', async (_, message) => {
await expect(validateMessage(message)).resolves.not.toHaveLength(0);
});
});
Loading
Loading