const request = require("supertest"); const socketIoClient = require("socket.io-client"); const jwt = require("jsonwebtoken"); const { createServer } = require("http"); const { Server } = require("socket.io"); // Create test server with Socket.IO const createTestServer = () => { const app = require("express")(); const server = createServer(app); const io = new Server(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); // Socket.IO authentication middleware io.use((socket, next) => { const token = socket.handshake.auth.token; if (!token) { return next(new Error("Authentication error")); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET || "test_secret"); socket.userId = decoded.user.id; next(); } catch (err) { next(new Error("Authentication error")); } }); io.on("connection", (socket) => { console.log("User connected:", socket.userId); // Join event rooms socket.on("joinEvent", (eventId) => { socket.join(`event_${eventId}`); socket.emit("joinedEvent", { eventId }); }); // Leave event rooms socket.on("leaveEvent", (eventId) => { socket.leave(`event_${eventId}`); socket.emit("leftEvent", { eventId }); }); // Handle event updates socket.on("eventUpdate", (data) => { socket.to(`event_${data.eventId}`).emit("eventUpdate", data); }); // Handle new posts socket.on("newPost", (data) => { socket.broadcast.emit("newPost", data); }); // Handle task updates socket.on("taskUpdate", (data) => { socket.broadcast.emit("taskUpdate", data); }); socket.on("disconnect", () => { console.log("User disconnected:", socket.userId); }); }); return { server, io }; }; describe("Socket.IO Real-time Features", () => { let server; let io; let clientSocket; let testUser; let authToken; beforeAll(async () => { // Create test server const testServer = createTestServer(); server = testServer.server; io = testServer.io; // Start server on random port await new Promise((resolve) => { server.listen(0, resolve); }); // Create mock test user testUser = { _id: "test_user_123", name: "Test User", email: "test@example.com" }; // Generate auth token authToken = jwt.sign( { user: { id: testUser._id } }, process.env.JWT_SECRET || "test_secret" ); }); afterAll(async () => { if (clientSocket) { clientSocket.disconnect(); } io.close(); server.close(); }); beforeEach((done) => { // Connect client socket with authentication clientSocket = socketIoClient(`http://localhost:${server.address().port}`, { auth: { token: authToken }, }); clientSocket.on("connect", () => { done(); }); clientSocket.on("connect_error", (err) => { done(err); }); }); afterEach(() => { if (clientSocket && clientSocket.connected) { clientSocket.disconnect(); } }); describe("Socket Authentication", () => { test("should connect with valid token", (done) => { expect(clientSocket.connected).toBe(true); done(); }); test("should reject connection with invalid token", (done) => { const invalidSocket = socketIoClient( `http://localhost:${server.address().port}`, { auth: { token: "invalid_token" }, } ); invalidSocket.on("connect_error", (err) => { expect(err.message).toBe("Authentication error"); invalidSocket.disconnect(); done(); }); }); test("should reject connection without token", (done) => { const noTokenSocket = socketIoClient( `http://localhost:${server.address().port}` ); noTokenSocket.on("connect_error", (err) => { expect(err.message).toBe("Authentication error"); noTokenSocket.disconnect(); done(); }); }); }); describe("Event Participation", () => { let testEvent; beforeEach(() => { testEvent = { _id: "test_event_123", title: "Test Event", description: "Test Description", date: new Date(Date.now() + 86400000), // Tomorrow location: "Test Location", participants: [], }; }); test("should join event room", (done) => { clientSocket.emit("joinEvent", testEvent._id); clientSocket.on("joinedEvent", (data) => { expect(data.eventId).toBe(testEvent._id); done(); }); }); test("should receive event updates in room", (done) => { clientSocket.emit("joinEvent", testEvent._id); // Create another client to send updates to the room const anotherClient = socketIoClient(`http://localhost:${server.address().port}`, { auth: { token: authToken }, }); anotherClient.on("connect", () => { // Listen for updates from first client clientSocket.on("eventUpdate", (data) => { expect(data.message).toBe("Event status updated to ongoing"); anotherClient.disconnect(); done(); }); // Join the same event room anotherClient.emit("joinEvent", testEvent._id); // Send update from second client (will be broadcast to room) setTimeout(() => { anotherClient.emit("eventUpdate", { eventId: testEvent._id, message: "Event status updated to ongoing", }); }, 100); }); }); test("should not receive updates for events not joined", (done) => { const anotherEventId = "another_event_456"; // Listen for updates (should not receive any) let updateReceived = false; clientSocket.on("eventUpdate", () => { updateReceived = true; }); // Send update for event not joined setTimeout(() => { clientSocket.emit("eventUpdate", { eventId: anotherEventId, message: "This should not be received", }); // Check after delay that no update was received setTimeout(() => { expect(updateReceived).toBe(false); done(); }, 100); }, 100); }); }); describe("Post Interactions", () => { let testPost; let testEvent; beforeEach(() => { testPost = { _id: "test_post_123", user: { userId: testUser._id, name: testUser.name, }, content: "Test post content", likes: [], commentsCount: 0, }; testEvent = { _id: "test_event_123", title: "Test Event", description: "Test Description", date: new Date(Date.now() + 86400000), location: "Test Location", participants: [], }; }); test("should broadcast new posts", (done) => { // Create another client to receive broadcasts const anotherClient = socketIoClient(`http://localhost:${server.address().port}`, { auth: { token: authToken }, }); anotherClient.on("connect", () => { // Listen for new posts anotherClient.on("newPost", (data) => { expect(data.content).toBe("Test broadcast post"); anotherClient.disconnect(); done(); }); // Send new post from first client clientSocket.emit("newPost", { content: "Test broadcast post", user: testUser }); }); }); test("should handle multiple event joins", (done) => { const testEvent2 = { _id: "test_event_456", title: "Another Event", description: "Another Description", date: new Date(Date.now() + 86400000), location: "Another Location", participants: [], }; let joinCount = 0; const checkJoins = () => { joinCount++; if (joinCount === 2) { done(); } }; clientSocket.on("joinedEvent", (data) => { checkJoins(); }); clientSocket.emit("joinEvent", testEvent._id); clientSocket.emit("joinEvent", testEvent2._id); }); }); describe("Connection Stability", () => { test("should handle disconnection gracefully", (done) => { // Simple test that disconnection doesn't throw errors expect(() => { clientSocket.disconnect(); }).not.toThrow(); setTimeout(() => { expect(clientSocket.connected).toBe(false); done(); }, 100); }); test("should maintain connection under load", async () => { const startTime = Date.now(); const messageCount = 50; // Reduced for test stability for (let i = 0; i < messageCount; i++) { await new Promise((resolve) => { clientSocket.emit("eventUpdate", { eventId: `test_event_${i}`, message: `Test message ${i}`, }); setTimeout(resolve, 10); }); } const endTime = Date.now(); const duration = endTime - startTime; // Should complete within reasonable time (less than 5 seconds) expect(duration).toBeLessThan(5000); expect(clientSocket.connected).toBe(true); }); }); describe("Concurrent Connections", () => { test("should handle multiple simultaneous connections", async () => { const clients = []; const connectionPromises = []; // Create 10 concurrent connections for (let i = 0; i < 10; i++) { const promise = new Promise((resolve) => { const client = socketIoClient( `http://localhost:${server.address().port}`, { auth: { token: authToken }, } ); client.on("connect", () => { clients.push(client); resolve(); }); client.on("connect_error", (err) => { resolve(err); }); }); connectionPromises.push(promise); } await Promise.all(connectionPromises); // All connections should succeed expect(clients.length).toBe(10); clients.forEach((client) => { expect(client.connected).toBe(true); }); // Clean up clients.forEach((client) => client.disconnect()); }); }); });