Files
adopt-a-street/backend/__tests__/socketio.test.js
William Valentin a0c863a972 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>
2025-11-01 16:17:28 -07:00

299 lines
8.0 KiB
JavaScript

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());
});
});
});