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: `507f1f77bcf86cd799439011`, _rev: '1-test', type: 'report', ...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(), })); // Mock upload middleware jest.mock('../../middleware/upload', () => ({ upload: { single: () => (req, res, next) => { req.file = null; // No file by default next(); } }, handleUploadError: (req, res, next) => next() })); // Mock cloudinary jest.mock('../../config/cloudinary', () => ({ uploadImage: jest.fn().mockResolvedValue({ url: 'http://example.com/image.jpg', publicId: 'test_public_id' }), deleteImage: jest.fn().mockResolvedValue(true) })); const reportsRoutes = require('../../routes/reports'); const Report = require('../../models/Report'); const User = require('../../models/User'); const Street = require('../../models/Street'); const { createTestUser, createTestStreet, createTestReport } = require('../utils/testHelpers'); const couchdbService = require('../../services/couchdbService'); // Create Express app for testing const app = express(); app.use(express.json()); app.use('/api/reports', reportsRoutes); describe('Reports Routes', () => { beforeEach(() => { jest.clearAllMocks(); // Mock Report model methods Report.findWithPagination = jest.fn().mockResolvedValue({ docs: [], totalDocs: 0, page: 1, totalPages: 0, hasNextPage: false, hasPrevPage: false }); Report.findById = jest.fn().mockResolvedValue(null); Report.create = jest.fn().mockImplementation((data) => Promise.resolve({ _id: '507f1f77bcf86cd799439011', _rev: '1-test', type: 'report', ...data, status: 'pending', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' })); Report.update = jest.fn().mockImplementation((id, data) => Promise.resolve({ _id: id, _rev: '2-test', ...data })); // Mock User model methods User.findById = jest.fn().mockResolvedValue({ _id: '507f1f77bcf86cd799439011', name: 'Test User', profilePicture: '', stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 } }); // Mock Street model methods Street.findById = jest.fn().mockResolvedValue({ _id: '507f1f77bcf86cd799439012', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610] } }); // Mock couchdbService methods couchdbService.find.mockResolvedValue([]); couchdbService.getDocument.mockResolvedValue(null); }); describe('GET /api/reports', () => { it('should get all reports', async () => { const { user } = await createTestUser(); const street = await createTestStreet(user._id); const report1 = await createTestReport(user._id, street._id, { type: 'pothole' }); const report2 = await createTestReport(user._id, street._id, { type: 'litter' }); // Mock Report.findWithPagination to return test reports Report.findWithPagination.mockResolvedValue({ docs: [report1, report2], totalDocs: 2, page: 1, totalPages: 1, hasNextPage: false, hasPrevPage: false }); const response = await request(app) .get('/api/reports') .expect(200); expect(Array.isArray(response.body.reports)).toBe(true); expect(response.body.reports.length).toBe(2); expect(response.body.reports[0]).toHaveProperty('type'); expect(response.body.reports[0]).toHaveProperty('description'); }); it('should return empty array when no reports exist', async () => { // Mock Report.findWithPagination to return empty array Report.findWithPagination.mockResolvedValue({ docs: [], totalDocs: 0, page: 1, totalPages: 0, hasNextPage: false, hasPrevPage: false }); const response = await request(app) .get('/api/reports') .expect(200); expect(Array.isArray(response.body.reports)).toBe(true); expect(response.body.reports.length).toBe(0); }); it('should populate street and user data', async () => { const { user } = await createTestUser(); const street = await createTestStreet(user._id); const report = await createTestReport(user._id, street._id, { type: 'pothole' }); // Mock Report.findWithPagination to return report with populated data Report.findWithPagination.mockResolvedValue({ docs: [{ ...report, street: { _id: street._id, name: street.name }, user: { _id: user._id, name: user.name, profilePicture: user.profilePicture } }], totalDocs: 1, page: 1, totalPages: 1, hasNextPage: false, hasPrevPage: false }); const response = await request(app) .get('/api/reports') .expect(200); expect(response.body.reports[0]).toHaveProperty('street'); expect(response.body.reports[0]).toHaveProperty('user'); }); }); describe('POST /api/reports', () => { it('should create a new report with authentication', async () => { const { user, token } = await createTestUser(); const street = await createTestStreet(user._id); // Use valid MongoDB ObjectId for street ID const streetId = '507f1f77bcf86cd799439012'; const reportData = { street: streetId, issue: 'Large pothole on main street', }; // Mock Street.findById to return street Street.findById.mockResolvedValue({ _id: streetId, name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610] } }); // Mock Report.create to return created report const createdReport = { _id: '507f1f77bcf86cd799439013', _rev: '1-test', type: 'report', street: { _id: streetId, name: 'Test Street' }, user: { _id: user._id, name: user.name, profilePicture: user.profilePicture }, issue: reportData.issue, status: 'pending', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; Report.create.mockResolvedValue(createdReport); const response = await request(app) .post('/api/reports') .set('x-auth-token', token) .send(reportData) .expect(200); expect(response.body).toHaveProperty('_id'); expect(response.body.issue).toBe(reportData.issue); }); it('should reject report creation without authentication', async () => { const reportData = { street: '507f1f77bcf86cd799439012', issue: 'Unauthorized report', }; const response = await request(app) .post('/api/reports') .send(reportData) .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/reports') .set('x-auth-token', token) .send({ issue: 'Incomplete report' }) .expect(400); expect(response.body).toHaveProperty('success', false); }); }); describe('PUT /api/reports/:id', () => { it('should resolve a report with authentication', async () => { const { user, token } = await createTestUser(); const street = await createTestStreet(user._id); const report = await createTestReport(user._id, street._id); // Set report ID to MongoDB format report.id = '507f1f77bcf86cd799439014'; report._id = '507f1f77bcf86cd799439014'; // Mock Report.findById to return test report Report.findById.mockResolvedValue(report); // Mock Report.update to return updated report const updatedReport = { ...report, status: 'resolved' }; Report.update.mockResolvedValue(updatedReport); const response = await request(app) .put(`/api/reports/${report.id}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('status', 'resolved'); }); it('should return 404 for non-existent report', async () => { const { token } = await createTestUser(); const fakeId = '507f1f77bcf86cd799439999'; // Mock Report.findById to return null (not found) Report.findById.mockResolvedValue(null); const response = await request(app) .put(`/api/reports/${fakeId}`) .set('x-auth-token', token) .expect(404); expect(response.body).toHaveProperty('msg', 'Report not found'); }); it('should reject resolution without authentication', async () => { const { user } = await createTestUser(); const street = await createTestStreet(user._id); const report = await createTestReport(user._id, street._id); report.id = '507f1f77bcf86cd799439015'; const response = await request(app) .put(`/api/reports/${report.id}`) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); it('should handle invalid report ID format', async () => { const { token } = await createTestUser(); const response = await request(app) .put('/api/reports/invalid-id') .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('success', false); expect(response.body.errors).toBeDefined(); }); it('should allow resolving already resolved reports', async () => { const { user, token } = await createTestUser(); const street = await createTestStreet(user._id); const report = await createTestReport(user._id, street._id, { status: 'resolved' }); report.id = '507f1f77bcf86cd799439016'; report._id = '507f1f77bcf86cd799439016'; // Mock Report.findById to return already resolved report Report.findById.mockResolvedValue(report); // Mock Report.update to return updated report const updatedReport = { ...report, status: 'resolved' }; Report.update.mockResolvedValue(updatedReport); const response = await request(app) .put(`/api/reports/${report.id}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('status', 'resolved'); }); }); });