const request = require('supertest'); const express = require('express'); // Mock Task model before importing routes jest.mock('../../models/Task', () => { const MockTask = jest.fn().mockImplementation((data) => ({ _id: data._id || '507f1f77bcf86cd799439013', _rev: data._rev || '1-abc', type: 'task', street: data.street, description: data.description, status: data.status || 'pending', completedBy: data.completedBy, completedAt: data.completedAt, pointsAwarded: data.pointsAwarded || 10, createdAt: data.createdAt || new Date().toISOString(), updatedAt: data.updatedAt || new Date().toISOString(), save: jest.fn().mockResolvedValue(this), populate: jest.fn().mockResolvedValue(this), toJSON: jest.fn().mockReturnValue(this), toObject: jest.fn().mockReturnValue(this), ...data })); MockTask.findById = jest.fn(); MockTask.find = jest.fn(() => ({ sort: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), limit: jest.fn().mockResolvedValue([]) })); MockTask.findByUser = jest.fn(); MockTask.create = jest.fn(); MockTask.findByIdAndUpdate = jest.fn(); MockTask.findByIdAndDelete = jest.fn(); MockTask.countDocuments = jest.fn(); return MockTask; }); // Mock User model jest.mock('../../models/User', () => ({ findById: jest.fn() })); // Mock Street model jest.mock('../../models/Street', () => ({ findById: jest.fn() })); const taskRoutes = require('../../routes/tasks'); const { createTestUser, createTestStreet, createTestTask } = require('../utils/testHelpers'); const couchdbService = require('../../services/couchdbService'); const Task = require('../../models/Task'); const User = require('../../models/User'); const Street = require('../../models/Street'); const app = express(); app.use(express.json()); app.use('/api/tasks', taskRoutes); describe('Task Routes', () => { beforeEach(() => { jest.clearAllMocks(); // Reset default implementations Task.findById.mockResolvedValue(null); Task.find.mockImplementation(() => ({ sort: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), limit: jest.fn().mockResolvedValue([]) })); Task.findByUser.mockResolvedValue([]); Task.countDocuments.mockResolvedValue(0); Task.create.mockImplementation((data) => Promise.resolve({ _id: '507f1f77bcf86cd799439013', type: 'task', street: data.street, description: data.description, status: 'pending', completedBy: null, completedAt: null, pointsAwarded: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() })); Task.findByIdAndUpdate.mockImplementation((id, data) => Promise.resolve({ _id: id, ...data })); User.findById.mockResolvedValue({ _id: '507f1f77bcf86cd799439099', name: 'Test User', profilePicture: '' }); Street.findById.mockResolvedValue(null); }); describe('GET /api/tasks', () => { it('should get all tasks completed by authenticated user', async () => { const { user, token } = await createTestUser(); const userId = user._id; const task1 = { _id: '507f1f77bcf86cd799439013', type: 'task', street: { streetId: '507f1f77bcf86cd799439011', name: 'Test Street', location: { lat: 0, lng: 0 } }, description: 'Clean sidewalk', completedBy: { userId: userId, name: user.name, profilePicture: '' }, status: 'completed', completedAt: new Date().toISOString(), pointsAwarded: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), populate: jest.fn().mockResolvedValue(this) }; const task2 = { _id: '507f1f77bcf86cd799439014', type: 'task', street: { streetId: '507f1f77bcf86cd799439012', name: 'Another Street', location: { lat: 1, lng: 1 } }, description: 'Pick up trash', completedBy: { userId: userId, name: user.name, profilePicture: '' }, status: 'completed', completedAt: new Date().toISOString(), pointsAwarded: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), populate: jest.fn().mockResolvedValue(this) }; const mockTasks = [task1, task2]; Task.find.mockImplementation(() => ({ sort: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), limit: jest.fn().mockResolvedValue(mockTasks) })); Task.countDocuments.mockResolvedValue(2); const response = await request(app) .get('/api/tasks') .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('data'); expect(response.body).toHaveProperty('pagination'); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(2); }); it('should return empty array when user has no completed tasks', async () => { const { token } = await createTestUser(); Task.find.mockImplementation(() => ({ sort: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), limit: jest.fn().mockResolvedValue([]) })); Task.countDocuments.mockResolvedValue(0); const response = await request(app) .get('/api/tasks') .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('data'); expect(response.body.data.length).toBe(0); }); it('should not get tasks without authentication', async () => { const response = await request(app) .get('/api/tasks') .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); }); describe('POST /api/tasks', () => { it('should create a new task with authentication', async () => { const { user, token } = await createTestUser(); const streetId = '507f1f77bcf86cd799439011'; const taskData = { street: streetId, description: 'Clean the sidewalk', }; const mockStreet = { _id: streetId, name: 'Test Street', location: { lat: 0, lng: 0 } }; const mockTask = { _id: '507f1f77bcf86cd799439013', type: 'task', street: { streetId: streetId, name: 'Test Street', location: { lat: 0, lng: 0 } }, description: taskData.description, status: 'pending', pointsAwarded: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; Street.findById.mockResolvedValue(mockStreet); Task.create.mockResolvedValue(mockTask); const response = await request(app) .post('/api/tasks') .set('x-auth-token', token) .send(taskData) .expect(200); expect(response.body).toHaveProperty('description', taskData.description); expect(response.body).toHaveProperty('street'); expect(response.body.street).toHaveProperty('streetId', streetId); }); it('should return 404 when street not found', async () => { const { user, token } = await createTestUser(); const taskData = { street: '507f1f77bcf86cd799439011', description: 'Clean the sidewalk', }; Street.findById.mockResolvedValue(null); const response = await request(app) .post('/api/tasks') .set('x-auth-token', token) .send(taskData) .expect(404); expect(response.body).toHaveProperty('msg', 'Street not found'); }); it('should not create task without authentication', async () => { const taskData = { street: '507f1f77bcf86cd799439011', description: 'Clean the sidewalk', }; const response = await request(app) .post('/api/tasks') .send(taskData) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); }); describe('PUT /api/tasks/:id', () => { it('should complete a task', async () => { const { user, token } = await createTestUser(); const taskId = '507f1f77bcf86cd799439013'; const task = { _id: taskId, type: 'task', street: { streetId: '507f1f77bcf86cd799439011', name: 'Test Street', location: { lat: 0, lng: 0 } }, description: 'Clean sidewalk', status: 'pending', completedBy: null, completedAt: null, pointsAwarded: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), save: jest.fn().mockResolvedValue(this) }; const mockUser = { _id: user._id, name: 'Test User', profilePicture: '' }; const updatedTask = { ...task, status: 'completed', completedBy: { userId: user._id, name: 'Test User', profilePicture: '' }, completedAt: new Date().toISOString() }; const updatedUser = { _id: user._id, points: 10 }; User.findById.mockResolvedValue(mockUser); // Mock save method on task instance const mockTaskInstance = { ...task, save: jest.fn().mockResolvedValue(updatedTask) }; Task.findById.mockResolvedValue(mockTaskInstance); couchdbService.updateUserPoints.mockResolvedValue(updatedUser); const response = await request(app) .put(`/api/tasks/${taskId}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('task'); expect(response.body.task).toHaveProperty('status', 'completed'); expect(response.body).toHaveProperty('pointsAwarded', 10); expect(response.body).toHaveProperty('newBalance', 10); }); it('should return 404 for non-existent task', async () => { const { token } = await createTestUser(); const fakeId = '507f1f77bcf86cd799439011'; Task.findById.mockResolvedValue(null); const response = await request(app) .put(`/api/tasks/${fakeId}`) .set('x-auth-token', token) .expect(404); expect(response.body).toHaveProperty('msg', 'Task not found'); }); it('should return 400 for already completed task', async () => { const { user, token } = await createTestUser(); const taskId = '507f1f77bcf86cd799439013'; const task = { _id: taskId, type: 'task', street: { streetId: '507f1f77bcf86cd799439011', name: 'Test Street', location: { lat: 0, lng: 0 } }, description: 'Clean sidewalk', status: 'completed', completedBy: { userId: user._id, name: 'Test User', profilePicture: '' }, completedAt: new Date().toISOString(), pointsAwarded: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; Task.findById.mockResolvedValue(task); const response = await request(app) .put(`/api/tasks/${taskId}`) .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('msg', 'Task already completed'); }); it('should not complete task without authentication', async () => { const taskId = '507f1f77bcf86cd799439013'; const response = await request(app) .put(`/api/tasks/${taskId}`) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); it('should handle invalid task ID format', async () => { const { token } = await createTestUser(); const response = await request(app) .put('/api/tasks/invalid-id') .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('success', false); expect(response.body.errors).toEqual( expect.arrayContaining([ expect.objectContaining({ field: 'id', message: 'Invalid task ID' }) ]) ); }); }); });