const request = require('supertest'); const express = require('express'); // Mock CouchDB service before importing routes jest.mock('../../services/couchdbService', () => ({ initialize: jest.fn().mockResolvedValue(true), create: jest.fn(), getById: jest.fn(), find: jest.fn(), createDocument: jest.fn().mockImplementation((doc) => Promise.resolve({ _id: `event_${Date.now()}`, _rev: '1-test', type: 'event', ...doc })), updateDocument: jest.fn().mockImplementation((id, doc) => Promise.resolve({ _id: id, _rev: '2-test', ...doc })), deleteDocument: jest.fn(), findByType: jest.fn().mockResolvedValue([]), findUserById: jest.fn(), findUserByEmail: jest.fn(), update: jest.fn(), getDocument: jest.fn(), updateUserPoints: jest.fn().mockResolvedValue({ _id: 'user_123', points: 15 }), checkAndAwardBadges: jest.fn().mockResolvedValue([]) })); const eventsRoutes = require('../../routes/events'); const Event = require('../../models/Event'); const User = require('../../models/User'); const { createTestUser, createTestEvent } = require('../utils/testHelpers'); const couchdbService = require('../../services/couchdbService'); // Create Express app for testing const app = express(); app.use(express.json()); app.use('/api/events', eventsRoutes); describe('Events Routes', () => { beforeEach(() => { jest.clearAllMocks(); // Mock Event model methods Event.getAllPaginated = jest.fn().mockResolvedValue({ events: [], pagination: { totalCount: 0 } }); Event.findById = jest.fn().mockResolvedValue(null); Event.create = jest.fn().mockImplementation((data) => Promise.resolve({ _id: `event_${Date.now()}`, _rev: '1-test', type: 'event', ...data })); Event.addParticipant = jest.fn().mockResolvedValue({ _id: 'event_123', participants: [] }); // Mock User model methods User.findById = jest.fn().mockResolvedValue({ _id: 'user_123', name: 'Test User', profilePicture: '', events: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 } }); User.update = jest.fn().mockResolvedValue(true); // Mock couchdbService methods couchdbService.find.mockResolvedValue([]); couchdbService.getDocument.mockResolvedValue(null); }); describe('GET /api/events', () => { it('should get all events', async () => { const { user } = await createTestUser(); const event1 = await createTestEvent(user.id, { title: 'Event 1' }); const event2 = await createTestEvent(user.id, { title: 'Event 2' }); // Mock Event.getAllPaginated to return test events Event.getAllPaginated.mockResolvedValue({ events: [event1, event2], pagination: { totalCount: 2 } }); const response = await request(app) .get('/api/events') .expect(200); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(2); expect(response.body.data[0]).toHaveProperty('title'); }); it('should return empty array when no events exist', async () => { // Mock Event.getAllPaginated to return empty array Event.getAllPaginated.mockResolvedValue({ events: [], pagination: { totalCount: 0 } }); const response = await request(app) .get('/api/events') .expect(200); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(0); }); }); describe('POST /api/events', () => { it('should create a new event with authentication', async () => { const { token } = await createTestUser(); const eventData = { title: 'Community Cleanup', description: 'Annual community cleanup event', date: new Date(Date.now() + 86400000), location: 'Central Park', }; // Mock Event.create to return created event const createdEvent = { _id: 'event_123', _rev: '1-test', type: 'event', ...eventData, participants: [], participantsCount: 0, status: 'upcoming', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; Event.create.mockResolvedValue(createdEvent); const response = await request(app) .post('/api/events') .set('x-auth-token', token) .send(eventData) .expect(200); expect(response.body).toHaveProperty('_id'); expect(response.body.title).toBe(eventData.title); expect(response.body.description).toBe(eventData.description); expect(response.body.location).toBe(eventData.location); }); it('should reject event creation without authentication', async () => { const eventData = { title: 'Unauthorized Event', description: 'This should fail', date: new Date(Date.now() + 86400000), location: 'Nowhere', }; const response = await request(app) .post('/api/events') .send(eventData) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); it('should handle missing required fields', async () => { const { token } = await createTestUser(); const response = await request(app) .post('/api/events') .set('x-auth-token', token) .send({ title: 'Incomplete Event' }) .expect(400); expect(response.body).toHaveProperty('success', false); }); }); describe('PUT /api/events/rsvp/:id', () => { it('should allow user to RSVP to an event', async () => { const { user, token } = await createTestUser(); const event = await createTestEvent(user.id); // Mock Event.findById to return test event Event.findById.mockResolvedValue(event); // Mock User.findById to return test user User.findById.mockResolvedValue({ _id: user.id, name: user.name, profilePicture: '', events: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 } }); // Mock Event.addParticipant to return updated event const updatedEvent = { ...event, participants: [{ userId: user.id, name: user.name, profilePicture: '' }] }; Event.addParticipant.mockResolvedValue(updatedEvent); const response = await request(app) .put(`/api/events/rsvp/${event.id}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('participants'); expect(response.body).toHaveProperty('pointsAwarded', 15); expect(response.body).toHaveProperty('newBalance', 15); }); it('should not allow duplicate RSVPs', async () => { const { user, token } = await createTestUser(); // Create event with user already participating const event = await createTestEvent(user._id); event.participants = [{ userId: user._id, // Use _id to match what JWT contains name: user.name, profilePicture: '' }]; // Mock Event.findById to return event with user already participating Event.findById.mockResolvedValue(event); const response = await request(app) .put(`/api/events/rsvp/${event.id}`) .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('msg', 'Already RSVPed'); }); it('should return 404 for non-existent event', async () => { const { token } = await createTestUser(); const fakeId = '507f1f77bcf86cd799439011'; // Mock Event.findById to return null (not found) Event.findById.mockResolvedValue(null); const response = await request(app) .put(`/api/events/rsvp/${fakeId}`) .set('x-auth-token', token) .expect(404); expect(response.body).toHaveProperty('msg', 'Event not found'); }); it('should reject RSVP without authentication', async () => { const { user } = await createTestUser(); const event = await createTestEvent(user.id); const response = await request(app) .put(`/api/events/rsvp/${event.id}`) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); it('should handle invalid event ID format', async () => { const { token } = await createTestUser(); const response = await request(app) .put('/api/events/rsvp/invalid-id') .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('success', false); expect(response.body.errors).toBeDefined(); }); }); });