const request = require('supertest'); const express = require('express'); // Mock Post model before importing routes jest.mock('../../models/Post', () => { const MockPost = jest.fn().mockImplementation((data) => ({ _id: data._id || `post_${Date.now()}`, content: data.content, imageUrl: data.imageUrl, cloudinaryPublicId: data.cloudinaryPublicId, user: data.user, likes: data.likes || [], comments: data.comments || [], createdAt: data.createdAt || new Date(), updatedAt: data.updatedAt || new Date(), save: jest.fn().mockResolvedValue(this), populate: jest.fn().mockResolvedValue(this), toJSON: jest.fn().mockReturnValue(this), ...data })); MockPost.findById = jest.fn(); MockPost.find = jest.fn(); MockPost.findAll = jest.fn(); MockPost.countDocuments = jest.fn(); MockPost.create = jest.fn(); MockPost.updatePost = jest.fn(); MockPost.addLike = jest.fn(); MockPost.removeLike = jest.fn(); return MockPost; }); const postRoutes = require('../../routes/posts'); const { createTestUser, createTestPost } = require('../utils/testHelpers'); const couchdbService = require('../../services/couchdbService'); const Post = require('../../models/Post'); const app = express(); app.use(express.json()); app.use('/api/posts', postRoutes); describe('Post Routes', () => { beforeEach(() => { jest.clearAllMocks(); // Reset default implementations Post.findById.mockResolvedValue(null); Post.find.mockResolvedValue([]); Post.findAll.mockResolvedValue([]); Post.countDocuments.mockResolvedValue(0); Post.create.mockImplementation((data) => { // Return post structure matching actual Post model (user object with userId) return Promise.resolve({ _id: '507f1f77bcf86cd799439011', user: { userId: data.user || '507f1f77bcf86cd799439099', name: 'Test User', profilePicture: '' }, content: data.content, imageUrl: data.imageUrl, cloudinaryPublicId: data.cloudinaryPublicId, likes: [], likesCount: 0, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); }); Post.updatePost.mockImplementation((id, data) => Promise.resolve({ _id: id, ...data })); Post.addLike.mockImplementation((id, userId) => Promise.resolve({ _id: id, likes: [userId] })); Post.removeLike.mockImplementation((id, userId) => Promise.resolve({ _id: id, likes: [] })); }); describe('GET /api/posts', () => { it('should get all posts with pagination', async () => { const { user } = await createTestUser(); const post1 = await createTestPost(user._id, { content: 'First post' }); const post2 = await createTestPost(user._id, { content: 'Second post' }); const mockPosts = [post1, post2]; Post.findAll.mockResolvedValue(mockPosts); Post.countDocuments.mockResolvedValue(2); const response = await request(app) .get('/api/posts') .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 no posts exist', async () => { Post.findAll.mockResolvedValue([]); Post.countDocuments.mockResolvedValue(0); const response = await request(app) .get('/api/posts') .expect(200); expect(response.body).toHaveProperty('data'); expect(response.body.data.length).toBe(0); }); }); describe('POST /api/posts', () => { it('should create a new post with authentication', async () => { const { user, token } = await createTestUser(); const postData = { content: 'This is my new post about street cleaning', }; const mockPost = { _id: '507f1f77bcf86cd799439011', user: { userId: user._id, name: user.name, profilePicture: user.profilePicture || '' }, content: postData.content, likes: [], likesCount: 0, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; Post.create.mockResolvedValue(mockPost); const response = await request(app) .post('/api/posts') .set('x-auth-token', token) .send(postData); console.log('Response status:', response.status); console.log('Response body:', JSON.stringify(response.body, null, 2)); if (response.status !== 200) { console.log('ERROR - Status:', response.status); console.log('ERROR - Body:', JSON.stringify(response.body, null, 2)); } expect(response.status).toBe(200); expect(response.body).toHaveProperty('post'); expect(response.body.post).toHaveProperty('content', postData.content); expect(response.body.post.user).toHaveProperty('userId', user._id); expect(response.body).toHaveProperty('pointsAwarded', 5); }); it('should create a post with image', async () => { const { user, token } = await createTestUser(); const postData = { content: 'Post with image', imageUrl: 'https://example.com/image.jpg', cloudinaryPublicId: 'test_public_id', }; const mockPost = { _id: '507f1f77bcf86cd799439012', user: { userId: user._id, name: user.name, profilePicture: user.profilePicture || '' }, content: postData.content, imageUrl: postData.imageUrl, cloudinaryPublicId: postData.cloudinaryPublicId, likes: [], likesCount: 0, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; Post.create.mockResolvedValue(mockPost); const response = await request(app) .post('/api/posts') .set('x-auth-token', token) .send(postData) .expect(200); expect(response.body.post).toHaveProperty('imageUrl', postData.imageUrl); expect(response.body.post).toHaveProperty('cloudinaryPublicId', postData.cloudinaryPublicId); }); it('should not create post without authentication', async () => { const postData = { content: 'This should fail', }; const response = await request(app) .post('/api/posts') .send(postData) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); it('should not create post without content', async () => { const { token } = await createTestUser(); const response = await request(app) .post('/api/posts') .set('x-auth-token', token) .send({}) .expect(400); expect(response.body).toHaveProperty('msg', 'Content is required'); }); }); describe('PUT /api/posts/like/:id', () => { it('should like a post', async () => { const { user: author } = await createTestUser({ email: 'author@example.com' }); const { user: liker, token } = await createTestUser({ email: 'liker@example.com' }); const postId = '507f1f77bcf86cd799439011'; const post = { _id: postId, user: { userId: author._id, name: author.name, profilePicture: author.profilePicture || '' }, likes: [], likesCount: 0, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; const mockPost = { ...post, likes: [liker._id], likesCount: 1 }; Post.findById.mockResolvedValue(post); Post.addLike.mockResolvedValue(mockPost); const response = await request(app) .put(`/api/posts/like/${postId}`) .set('x-auth-token', token) .expect(200); expect(Array.isArray(response.body)).toBe(true); expect(response.body.length).toBe(1); expect(response.body[0]).toBe(liker._id); }); it('should not like a post twice', async () => { const { user: author } = await createTestUser({ email: 'author@example.com' }); const { user: liker, token } = await createTestUser({ email: 'liker@example.com' }); const postId = '507f1f77bcf86cd799439011'; const mockPost = { _id: postId, user: { userId: author._id, name: author.name, profilePicture: author.profilePicture || '' }, likes: [liker._id], likesCount: 1, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; Post.findById.mockResolvedValue(mockPost); const response = await request(app) .put(`/api/posts/like/${postId}`) .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('msg', 'Post already liked'); }); it('should return 404 for non-existent post', async () => { const { token } = await createTestUser(); const fakeId = '507f1f77bcf86cd799439011'; Post.findById.mockResolvedValue(null); const response = await request(app) .put(`/api/posts/like/${fakeId}`) .set('x-auth-token', token) .expect(404); expect(response.body).toHaveProperty('msg', 'Post not found'); }); it('should not like post without authentication', async () => { const { user } = await createTestUser(); const postId = '507f1f77bcf86cd799439011'; const response = await request(app) .put(`/api/posts/like/${postId}`) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); }); describe('PUT /api/posts/unlike/:id', () => { it('should unlike a post', async () => { const { user: author } = await createTestUser({ email: 'author@example.com' }); const { user: liker, token } = await createTestUser({ email: 'liker@example.com' }); const postId = '507f1f77bcf86cd799439011'; const mockPost = { _id: postId, user: { userId: author._id, name: author.name, profilePicture: author.profilePicture || '' }, likes: [liker._id], likesCount: 1, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; const mockUpdatedPost = { ...mockPost, likes: [], likesCount: 0 }; Post.findById.mockResolvedValue(mockPost); Post.removeLike.mockResolvedValue(mockUpdatedPost); const response = await request(app) .put(`/api/posts/unlike/${postId}`) .set('x-auth-token', token) .expect(200); expect(Array.isArray(response.body)).toBe(true); expect(response.body.length).toBe(0); }); it('should not unlike a post that was not liked', async () => { const { user: author } = await createTestUser({ email: 'author@example.com' }); const { user: liker, token } = await createTestUser({ email: 'liker@example.com' }); const postId = '507f1f77bcf86cd799439011'; const post = { _id: postId, user: { userId: author._id, name: author.name, profilePicture: author.profilePicture || '' }, likes: [], likesCount: 0, commentsCount: 0, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; Post.findById.mockResolvedValue(post); const response = await request(app) .put(`/api/posts/unlike/${postId}`) .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('msg', 'Post not yet liked'); }); }); });