// Mock CouchDB service for testing const mockCouchdbService = { createDocument: jest.fn(), findDocumentById: jest.fn(), updateDocument: jest.fn(), findByType: jest.fn(), initialize: jest.fn(), getDocument: jest.fn(), findUserById: jest.fn(), }; // Mock the service module jest.mock('../../services/couchdbService', () => mockCouchdbService); const Task = require('../../models/Task'); const User = require('../../models/User'); const Street = require('../../models/Street'); describe('Task Model', () => { beforeEach(() => { jest.clearAllMocks(); // Reset all mocks to ensure clean state mockCouchdbService.createDocument.mockReset(); mockCouchdbService.findDocumentById.mockReset(); mockCouchdbService.updateDocument.mockReset(); mockCouchdbService.findByType.mockReset(); mockCouchdbService.initialize.mockReset(); mockCouchdbService.getDocument.mockReset(); mockCouchdbService.findUserById.mockReset(); }); describe('Schema Validation', () => { it('should create a valid task', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Clean up litter on street', status: 'pending', }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task._id).toBeDefined(); expect(task.description).toBe(taskData.description); expect(task.status).toBe(taskData.status); expect(task.street.streetId).toBe(streetData.streetId); expect(task.street.name).toBe(streetData.name); }); it('should require street field', async () => { const taskData = { description: 'Task without street', }; expect(() => new Task(taskData)).toThrow(); }); it('should require description field', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, }; expect(() => new Task(taskData)).toThrow(); }); }); describe('Task Status', () => { it('should default status to pending', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Default status task', }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.status).toBe('pending'); }); const validStatuses = ['pending', 'completed']; validStatuses.forEach(status => { it(`should accept "${status}" as valid status`, async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: `Task with ${status} status`, status, }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.status).toBe(status); }); }); }); describe('Task Completion', () => { it('should allow completing a task', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Task to complete', status: 'pending', }; const mockTask = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.getDocument.mockResolvedValue(mockTask); mockCouchdbService.findUserById.mockResolvedValue({ _id: 'user_123', name: 'Test User', email: 'test@example.com', password: 'password123' }); mockCouchdbService.updateDocument.mockResolvedValue({ ...mockTask, status: 'completed', completedBy: { userId: 'user_123', name: 'Test User', profilePicture: '' }, completedAt: '2023-01-01T01:00:00.000Z', _rev: '2-def' }); const task = await Task.findById('task_123'); task.completedBy = { userId: 'user_123', name: 'Test User', profilePicture: '' }; task.status = 'completed'; task.completedAt = '2023-01-01T01:00:00.000Z'; await task.save(); expect(task.status).toBe('completed'); expect(task.completedBy.userId).toBe('user_123'); expect(task.completedAt).toBeDefined(); }); }); describe('Points Awarded', () => { it('should default pointsAwarded to 10', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Default points task', }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.pointsAwarded).toBe(10); }); it('should allow custom pointsAwarded', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Custom points task', pointsAwarded: 25, }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.pointsAwarded).toBe(25); }); }); describe('Timestamps', () => { it('should automatically set createdAt and updatedAt', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Timestamp task', }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.createdAt).toBeDefined(); expect(task.updatedAt).toBeDefined(); expect(typeof task.createdAt).toBe('string'); expect(typeof task.updatedAt).toBe('string'); }); it('should update updatedAt on modification', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Update test task', }; const mockTask = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.getDocument.mockResolvedValue(mockTask); mockCouchdbService.updateDocument.mockResolvedValue({ ...mockTask, status: 'completed', _rev: '2-def', updatedAt: '2023-01-01T00:00:01.000Z' }); const task = await Task.findById('task_123'); const originalUpdatedAt = task.updatedAt; task.status = 'completed'; await task.save(); expect(task.updatedAt).not.toBe(originalUpdatedAt); }); }); describe('Relationships', () => { it('should reference Street model', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const taskData = { street: streetData, description: 'Street relationship task', }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.street).toBeDefined(); expect(task.street.name).toBe('Test Street'); }); it('should reference User model for completedBy', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const userData = { userId: 'user_123', name: 'Test User', profilePicture: '' }; const taskData = { street: streetData, description: 'Completed relationship task', completedBy: userData, status: 'completed', }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.completedBy).toBeDefined(); expect(task.completedBy.name).toBe('Test User'); }); }); describe('Description Length', () => { it('should allow long descriptions', async () => { const streetData = { streetId: 'street_123', name: 'Test Street', location: { type: 'Point', coordinates: [-73.935242, 40.730610], } }; const longDescription = 'a'.repeat(1001); // Long description const taskData = { street: streetData, description: longDescription, }; const mockCreated = { _id: 'task_123', _rev: '1-abc', type: 'task', ...taskData, status: 'pending', pointsAwarded: 10, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.initialize.mockResolvedValue(true); mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const task = await Task.create(taskData); expect(task.description).toBe(longDescription); }); }); });