essential forum code

This commit is contained in:
jackiettran
2025-11-11 16:55:00 -05:00
parent 4a4eee86a7
commit 825389228d
29 changed files with 2557 additions and 2861 deletions

View File

@@ -1,823 +0,0 @@
const request = require('supertest');
const express = require('express');
const itemRequestsRouter = require('../../../routes/itemRequests');
// Mock all dependencies
jest.mock('../../../models', () => ({
ItemRequest: {
findAndCountAll: jest.fn(),
findAll: jest.fn(),
findByPk: jest.fn(),
create: jest.fn(),
},
ItemRequestResponse: {
findByPk: jest.fn(),
create: jest.fn(),
},
User: jest.fn(),
Item: jest.fn(),
}));
jest.mock('../../../middleware/auth', () => ({
authenticateToken: jest.fn((req, res, next) => {
req.user = { id: 1 };
next();
}),
}));
jest.mock('sequelize', () => ({
Op: {
or: Symbol('or'),
iLike: Symbol('iLike'),
},
}));
const { ItemRequest, ItemRequestResponse, User, Item } = require('../../../models');
// Create express app with the router
const app = express();
app.use(express.json());
app.use('/item-requests', itemRequestsRouter);
// Mock models
const mockItemRequestFindAndCountAll = ItemRequest.findAndCountAll;
const mockItemRequestFindAll = ItemRequest.findAll;
const mockItemRequestFindByPk = ItemRequest.findByPk;
const mockItemRequestCreate = ItemRequest.create;
const mockItemRequestResponseFindByPk = ItemRequestResponse.findByPk;
const mockItemRequestResponseCreate = ItemRequestResponse.create;
describe('ItemRequests Routes', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('GET /', () => {
it('should get item requests with default pagination and status', async () => {
const mockRequestsData = {
count: 25,
rows: [
{
id: 1,
title: 'Need a Camera',
description: 'Looking for a DSLR camera for weekend photography',
status: 'open',
requesterId: 2,
createdAt: '2024-01-15T10:00:00.000Z',
requester: {
id: 2,
username: 'jane_doe',
firstName: 'Jane',
lastName: 'Doe'
}
},
{
id: 2,
title: 'Power Drill Needed',
description: 'Need a drill for home improvement project',
status: 'open',
requesterId: 3,
createdAt: '2024-01-14T10:00:00.000Z',
requester: {
id: 3,
username: 'bob_smith',
firstName: 'Bob',
lastName: 'Smith'
}
}
]
};
mockItemRequestFindAndCountAll.mockResolvedValue(mockRequestsData);
const response = await request(app)
.get('/item-requests');
expect(response.status).toBe(200);
expect(response.body).toEqual({
requests: mockRequestsData.rows,
totalPages: 2,
currentPage: 1,
totalRequests: 25
});
expect(mockItemRequestFindAndCountAll).toHaveBeenCalledWith({
where: { status: 'open' },
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
}
],
limit: 20,
offset: 0,
order: [['createdAt', 'DESC']]
});
});
it('should filter requests with search query', async () => {
const mockSearchResults = {
count: 5,
rows: [
{
id: 1,
title: 'Need a Camera',
description: 'Looking for a DSLR camera',
status: 'open'
}
]
};
mockItemRequestFindAndCountAll.mockResolvedValue(mockSearchResults);
const response = await request(app)
.get('/item-requests?search=camera&page=1&limit=10');
const { Op } = require('sequelize');
expect(mockItemRequestFindAndCountAll).toHaveBeenCalledWith({
where: {
status: 'open',
[Op.or]: [
{ title: { [Op.iLike]: '%camera%' } },
{ description: { [Op.iLike]: '%camera%' } }
]
},
include: expect.any(Array),
limit: 10,
offset: 0,
order: [['createdAt', 'DESC']]
});
});
it('should handle custom pagination', async () => {
const mockData = { count: 50, rows: [] };
mockItemRequestFindAndCountAll.mockResolvedValue(mockData);
const response = await request(app)
.get('/item-requests?page=3&limit=5');
expect(mockItemRequestFindAndCountAll).toHaveBeenCalledWith({
where: { status: 'open' },
include: expect.any(Array),
limit: 5,
offset: 10, // (3-1) * 5
order: [['createdAt', 'DESC']]
});
});
it('should filter by custom status', async () => {
const mockData = { count: 10, rows: [] };
mockItemRequestFindAndCountAll.mockResolvedValue(mockData);
await request(app)
.get('/item-requests?status=fulfilled');
expect(mockItemRequestFindAndCountAll).toHaveBeenCalledWith({
where: { status: 'fulfilled' },
include: expect.any(Array),
limit: 20,
offset: 0,
order: [['createdAt', 'DESC']]
});
});
it('should handle database errors', async () => {
mockItemRequestFindAndCountAll.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.get('/item-requests');
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('GET /my-requests', () => {
it('should get user\'s own requests with responses', async () => {
const mockRequests = [
{
id: 1,
title: 'My Camera Request',
description: 'Need a camera',
status: 'open',
requesterId: 1,
requester: {
id: 1,
username: 'john_doe',
firstName: 'John',
lastName: 'Doe'
},
responses: [
{
id: 1,
message: 'I have a Canon DSLR available',
responder: {
id: 2,
username: 'jane_doe',
firstName: 'Jane',
lastName: 'Doe'
},
existingItem: {
id: 5,
name: 'Canon EOS 5D',
description: 'Professional DSLR camera'
}
}
]
}
];
mockItemRequestFindAll.mockResolvedValue(mockRequests);
const response = await request(app)
.get('/item-requests/my-requests');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockRequests);
expect(mockItemRequestFindAll).toHaveBeenCalledWith({
where: { requesterId: 1 },
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: ItemRequestResponse,
as: 'responses',
include: [
{
model: User,
as: 'responder',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: Item,
as: 'existingItem'
}
]
}
],
order: [['createdAt', 'DESC']]
});
});
it('should handle database errors', async () => {
mockItemRequestFindAll.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.get('/item-requests/my-requests');
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('GET /:id', () => {
it('should get specific request with responses', async () => {
const mockRequest = {
id: 1,
title: 'Camera Request',
description: 'Need a DSLR camera',
status: 'open',
requesterId: 2,
requester: {
id: 2,
username: 'jane_doe',
firstName: 'Jane',
lastName: 'Doe'
},
responses: [
{
id: 1,
message: 'I have a Canon DSLR',
responder: {
id: 1,
username: 'john_doe',
firstName: 'John',
lastName: 'Doe'
},
existingItem: null
}
]
};
mockItemRequestFindByPk.mockResolvedValue(mockRequest);
const response = await request(app)
.get('/item-requests/1');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockRequest);
expect(mockItemRequestFindByPk).toHaveBeenCalledWith('1', {
include: [
{
model: User,
as: 'requester',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: ItemRequestResponse,
as: 'responses',
include: [
{
model: User,
as: 'responder',
attributes: ['id', 'username', 'firstName', 'lastName']
},
{
model: Item,
as: 'existingItem'
}
]
}
]
});
});
it('should return 404 for non-existent request', async () => {
mockItemRequestFindByPk.mockResolvedValue(null);
const response = await request(app)
.get('/item-requests/999');
expect(response.status).toBe(404);
expect(response.body).toEqual({ error: 'Item request not found' });
});
it('should handle database errors', async () => {
mockItemRequestFindByPk.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.get('/item-requests/1');
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('POST /', () => {
it('should create a new item request', async () => {
const requestData = {
title: 'Need a Drill',
description: 'Looking for a power drill for weekend project',
category: 'tools',
budget: 50,
location: 'New York'
};
const mockCreatedRequest = {
id: 3,
...requestData,
requesterId: 1,
status: 'open'
};
const mockRequestWithRequester = {
...mockCreatedRequest,
requester: {
id: 1,
username: 'john_doe',
firstName: 'John',
lastName: 'Doe'
}
};
mockItemRequestCreate.mockResolvedValue(mockCreatedRequest);
mockItemRequestFindByPk.mockResolvedValue(mockRequestWithRequester);
const response = await request(app)
.post('/item-requests')
.send(requestData);
expect(response.status).toBe(201);
expect(response.body).toEqual(mockRequestWithRequester);
expect(mockItemRequestCreate).toHaveBeenCalledWith({
...requestData,
requesterId: 1
});
});
it('should handle database errors during creation', async () => {
mockItemRequestCreate.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.post('/item-requests')
.send({
title: 'Test Request',
description: 'Test description'
});
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('PUT /:id', () => {
const mockRequest = {
id: 1,
title: 'Original Title',
requesterId: 1,
update: jest.fn()
};
beforeEach(() => {
mockItemRequestFindByPk.mockResolvedValue(mockRequest);
});
it('should update item request for owner', async () => {
const updateData = {
title: 'Updated Title',
description: 'Updated description'
};
const mockUpdatedRequest = {
...mockRequest,
...updateData,
requester: {
id: 1,
username: 'john_doe',
firstName: 'John',
lastName: 'Doe'
}
};
mockRequest.update.mockResolvedValue();
mockItemRequestFindByPk
.mockResolvedValueOnce(mockRequest)
.mockResolvedValueOnce(mockUpdatedRequest);
const response = await request(app)
.put('/item-requests/1')
.send(updateData);
expect(response.status).toBe(200);
expect(response.body).toEqual({
id: 1,
title: 'Updated Title',
description: 'Updated description',
requesterId: 1,
requester: {
id: 1,
username: 'john_doe',
firstName: 'John',
lastName: 'Doe'
}
});
expect(mockRequest.update).toHaveBeenCalledWith(updateData);
});
it('should return 404 for non-existent request', async () => {
mockItemRequestFindByPk.mockResolvedValue(null);
const response = await request(app)
.put('/item-requests/999')
.send({ title: 'Updated' });
expect(response.status).toBe(404);
expect(response.body).toEqual({ error: 'Item request not found' });
});
it('should return 403 for unauthorized user', async () => {
const unauthorizedRequest = { ...mockRequest, requesterId: 2 };
mockItemRequestFindByPk.mockResolvedValue(unauthorizedRequest);
const response = await request(app)
.put('/item-requests/1')
.send({ title: 'Updated' });
expect(response.status).toBe(403);
expect(response.body).toEqual({ error: 'Unauthorized' });
});
it('should handle database errors', async () => {
mockItemRequestFindByPk.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.put('/item-requests/1')
.send({ title: 'Updated' });
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('DELETE /:id', () => {
const mockRequest = {
id: 1,
requesterId: 1,
destroy: jest.fn()
};
beforeEach(() => {
mockItemRequestFindByPk.mockResolvedValue(mockRequest);
});
it('should delete item request for owner', async () => {
mockRequest.destroy.mockResolvedValue();
const response = await request(app)
.delete('/item-requests/1');
expect(response.status).toBe(204);
expect(mockRequest.destroy).toHaveBeenCalled();
});
it('should return 404 for non-existent request', async () => {
mockItemRequestFindByPk.mockResolvedValue(null);
const response = await request(app)
.delete('/item-requests/999');
expect(response.status).toBe(404);
expect(response.body).toEqual({ error: 'Item request not found' });
});
it('should return 403 for unauthorized user', async () => {
const unauthorizedRequest = { ...mockRequest, requesterId: 2 };
mockItemRequestFindByPk.mockResolvedValue(unauthorizedRequest);
const response = await request(app)
.delete('/item-requests/1');
expect(response.status).toBe(403);
expect(response.body).toEqual({ error: 'Unauthorized' });
});
it('should handle database errors', async () => {
mockItemRequestFindByPk.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.delete('/item-requests/1');
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('POST /:id/responses', () => {
const mockRequest = {
id: 1,
requesterId: 2,
status: 'open',
increment: jest.fn()
};
const mockResponseData = {
message: 'I have a drill you can borrow',
price: 25,
existingItemId: 5
};
const mockCreatedResponse = {
id: 1,
...mockResponseData,
itemRequestId: 1,
responderId: 1
};
const mockResponseWithDetails = {
...mockCreatedResponse,
responder: {
id: 1,
username: 'john_doe',
firstName: 'John',
lastName: 'Doe'
},
existingItem: {
id: 5,
name: 'Power Drill',
description: 'Cordless power drill'
}
};
beforeEach(() => {
mockItemRequestFindByPk.mockResolvedValue(mockRequest);
mockItemRequestResponseCreate.mockResolvedValue(mockCreatedResponse);
mockItemRequestResponseFindByPk.mockResolvedValue(mockResponseWithDetails);
});
it('should create a response to item request', async () => {
mockRequest.increment.mockResolvedValue();
const response = await request(app)
.post('/item-requests/1/responses')
.send(mockResponseData);
expect(response.status).toBe(201);
expect(response.body).toEqual(mockResponseWithDetails);
expect(mockItemRequestResponseCreate).toHaveBeenCalledWith({
...mockResponseData,
itemRequestId: '1',
responderId: 1
});
expect(mockRequest.increment).toHaveBeenCalledWith('responseCount');
});
it('should return 404 for non-existent request', async () => {
mockItemRequestFindByPk.mockResolvedValue(null);
const response = await request(app)
.post('/item-requests/999/responses')
.send(mockResponseData);
expect(response.status).toBe(404);
expect(response.body).toEqual({ error: 'Item request not found' });
});
it('should prevent responding to own request', async () => {
const ownRequest = { ...mockRequest, requesterId: 1 };
mockItemRequestFindByPk.mockResolvedValue(ownRequest);
const response = await request(app)
.post('/item-requests/1/responses')
.send(mockResponseData);
expect(response.status).toBe(400);
expect(response.body).toEqual({ error: 'Cannot respond to your own request' });
});
it('should prevent responding to closed request', async () => {
const closedRequest = { ...mockRequest, status: 'fulfilled' };
mockItemRequestFindByPk.mockResolvedValue(closedRequest);
const response = await request(app)
.post('/item-requests/1/responses')
.send(mockResponseData);
expect(response.status).toBe(400);
expect(response.body).toEqual({ error: 'Cannot respond to closed request' });
});
it('should handle database errors', async () => {
mockItemRequestResponseCreate.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.post('/item-requests/1/responses')
.send(mockResponseData);
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('PUT /responses/:responseId/status', () => {
const mockResponse = {
id: 1,
status: 'pending',
itemRequest: {
id: 1,
requesterId: 1
},
update: jest.fn()
};
beforeEach(() => {
mockItemRequestResponseFindByPk.mockResolvedValue(mockResponse);
});
it('should update response status to accepted and fulfill request', async () => {
const updatedResponse = {
...mockResponse,
status: 'accepted',
responder: {
id: 2,
username: 'jane_doe',
firstName: 'Jane',
lastName: 'Doe'
},
existingItem: null
};
mockResponse.update.mockResolvedValue();
mockResponse.itemRequest.update = jest.fn().mockResolvedValue();
mockItemRequestResponseFindByPk
.mockResolvedValueOnce(mockResponse)
.mockResolvedValueOnce(updatedResponse);
const response = await request(app)
.put('/item-requests/responses/1/status')
.send({ status: 'accepted' });
expect(response.status).toBe(200);
expect(response.body).toEqual({
id: 1,
status: 'accepted',
itemRequest: {
id: 1,
requesterId: 1
},
responder: {
id: 2,
username: 'jane_doe',
firstName: 'Jane',
lastName: 'Doe'
},
existingItem: null
});
expect(mockResponse.update).toHaveBeenCalledWith({ status: 'accepted' });
expect(mockResponse.itemRequest.update).toHaveBeenCalledWith({ status: 'fulfilled' });
});
it('should update response status without fulfilling request', async () => {
const updatedResponse = { ...mockResponse, status: 'declined' };
mockResponse.update.mockResolvedValue();
mockItemRequestResponseFindByPk
.mockResolvedValueOnce(mockResponse)
.mockResolvedValueOnce(updatedResponse);
const response = await request(app)
.put('/item-requests/responses/1/status')
.send({ status: 'declined' });
expect(response.status).toBe(200);
expect(mockResponse.update).toHaveBeenCalledWith({ status: 'declined' });
expect(mockResponse.itemRequest.update).not.toHaveBeenCalled();
});
it('should return 404 for non-existent response', async () => {
mockItemRequestResponseFindByPk.mockResolvedValue(null);
const response = await request(app)
.put('/item-requests/responses/999/status')
.send({ status: 'accepted' });
expect(response.status).toBe(404);
expect(response.body).toEqual({ error: 'Response not found' });
});
it('should return 403 for unauthorized user', async () => {
const unauthorizedResponse = {
...mockResponse,
itemRequest: { ...mockResponse.itemRequest, requesterId: 2 }
};
mockItemRequestResponseFindByPk.mockResolvedValue(unauthorizedResponse);
const response = await request(app)
.put('/item-requests/responses/1/status')
.send({ status: 'accepted' });
expect(response.status).toBe(403);
expect(response.body).toEqual({ error: 'Only the requester can update response status' });
});
it('should handle database errors', async () => {
mockItemRequestResponseFindByPk.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.put('/item-requests/responses/1/status')
.send({ status: 'accepted' });
expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Database error' });
});
});
describe('Edge cases', () => {
it('should handle empty search results', async () => {
mockItemRequestFindAndCountAll.mockResolvedValue({ count: 0, rows: [] });
const response = await request(app)
.get('/item-requests?search=nonexistent');
expect(response.status).toBe(200);
expect(response.body.requests).toEqual([]);
expect(response.body.totalRequests).toBe(0);
});
it('should handle zero page calculation', async () => {
mockItemRequestFindAndCountAll.mockResolvedValue({ count: 0, rows: [] });
const response = await request(app)
.get('/item-requests');
expect(response.body.totalPages).toBe(0);
});
it('should handle request without optional fields', async () => {
const minimalRequest = {
title: 'Basic Request',
description: 'Simple description'
};
const mockCreated = { id: 1, ...minimalRequest, requesterId: 1 };
const mockWithRequester = {
...mockCreated,
requester: { id: 1, username: 'test' }
};
mockItemRequestCreate.mockResolvedValue(mockCreated);
mockItemRequestFindByPk.mockResolvedValue(mockWithRequester);
const response = await request(app)
.post('/item-requests')
.send(minimalRequest);
expect(response.status).toBe(201);
expect(mockItemRequestCreate).toHaveBeenCalledWith({
...minimalRequest,
requesterId: 1
});
});
});
});