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: `street_${Date.now()}`, _rev: '1-test', type: 'street', ...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(), })); const streetRoutes = require('../../routes/streets'); const Street = require('../../models/Street'); const User = require('../../models/User'); const { createTestUser, createTestStreet } = require('../utils/testHelpers'); const couchdbService = require('../../services/couchdbService'); const app = express(); app.use(express.json()); app.use('/api/streets', streetRoutes); describe('Street Routes', () => { beforeEach(() => { jest.clearAllMocks(); // Mock Street model methods Street.find = jest.fn().mockResolvedValue([]); Street.findById = jest.fn().mockResolvedValue(null); Street.findOne = jest.fn().mockResolvedValue(null); Street.countDocuments = jest.fn().mockResolvedValue(0); Street.create = jest.fn().mockImplementation((data) => Promise.resolve({ _id: `street_${Date.now()}`, _rev: '1-test', type: 'street', ...data })); // Mock User model methods User.findById = jest.fn().mockResolvedValue({ _id: 'user_123', adoptedStreets: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 }, save: jest.fn().mockResolvedValue(true), toJSON: () => ({ _id: 'user_123', adoptedStreets: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 } }) }); // Mock couchdbService methods couchdbService.find.mockResolvedValue([]); couchdbService.getDocument.mockResolvedValue(null); couchdbService.createDocument.mockImplementation((doc) => Promise.resolve({ _id: `street_${Date.now()}`, _rev: '1-test', ...doc })); couchdbService.updateDocument.mockImplementation((id, doc) => Promise.resolve({ _id: id, _rev: '2-test', ...doc })); couchdbService.updateUserPoints = jest.fn().mockResolvedValue({ _id: 'user_123', points: 50 }); }); describe('GET /api/streets', () => { it('should get all streets', async () => { const { user } = await createTestUser(); const street1 = await createTestStreet(user.id, { name: 'Main Street' }); const street2 = await createTestStreet(user.id, { name: 'Oak Avenue' }); // Create street objects with populate and save methods const street1WithPopulate = { ...street1, populate: jest.fn().mockResolvedValue(street1), save: jest.fn().mockResolvedValue(street1) }; const street2WithPopulate = { ...street2, populate: jest.fn().mockResolvedValue(street2), save: jest.fn().mockResolvedValue(street2) }; // Mock Street.find and couchdbService.find to return test streets Street.find.mockResolvedValue([street1WithPopulate, street2WithPopulate]); couchdbService.find.mockResolvedValue([street1, street2]); Street.countDocuments.mockResolvedValue(2); const response = await request(app) .get('/api/streets') .expect(200); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(2); expect(response.body.data[0]).toHaveProperty('name'); expect(response.body.data[0]).toHaveProperty('location'); }); it('should return empty array when no streets exist', async () => { // Mock Street.find and couchdbService.find to return empty array Street.find.mockResolvedValue([]); couchdbService.find.mockResolvedValue([]); Street.countDocuments.mockResolvedValue(0); const response = await request(app) .get('/api/streets') .expect(200); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(0); }); }); describe('GET /api/streets/:id', () => { it('should get a single street by id', async () => { const { user } = await createTestUser(); const street = await createTestStreet(user.id, { name: 'Elm Street' }); // Create street object with populate, save, and toJSON methods const streetWithPopulate = { ...street, populate: jest.fn().mockResolvedValue(street), save: jest.fn().mockResolvedValue(street), toJSON: () => street }; // Mock Street.findById and couchdbService.getDocument to return test street Street.findById.mockResolvedValue(streetWithPopulate); couchdbService.getDocument.mockResolvedValue(street); const response = await request(app) .get(`/api/streets/${street.id}`) .expect(200); expect(response.body).toHaveProperty('_id', street._id); expect(response.body).toHaveProperty('name', 'Elm Street'); }); it('should return 404 for non-existent street', async () => { const fakeId = '507f1f77bcf86cd799439011'; // Mock Street.findById to return null (not found) Street.findById.mockResolvedValue(null); const response = await request(app) .get(`/api/streets/${fakeId}`) .expect(404); expect(response.body).toHaveProperty('msg', 'Street not found'); }); it('should handle invalid street ID format', async () => { // Mock Street.findById to throw an error for invalid ID Street.findById.mockRejectedValue(new Error('Invalid ID format')); const response = await request(app) .get('/api/streets/invalid-id') .expect(500); expect(response.body).toBeDefined(); }); }); describe('POST /api/streets', () => { it('should create a new street with authentication', async () => { const { token } = await createTestUser(); const streetData = { name: 'Broadway', location: { type: 'Point', coordinates: [-73.989308, 40.756432] } }; // Mock Street.create to return created street const createdStreet = { _id: 'street_123', _rev: '1-test', type: 'street', ...streetData, status: 'available', adoptedBy: null, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; Street.create.mockResolvedValue(createdStreet); const response = await request(app) .post('/api/streets') .set('x-auth-token', token) .send(streetData) .expect(200); expect(response.body).toHaveProperty('name', streetData.name); expect(response.body).toHaveProperty('location'); expect(response.body.location).toHaveProperty('coordinates'); }); it('should not create street without authentication', async () => { const streetData = { name: 'Broadway', location: { type: 'Point', coordinates: [-73.989308, 40.756432] } }; const response = await request(app) .post('/api/streets') .send(streetData) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); }); describe('PUT /api/streets/adopt/:id', () => { it('should adopt an available street', async () => { const { user, token } = await createTestUser(); const street = await createTestStreet(user.id, { status: 'available', adoptedBy: null }); // Create street object with populate and save methods const streetWithPopulate = { ...street, populate: jest.fn().mockResolvedValue(street), save: jest.fn().mockResolvedValue({ ...street, status: 'adopted', adoptedBy: { userId: user.id, name: user.name, profilePicture: '' } }) }; // Mock Street.findById and couchdbService.getDocument to return available street Street.findById.mockResolvedValue(streetWithPopulate); couchdbService.getDocument.mockResolvedValue(street); // Mock User.findById to return user with no adopted streets User.findById.mockResolvedValue({ _id: user.id, adoptedStreets: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 }, save: jest.fn().mockResolvedValue(true), toJSON: () => ({ _id: user.id, adoptedStreets: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 } }) }); // Mock couchdbService.updateDocument for adoption update const updatedStreet = { ...street, status: 'adopted', adoptedBy: { userId: user.id, name: user.name, profilePicture: '' } }; couchdbService.updateDocument.mockResolvedValue(updatedStreet); const response = await request(app) .put(`/api/streets/adopt/${street.id}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('street'); expect(response.body.street).toHaveProperty('status', 'adopted'); expect(response.body.street).toHaveProperty('adoptedBy'); expect(response.body).toHaveProperty('pointsAwarded', 50); expect(response.body).toHaveProperty('newBalance', 50); }); it('should not adopt an already adopted street', async () => { const { user, token } = await createTestUser(); const street = await createTestStreet(user.id, { status: 'adopted', adoptedBy: user.id }); // Mock Street.findById to return already adopted street Street.findById.mockResolvedValue(street); const response = await request(app) .put(`/api/streets/adopt/${street.id}`) .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('msg', 'Street already adopted'); }); it('should return 404 for non-existent street', async () => { const { token } = await createTestUser(); const fakeId = '507f1f77bcf86cd799439011'; // Mock Street.findById to return null (not found) Street.findById.mockResolvedValue(null); const response = await request(app) .put(`/api/streets/adopt/${fakeId}`) .set('x-auth-token', token) .expect(404); expect(response.body).toHaveProperty('msg', 'Street not found'); }); it('should not adopt street without authentication', async () => { const { user } = await createTestUser(); const street = await createTestStreet(user.id); const response = await request(app) .put(`/api/streets/adopt/${street.id}`) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); }); });