const request = require('supertest'); const express = require('express'); const messagesRouter = require('../../../routes/messages'); // Mock all dependencies jest.mock('../../../models', () => ({ Message: { findAll: jest.fn(), findOne: jest.fn(), findByPk: jest.fn(), create: jest.fn(), count: jest.fn(), }, User: { findByPk: jest.fn(), }, })); jest.mock('../../../middleware/auth', () => ({ authenticateToken: jest.fn((req, res, next) => { req.user = { id: 1 }; next(); }), })); jest.mock('sequelize', () => ({ Op: { or: Symbol('or'), }, })); const { Message, User } = require('../../../models'); // Create express app with the router const app = express(); app.use(express.json()); app.use('/messages', messagesRouter); // Mock models const mockMessageFindAll = Message.findAll; const mockMessageFindOne = Message.findOne; const mockMessageFindByPk = Message.findByPk; const mockMessageCreate = Message.create; const mockMessageCount = Message.count; const mockUserFindByPk = User.findByPk; describe('Messages Routes', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('GET /', () => { it('should get inbox messages for authenticated user', async () => { const mockMessages = [ { id: 1, senderId: 2, receiverId: 1, content: 'Hello there!', isRead: false, createdAt: '2024-01-15T10:00:00.000Z', sender: { id: 2, firstName: 'Jane', lastName: 'Smith', imageFilename: 'jane.jpg' } }, { id: 2, senderId: 3, receiverId: 1, content: 'Hi!', isRead: true, createdAt: '2024-01-14T10:00:00.000Z', sender: { id: 3, firstName: 'Bob', lastName: 'Johnson', imageFilename: null } } ]; mockMessageFindAll.mockResolvedValue(mockMessages); const response = await request(app) .get('/messages'); expect(response.status).toBe(200); expect(response.body).toEqual(mockMessages); expect(mockMessageFindAll).toHaveBeenCalledWith({ where: { receiverId: 1 }, include: [ { model: User, as: 'sender', attributes: ['id', 'firstName', 'lastName', 'imageFilename'] } ], order: [['createdAt', 'DESC']] }); }); it('should handle database errors', async () => { mockMessageFindAll.mockRejectedValue(new Error('Database error')); const response = await request(app) .get('/messages'); expect(response.status).toBe(500); expect(response.body).toEqual({ error: 'Database error' }); }); }); describe('GET /sent', () => { it('should get sent messages for authenticated user', async () => { const mockSentMessages = [ { id: 3, senderId: 1, receiverId: 2, content: 'Hello Jane!', isRead: false, createdAt: '2024-01-15T12:00:00.000Z', receiver: { id: 2, firstName: 'Jane', lastName: 'Smith', imageFilename: 'jane.jpg' } } ]; mockMessageFindAll.mockResolvedValue(mockSentMessages); const response = await request(app) .get('/messages/sent'); expect(response.status).toBe(200); expect(response.body).toEqual(mockSentMessages); expect(mockMessageFindAll).toHaveBeenCalledWith({ where: { senderId: 1 }, include: [ { model: User, as: 'receiver', attributes: ['id', 'firstName', 'lastName', 'imageFilename'] } ], order: [['createdAt', 'DESC']] }); }); it('should handle database errors', async () => { mockMessageFindAll.mockRejectedValue(new Error('Database error')); const response = await request(app) .get('/messages/sent'); expect(response.status).toBe(500); expect(response.body).toEqual({ error: 'Database error' }); }); }); describe('GET /:id', () => { const mockMessage = { id: 1, senderId: 2, receiverId: 1, content: 'Hello there!', isRead: false, createdAt: '2024-01-15T10:00:00.000Z', sender: { id: 2, firstName: 'Jane', lastName: 'Smith', imageFilename: 'jane.jpg' }, receiver: { id: 1, firstName: 'John', lastName: 'Doe', imageFilename: 'john.jpg' }, update: jest.fn() }; beforeEach(() => { mockMessageFindOne.mockResolvedValue(mockMessage); }); it('should get message for receiver and mark as read', async () => { mockMessage.update.mockResolvedValue(); const response = await request(app) .get('/messages/1'); expect(response.status).toBe(200); expect(response.body).toEqual({ id: 1, senderId: 2, receiverId: 1, content: 'Hello there!', isRead: false, createdAt: '2024-01-15T10:00:00.000Z', sender: { id: 2, firstName: 'Jane', lastName: 'Smith', imageFilename: 'jane.jpg' }, receiver: { id: 1, firstName: 'John', lastName: 'Doe', imageFilename: 'john.jpg' } }); expect(mockMessage.update).toHaveBeenCalledWith({ isRead: true }); }); it('should get message without marking as read for sender', async () => { const senderMessage = { ...mockMessage, senderId: 1, receiverId: 2 }; mockMessageFindOne.mockResolvedValue(senderMessage); const response = await request(app) .get('/messages/1'); expect(response.status).toBe(200); expect(response.body).toEqual({ id: 1, senderId: 1, receiverId: 2, content: 'Hello there!', isRead: false, createdAt: '2024-01-15T10:00:00.000Z', sender: { id: 2, firstName: 'Jane', lastName: 'Smith', imageFilename: 'jane.jpg' }, receiver: { id: 1, firstName: 'John', lastName: 'Doe', imageFilename: 'john.jpg' } }); expect(mockMessage.update).not.toHaveBeenCalled(); }); it('should not mark already read message as read', async () => { const readMessage = { ...mockMessage, isRead: true }; mockMessageFindOne.mockResolvedValue(readMessage); const response = await request(app) .get('/messages/1'); expect(response.status).toBe(200); expect(mockMessage.update).not.toHaveBeenCalled(); }); it('should return 404 for non-existent message', async () => { mockMessageFindOne.mockResolvedValue(null); const response = await request(app) .get('/messages/999'); expect(response.status).toBe(404); expect(response.body).toEqual({ error: 'Message not found' }); }); it('should handle database errors', async () => { mockMessageFindOne.mockRejectedValue(new Error('Database error')); const response = await request(app) .get('/messages/1'); expect(response.status).toBe(500); expect(response.body).toEqual({ error: 'Database error' }); }); }); describe('POST /', () => { const mockReceiver = { id: 2, firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' }; const mockCreatedMessage = { id: 5, senderId: 1, receiverId: 2, content: 'Hello Jane!' }; const mockMessageWithSender = { ...mockCreatedMessage, sender: { id: 1, firstName: 'John', lastName: 'Doe', imageFilename: 'john.jpg' } }; beforeEach(() => { mockUserFindByPk.mockResolvedValue(mockReceiver); mockMessageCreate.mockResolvedValue(mockCreatedMessage); mockMessageFindByPk.mockResolvedValue(mockMessageWithSender); }); it('should create a new message', async () => { const messageData = { receiverId: 2, content: 'Hello Jane!' }; const response = await request(app) .post('/messages') .send(messageData); expect(response.status).toBe(201); expect(response.body).toEqual(mockMessageWithSender); expect(mockMessageCreate).toHaveBeenCalledWith({ senderId: 1, receiverId: 2, content: 'Hello Jane!', imageFilename: null }); }); it('should return 404 for non-existent receiver', async () => { mockUserFindByPk.mockResolvedValue(null); const response = await request(app) .post('/messages') .send({ receiverId: 999, content: 'Test message' }); expect(response.status).toBe(404); expect(response.body).toEqual({ error: 'Receiver not found' }); }); it('should prevent sending messages to self', async () => { const response = await request(app) .post('/messages') .send({ receiverId: 1, // Same as sender ID content: 'Hello self!' }); expect(response.status).toBe(400); expect(response.body).toEqual({ error: 'Cannot send messages to yourself' }); }); it('should handle database errors during creation', async () => { mockMessageCreate.mockRejectedValue(new Error('Database error')); const response = await request(app) .post('/messages') .send({ receiverId: 2, content: 'Test message' }); expect(response.status).toBe(500); expect(response.body).toEqual({ error: 'Database error' }); }); }); describe('PUT /:id/read', () => { const mockMessage = { id: 1, senderId: 2, receiverId: 1, isRead: false, update: jest.fn() }; beforeEach(() => { mockMessageFindOne.mockResolvedValue(mockMessage); }); it('should mark message as read', async () => { const updatedMessage = { ...mockMessage, isRead: true }; mockMessage.update.mockResolvedValue(updatedMessage); const response = await request(app) .put('/messages/1/read'); expect(response.status).toBe(200); expect(response.body).toEqual({ id: 1, senderId: 2, receiverId: 1, isRead: false }); expect(mockMessage.update).toHaveBeenCalledWith({ isRead: true }); expect(mockMessageFindOne).toHaveBeenCalledWith({ where: { id: '1', receiverId: 1 } }); }); it('should return 404 for non-existent message', async () => { mockMessageFindOne.mockResolvedValue(null); const response = await request(app) .put('/messages/999/read'); expect(response.status).toBe(404); expect(response.body).toEqual({ error: 'Message not found' }); }); it('should return 404 when user is not the receiver', async () => { // Message exists but user is not the receiver (query will return null) mockMessageFindOne.mockResolvedValue(null); const response = await request(app) .put('/messages/1/read'); expect(response.status).toBe(404); expect(response.body).toEqual({ error: 'Message not found' }); }); it('should handle database errors', async () => { mockMessageFindOne.mockRejectedValue(new Error('Database error')); const response = await request(app) .put('/messages/1/read'); expect(response.status).toBe(500); expect(response.body).toEqual({ error: 'Database error' }); }); }); describe('GET /unread/count', () => { it('should get unread message count for authenticated user', async () => { mockMessageCount.mockResolvedValue(5); const response = await request(app) .get('/messages/unread/count'); expect(response.status).toBe(200); expect(response.body).toEqual({ count: 5 }); expect(mockMessageCount).toHaveBeenCalledWith({ where: { receiverId: 1, isRead: false } }); }); it('should return count of 0 when no unread messages', async () => { mockMessageCount.mockResolvedValue(0); const response = await request(app) .get('/messages/unread/count'); expect(response.status).toBe(200); expect(response.body).toEqual({ count: 0 }); }); it('should handle database errors', async () => { mockMessageCount.mockRejectedValue(new Error('Database error')); const response = await request(app) .get('/messages/unread/count'); expect(response.status).toBe(500); expect(response.body).toEqual({ error: 'Database error' }); }); }); describe('Message authorization', () => { it('should only find messages where user is sender or receiver', async () => { const { Op } = require('sequelize'); await request(app) .get('/messages/1'); expect(mockMessageFindOne).toHaveBeenCalledWith({ where: { id: '1', [Op.or]: [ { senderId: 1 }, { receiverId: 1 } ] }, include: expect.any(Array) }); }); }); describe('Edge cases', () => { it('should handle empty inbox', async () => { mockMessageFindAll.mockResolvedValue([]); const response = await request(app) .get('/messages'); expect(response.status).toBe(200); expect(response.body).toEqual([]); }); it('should handle empty sent messages', async () => { mockMessageFindAll.mockResolvedValue([]); const response = await request(app) .get('/messages/sent'); expect(response.status).toBe(200); expect(response.body).toEqual([]); }); }); });