2025-11-12 22:42:15 +01:00
|
|
|
const http = require('http');
|
|
|
|
|
const { Server } = require('socket.io');
|
|
|
|
|
const Client = require('socket.io-client');
|
|
|
|
|
const { initializeSocket } = require('../socket');
|
|
|
|
|
const { generateToken } = require('../utils/auth');
|
|
|
|
|
const { prisma } = require('../utils/db');
|
|
|
|
|
|
|
|
|
|
describe('Socket.IO Server', () => {
|
|
|
|
|
let httpServer;
|
|
|
|
|
let io;
|
|
|
|
|
let serverSocket;
|
|
|
|
|
let clientSocket;
|
|
|
|
|
let testUser;
|
|
|
|
|
let testToken;
|
|
|
|
|
const port = 3001;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
2025-11-19 21:46:04 +01:00
|
|
|
// Clean up existing test user if any
|
|
|
|
|
await prisma.user.deleteMany({
|
|
|
|
|
where: {
|
|
|
|
|
OR: [
|
|
|
|
|
{ username: 'sockettest' },
|
|
|
|
|
{ email: 'sockettest@example.com' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-12 22:42:15 +01:00
|
|
|
// Create test user
|
|
|
|
|
testUser = await prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
username: 'sockettest',
|
|
|
|
|
email: 'sockettest@example.com',
|
|
|
|
|
passwordHash: 'hashedpassword',
|
|
|
|
|
avatar: 'https://example.com/avatar.jpg',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testToken = generateToken({ userId: testUser.id });
|
|
|
|
|
|
|
|
|
|
// Create HTTP server and initialize Socket.IO
|
|
|
|
|
httpServer = http.createServer();
|
|
|
|
|
io = initializeSocket(httpServer);
|
|
|
|
|
|
|
|
|
|
httpServer.listen(port);
|
|
|
|
|
|
|
|
|
|
// Wait for server to be ready
|
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
|
httpServer.once('listening', resolve);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
// Cleanup test user
|
|
|
|
|
await prisma.user.delete({
|
|
|
|
|
where: { id: testUser.id },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Close server and client
|
|
|
|
|
if (clientSocket) clientSocket.close();
|
|
|
|
|
io.close();
|
|
|
|
|
httpServer.close();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
if (clientSocket && clientSocket.connected) {
|
|
|
|
|
clientSocket.close();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Authentication', () => {
|
|
|
|
|
test('should reject connection without token', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`);
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect_error', (error) => {
|
|
|
|
|
expect(error.message).toBe('Authentication required');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
done(new Error('Should not connect without token'));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should reject connection with invalid token', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: 'invalid-token' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect_error', (error) => {
|
|
|
|
|
expect(error.message).toBe('Invalid token');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should accept connection with valid token', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
expect(clientSocket.connected).toBe(true);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect_error', (error) => {
|
|
|
|
|
done(error);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Event Rooms', () => {
|
|
|
|
|
let testEvent;
|
|
|
|
|
let testChatRoom;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
// Create test event and chat room
|
|
|
|
|
testEvent = await prisma.event.create({
|
|
|
|
|
data: {
|
|
|
|
|
name: 'Test Event',
|
|
|
|
|
location: 'Test Location',
|
|
|
|
|
startDate: new Date('2025-12-01'),
|
|
|
|
|
endDate: new Date('2025-12-03'),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testChatRoom = await prisma.chatRoom.create({
|
|
|
|
|
data: {
|
|
|
|
|
eventId: testEvent.id,
|
|
|
|
|
type: 'event',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
// Cleanup test data
|
|
|
|
|
await prisma.chatRoom.delete({
|
|
|
|
|
where: { id: testChatRoom.id },
|
|
|
|
|
});
|
|
|
|
|
await prisma.event.delete({
|
|
|
|
|
where: { id: testEvent.id },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should join event room successfully', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('active_users', (users) => {
|
|
|
|
|
expect(Array.isArray(users)).toBe(true);
|
|
|
|
|
expect(users.length).toBeGreaterThan(0);
|
|
|
|
|
const currentUser = users.find(u => u.userId === testUser.id);
|
|
|
|
|
expect(currentUser).toBeDefined();
|
|
|
|
|
expect(currentUser.username).toBe(testUser.username);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('error', (error) => {
|
|
|
|
|
done(error);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should receive user_joined notification', (done) => {
|
|
|
|
|
const client1 = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client1.on('connect', () => {
|
|
|
|
|
client1.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
|
|
|
|
|
// Create second user and join the same room
|
|
|
|
|
const client2Token = generateToken({ userId: testUser.id });
|
|
|
|
|
const client2 = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: client2Token },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client1.on('user_joined', (userData) => {
|
|
|
|
|
expect(userData.userId).toBe(testUser.id);
|
|
|
|
|
expect(userData.username).toBe(testUser.username);
|
|
|
|
|
client1.close();
|
|
|
|
|
client2.close();
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client2.on('connect', () => {
|
|
|
|
|
client2.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should send and receive event messages', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const messageContent = 'Test message content';
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
|
|
|
|
|
clientSocket.on('active_users', () => {
|
|
|
|
|
// Wait for active_users, then send message
|
|
|
|
|
clientSocket.emit('send_event_message', {
|
|
|
|
|
eventId: testEvent.id,
|
|
|
|
|
content: messageContent,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('event_message', async (message) => {
|
|
|
|
|
expect(message.content).toBe(messageContent);
|
|
|
|
|
expect(message.userId).toBe(testUser.id);
|
|
|
|
|
expect(message.username).toBe(testUser.username);
|
|
|
|
|
expect(message.type).toBe('text');
|
|
|
|
|
expect(message.createdAt).toBeDefined();
|
|
|
|
|
|
|
|
|
|
// Verify message was saved to database
|
|
|
|
|
const dbMessage = await prisma.message.findUnique({
|
|
|
|
|
where: { id: message.id },
|
|
|
|
|
});
|
|
|
|
|
expect(dbMessage).toBeDefined();
|
|
|
|
|
expect(dbMessage.content).toBe(messageContent);
|
|
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
|
await prisma.message.delete({ where: { id: message.id } });
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('error', (error) => {
|
|
|
|
|
done(error);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should leave event room and update active users', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
|
|
|
|
|
clientSocket.on('active_users', (users) => {
|
|
|
|
|
if (users.length > 0) {
|
|
|
|
|
// User joined, now leave
|
|
|
|
|
clientSocket.emit('leave_event_room', { eventId: testEvent.id });
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
done();
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Match Rooms', () => {
|
|
|
|
|
let testUser2;
|
|
|
|
|
let testMatch;
|
|
|
|
|
let testMatchRoom;
|
|
|
|
|
let testEvent;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
// Create test event for match
|
|
|
|
|
testEvent = await prisma.event.create({
|
|
|
|
|
data: {
|
|
|
|
|
name: 'Match Test Event',
|
|
|
|
|
location: 'Test Location',
|
|
|
|
|
startDate: new Date('2025-12-01'),
|
|
|
|
|
endDate: new Date('2025-12-03'),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create second user with timestamp to avoid conflicts
|
|
|
|
|
const timestamp = Date.now();
|
|
|
|
|
testUser2 = await prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
username: `sockettest2_${timestamp}`,
|
|
|
|
|
email: `sockettest2_${timestamp}@example.com`,
|
|
|
|
|
passwordHash: 'hashedpassword',
|
|
|
|
|
avatar: 'https://example.com/avatar2.jpg',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create match
|
|
|
|
|
testMatch = await prisma.match.create({
|
|
|
|
|
data: {
|
|
|
|
|
status: 'active',
|
|
|
|
|
user1: { connect: { id: testUser.id } },
|
|
|
|
|
user2: { connect: { id: testUser2.id } },
|
|
|
|
|
event: { connect: { id: testEvent.id } },
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create match chat room
|
|
|
|
|
testMatchRoom = await prisma.chatRoom.create({
|
|
|
|
|
data: {
|
|
|
|
|
type: 'private',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Link room to match
|
|
|
|
|
await prisma.match.update({
|
|
|
|
|
where: { id: testMatch.id },
|
|
|
|
|
data: { roomId: testMatchRoom.id },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
// Cleanup
|
|
|
|
|
await prisma.match.delete({
|
|
|
|
|
where: { id: testMatch.id },
|
|
|
|
|
});
|
|
|
|
|
await prisma.chatRoom.delete({
|
|
|
|
|
where: { id: testMatchRoom.id },
|
|
|
|
|
});
|
|
|
|
|
await prisma.user.delete({
|
|
|
|
|
where: { id: testUser2.id },
|
|
|
|
|
});
|
|
|
|
|
await prisma.event.delete({
|
|
|
|
|
where: { id: testEvent.id },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should join match room successfully', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_match_room', { matchId: testMatch.id });
|
|
|
|
|
// Just verify no error occurs
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
expect(clientSocket.connected).toBe(true);
|
|
|
|
|
done();
|
|
|
|
|
}, 100);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('error', (error) => {
|
|
|
|
|
done(error);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should send and receive match messages', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const messageContent = 'Private match message';
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_match_room', { matchId: testMatch.id });
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
clientSocket.emit('send_match_message', {
|
|
|
|
|
matchId: testMatch.id,
|
|
|
|
|
content: messageContent,
|
|
|
|
|
});
|
|
|
|
|
}, 100);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('match_message', async (message) => {
|
|
|
|
|
expect(message.content).toBe(messageContent);
|
|
|
|
|
expect(message.userId).toBe(testUser.id);
|
|
|
|
|
expect(message.username).toBe(testUser.username);
|
|
|
|
|
expect(message.type).toBe('text');
|
|
|
|
|
|
|
|
|
|
// Verify message was saved
|
|
|
|
|
const dbMessage = await prisma.message.findUnique({
|
|
|
|
|
where: { id: message.id },
|
|
|
|
|
});
|
|
|
|
|
expect(dbMessage).toBeDefined();
|
|
|
|
|
expect(dbMessage.roomId).toBe(testMatchRoom.id);
|
|
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
|
await prisma.message.delete({ where: { id: message.id } });
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('error', (error) => {
|
|
|
|
|
done(error);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should handle match room not found error', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_match_room', { matchId: 99999 });
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
clientSocket.emit('send_match_message', {
|
|
|
|
|
matchId: 99999,
|
|
|
|
|
content: 'Test',
|
|
|
|
|
});
|
|
|
|
|
}, 100);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('error', (error) => {
|
|
|
|
|
expect(error.message).toBe('Match room not found');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Disconnect Handling', () => {
|
|
|
|
|
let testEvent;
|
|
|
|
|
let testChatRoom;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
testEvent = await prisma.event.create({
|
|
|
|
|
data: {
|
|
|
|
|
name: 'Disconnect Test Event',
|
|
|
|
|
location: 'Test Location',
|
|
|
|
|
startDate: new Date('2025-12-01'),
|
|
|
|
|
endDate: new Date('2025-12-03'),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testChatRoom = await prisma.chatRoom.create({
|
|
|
|
|
data: {
|
|
|
|
|
eventId: testEvent.id,
|
|
|
|
|
type: 'event',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
await prisma.chatRoom.delete({
|
|
|
|
|
where: { id: testChatRoom.id },
|
|
|
|
|
});
|
|
|
|
|
await prisma.event.delete({
|
|
|
|
|
where: { id: testEvent.id },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should handle disconnect and update active users', (done) => {
|
|
|
|
|
const client1 = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const client2Token = generateToken({ userId: testUser.id });
|
|
|
|
|
const client2 = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: client2Token },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let client1Connected = false;
|
|
|
|
|
let client2Connected = false;
|
|
|
|
|
|
|
|
|
|
client1.on('connect', () => {
|
|
|
|
|
client1Connected = true;
|
|
|
|
|
client1.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client2.on('connect', () => {
|
|
|
|
|
client2Connected = true;
|
|
|
|
|
client2.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client2.on('user_left', (userData) => {
|
|
|
|
|
expect(userData.userId).toBe(testUser.id);
|
|
|
|
|
expect(userData.username).toBe(testUser.username);
|
|
|
|
|
client2.close();
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Wait for both to join, then disconnect client1
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (client1Connected && client2Connected) {
|
|
|
|
|
client1.close();
|
|
|
|
|
}
|
|
|
|
|
}, 500);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Error Handling', () => {
|
|
|
|
|
let testEvent;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
testEvent = await prisma.event.create({
|
|
|
|
|
data: {
|
|
|
|
|
name: 'Error Test Event',
|
|
|
|
|
location: 'Test Location',
|
|
|
|
|
startDate: new Date('2025-12-01'),
|
|
|
|
|
endDate: new Date('2025-12-03'),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
await prisma.event.delete({
|
|
|
|
|
where: { id: testEvent.id },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('should handle chat room not found error', (done) => {
|
|
|
|
|
clientSocket = Client(`http://localhost:${port}`, {
|
|
|
|
|
auth: { token: testToken },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('connect', () => {
|
|
|
|
|
clientSocket.emit('join_event_room', { eventId: testEvent.id });
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
clientSocket.emit('send_event_message', {
|
|
|
|
|
eventId: testEvent.id,
|
|
|
|
|
content: 'Test message',
|
|
|
|
|
});
|
|
|
|
|
}, 100);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientSocket.on('error', (error) => {
|
|
|
|
|
expect(error.message).toBe('Chat room not found');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|