feat: add comprehensive test coverage for advanced features
- Add Socket.IO real-time feature tests - Add geospatial query tests with CouchDB integration - Add gamification system tests (points, badges, leaderboard) - Add file upload tests with Cloudinary integration - Add comprehensive error handling tests - Add performance and stress tests - Add test documentation and coverage summary - Install missing testing dependencies (mongodb-memory-server, socket.io-client) Test Coverage: - Socket.IO: Authentication, events, rooms, concurrency - Geospatial: Nearby queries, bounding boxes, performance - Gamification: Points, badges, transactions, leaderboards - File Uploads: Profile pictures, posts, reports, validation - Error Handling: Auth, validation, database, rate limiting - Performance: Response times, concurrency, memory usage 🤖 Generated with AI Assistant Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
299
backend/__tests__/socketio.test.js
Normal file
299
backend/__tests__/socketio.test.js
Normal file
@@ -0,0 +1,299 @@
|
||||
const request = require("supertest");
|
||||
const mongoose = require("mongoose");
|
||||
const { MongoMemoryServer } = require("mongodb-memory-server");
|
||||
const socketIoClient = require("socket.io-client");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const app = require("../server");
|
||||
const User = require("../models/User");
|
||||
const Event = require("../models/Event");
|
||||
const Post = require("../models/Post");
|
||||
|
||||
describe("Socket.IO Real-time Features", () => {
|
||||
let mongoServer;
|
||||
let server;
|
||||
let io;
|
||||
let clientSocket;
|
||||
let testUser;
|
||||
let authToken;
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create();
|
||||
const mongoUri = mongoServer.getUri();
|
||||
await mongoose.connect(mongoUri);
|
||||
|
||||
// Start server
|
||||
server = app.listen(0); // Use random port
|
||||
io = app.get("io");
|
||||
|
||||
// Create test user
|
||||
testUser = new User({
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
await testUser.save();
|
||||
|
||||
// Generate auth token
|
||||
authToken = jwt.sign(
|
||||
{ user: { id: testUser._id.toString() } },
|
||||
process.env.JWT_SECRET || "test_secret"
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (clientSocket) {
|
||||
clientSocket.disconnect();
|
||||
}
|
||||
server.close();
|
||||
await mongoose.disconnect();
|
||||
await mongoServer.stop();
|
||||
});
|
||||
|
||||
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: Invalid token");
|
||||
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: No token provided");
|
||||
noTokenSocket.disconnect();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Event Participation", () => {
|
||||
let testEvent;
|
||||
|
||||
beforeEach(async () => {
|
||||
testEvent = new Event({
|
||||
title: "Test Event",
|
||||
description: "Test Description",
|
||||
date: new Date(Date.now() + 86400000), // Tomorrow
|
||||
location: "Test Location",
|
||||
participants: [],
|
||||
});
|
||||
await testEvent.save();
|
||||
});
|
||||
|
||||
test("should join event room", (done) => {
|
||||
clientSocket.emit("joinEvent", testEvent._id.toString());
|
||||
|
||||
// Verify socket joined room by checking server logs
|
||||
setTimeout(() => {
|
||||
// The socket should have joined the event room
|
||||
expect(clientSocket.rooms.has(`event_${testEvent._id}`)).toBe(true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
test("should receive event updates in room", (done) => {
|
||||
clientSocket.emit("joinEvent", testEvent._id.toString());
|
||||
|
||||
// Listen for updates
|
||||
clientSocket.on("update", (data) => {
|
||||
expect(data).toBe("Event status updated to ongoing");
|
||||
done();
|
||||
});
|
||||
|
||||
// Simulate event update
|
||||
setTimeout(() => {
|
||||
clientSocket.emit("eventUpdate", {
|
||||
eventId: testEvent._id.toString(),
|
||||
message: "Event status updated to ongoing",
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
test("should not receive updates for events not joined", (done) => {
|
||||
const anotherEventId = new mongoose.Types.ObjectId().toString();
|
||||
|
||||
// Listen for updates (should not receive any)
|
||||
let updateReceived = false;
|
||||
clientSocket.on("update", () => {
|
||||
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;
|
||||
|
||||
beforeEach(async () => {
|
||||
testPost = new Post({
|
||||
user: {
|
||||
userId: testUser._id,
|
||||
name: testUser.name,
|
||||
},
|
||||
content: "Test post content",
|
||||
likes: [],
|
||||
commentsCount: 0,
|
||||
});
|
||||
await testPost.save();
|
||||
});
|
||||
|
||||
test("should join post room", (done) => {
|
||||
clientSocket.emit("joinPost", testPost._id.toString());
|
||||
|
||||
setTimeout(() => {
|
||||
expect(clientSocket.rooms.has(`post_${testPost._id}`)).toBe(true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
test("should handle multiple room joins", (done) => {
|
||||
const testEvent = new Event({
|
||||
title: "Another Event",
|
||||
description: "Another Description",
|
||||
date: new Date(Date.now() + 86400000),
|
||||
location: "Another Location",
|
||||
participants: [],
|
||||
});
|
||||
testEvent.save().then(() => {
|
||||
clientSocket.emit("joinEvent", testEvent._id.toString());
|
||||
clientSocket.emit("joinPost", testPost._id.toString());
|
||||
|
||||
setTimeout(() => {
|
||||
expect(clientSocket.rooms.has(`event_${testEvent._id}`)).toBe(true);
|
||||
expect(clientSocket.rooms.has(`post_${testPost._id}`)).toBe(true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Connection Stability", () => {
|
||||
test("should handle disconnection gracefully", (done) => {
|
||||
const disconnectSpy = jest.spyOn(console, "log");
|
||||
|
||||
clientSocket.disconnect();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(disconnectSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Client disconnected:")
|
||||
);
|
||||
disconnectSpy.mockRestore();
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
test("should maintain connection under load", async () => {
|
||||
const startTime = Date.now();
|
||||
const messageCount = 100;
|
||||
|
||||
for (let i = 0; i < messageCount; i++) {
|
||||
await new Promise((resolve) => {
|
||||
clientSocket.emit("eventUpdate", {
|
||||
eventId: new mongoose.Types.ObjectId().toString(),
|
||||
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());
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user