backend unit tests
This commit is contained in:
657
backend/tests/unit/routes/messages.test.js
Normal file
657
backend/tests/unit/routes/messages.test.js
Normal file
@@ -0,0 +1,657 @@
|
||||
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,
|
||||
subject: 'Test Message',
|
||||
content: 'Hello there!',
|
||||
isRead: false,
|
||||
createdAt: '2024-01-15T10:00:00.000Z',
|
||||
sender: {
|
||||
id: 2,
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
profileImage: 'jane.jpg'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
senderId: 3,
|
||||
receiverId: 1,
|
||||
subject: 'Another Message',
|
||||
content: 'Hi!',
|
||||
isRead: true,
|
||||
createdAt: '2024-01-14T10:00:00.000Z',
|
||||
sender: {
|
||||
id: 3,
|
||||
firstName: 'Bob',
|
||||
lastName: 'Johnson',
|
||||
profileImage: 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', 'profileImage']
|
||||
}
|
||||
],
|
||||
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,
|
||||
subject: 'My Message',
|
||||
content: 'Hello Jane!',
|
||||
isRead: false,
|
||||
createdAt: '2024-01-15T12:00:00.000Z',
|
||||
receiver: {
|
||||
id: 2,
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
profileImage: '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', 'profileImage']
|
||||
}
|
||||
],
|
||||
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,
|
||||
subject: 'Test Message',
|
||||
content: 'Hello there!',
|
||||
isRead: false,
|
||||
createdAt: '2024-01-15T10:00:00.000Z',
|
||||
sender: {
|
||||
id: 2,
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
profileImage: 'jane.jpg'
|
||||
},
|
||||
receiver: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: 'john.jpg'
|
||||
},
|
||||
replies: [
|
||||
{
|
||||
id: 4,
|
||||
senderId: 1,
|
||||
content: 'Reply message',
|
||||
sender: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: 'john.jpg'
|
||||
}
|
||||
}
|
||||
],
|
||||
update: jest.fn()
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockMessageFindOne.mockResolvedValue(mockMessage);
|
||||
});
|
||||
|
||||
it('should get message with replies for receiver', 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,
|
||||
subject: 'Test Message',
|
||||
content: 'Hello there!',
|
||||
isRead: false,
|
||||
createdAt: '2024-01-15T10:00:00.000Z',
|
||||
sender: {
|
||||
id: 2,
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
profileImage: 'jane.jpg'
|
||||
},
|
||||
receiver: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: 'john.jpg'
|
||||
},
|
||||
replies: [
|
||||
{
|
||||
id: 4,
|
||||
senderId: 1,
|
||||
content: 'Reply message',
|
||||
sender: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: '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,
|
||||
subject: 'Test Message',
|
||||
content: 'Hello there!',
|
||||
isRead: false,
|
||||
createdAt: '2024-01-15T10:00:00.000Z',
|
||||
sender: {
|
||||
id: 2,
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
profileImage: 'jane.jpg'
|
||||
},
|
||||
receiver: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: 'john.jpg'
|
||||
},
|
||||
replies: [
|
||||
{
|
||||
id: 4,
|
||||
senderId: 1,
|
||||
content: 'Reply message',
|
||||
sender: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: '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,
|
||||
subject: 'New Message',
|
||||
content: 'Hello Jane!',
|
||||
parentMessageId: null
|
||||
};
|
||||
|
||||
const mockMessageWithSender = {
|
||||
...mockCreatedMessage,
|
||||
sender: {
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
profileImage: 'john.jpg'
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockUserFindByPk.mockResolvedValue(mockReceiver);
|
||||
mockMessageCreate.mockResolvedValue(mockCreatedMessage);
|
||||
mockMessageFindByPk.mockResolvedValue(mockMessageWithSender);
|
||||
});
|
||||
|
||||
it('should create a new message', async () => {
|
||||
const messageData = {
|
||||
receiverId: 2,
|
||||
subject: 'New Message',
|
||||
content: 'Hello Jane!',
|
||||
parentMessageId: null
|
||||
};
|
||||
|
||||
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,
|
||||
subject: 'New Message',
|
||||
content: 'Hello Jane!',
|
||||
parentMessageId: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a reply message with parentMessageId', async () => {
|
||||
const replyData = {
|
||||
receiverId: 2,
|
||||
subject: 'Re: Original Message',
|
||||
content: 'This is a reply',
|
||||
parentMessageId: 1
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.post('/messages')
|
||||
.send(replyData);
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(mockMessageCreate).toHaveBeenCalledWith({
|
||||
senderId: 1,
|
||||
receiverId: 2,
|
||||
subject: 'Re: Original Message',
|
||||
content: 'This is a reply',
|
||||
parentMessageId: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent receiver', async () => {
|
||||
mockUserFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/messages')
|
||||
.send({
|
||||
receiverId: 999,
|
||||
subject: 'Test',
|
||||
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
|
||||
subject: 'Self Message',
|
||||
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,
|
||||
subject: 'Test',
|
||||
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([]);
|
||||
});
|
||||
|
||||
it('should handle message with no replies', async () => {
|
||||
const messageWithoutReplies = {
|
||||
id: 1,
|
||||
senderId: 2,
|
||||
receiverId: 1,
|
||||
subject: 'Test Message',
|
||||
content: 'Hello there!',
|
||||
isRead: false,
|
||||
replies: [],
|
||||
update: jest.fn()
|
||||
};
|
||||
mockMessageFindOne.mockResolvedValue(messageWithoutReplies);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/messages/1');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.replies).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle missing optional fields in message creation', async () => {
|
||||
const mockReceiver = { id: 2, firstName: 'Jane', lastName: 'Smith' };
|
||||
const mockCreatedMessage = {
|
||||
id: 6,
|
||||
senderId: 1,
|
||||
receiverId: 2,
|
||||
subject: undefined,
|
||||
content: 'Just content',
|
||||
parentMessageId: undefined
|
||||
};
|
||||
const mockMessageWithSender = {
|
||||
...mockCreatedMessage,
|
||||
sender: { id: 1, firstName: 'John', lastName: 'Doe' }
|
||||
};
|
||||
|
||||
mockUserFindByPk.mockResolvedValue(mockReceiver);
|
||||
mockMessageCreate.mockResolvedValue(mockCreatedMessage);
|
||||
mockMessageFindByPk.mockResolvedValue(mockMessageWithSender);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/messages')
|
||||
.send({
|
||||
receiverId: 2,
|
||||
content: 'Just content'
|
||||
// subject and parentMessageId omitted
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(mockMessageCreate).toHaveBeenCalledWith({
|
||||
senderId: 1,
|
||||
receiverId: 2,
|
||||
subject: undefined,
|
||||
content: 'Just content',
|
||||
parentMessageId: undefined
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user