← back to Jill Website
src/__tests__/routes/inquiries.test.ts
508 lines
import request from 'supertest';
import { Express } from 'express';
// Mock database before imports
jest.mock('../../config/database', () => ({
getPool: jest.fn(() => ({
query: jest.fn(),
connect: jest.fn()
})),
testConnection: jest.fn(),
closePool: jest.fn()
}));
// Mock models
jest.mock('../../models/Inquiry');
import { InquiryModel } from '../../models/Inquiry';
import { createTestApp } from './test-app';
describe('Inquiries API Endpoints', () => {
let app: Express;
let mockInquiryModel: jest.Mocked<InquiryModel>;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Setup mock inquiry model
mockInquiryModel = {
create: jest.fn(),
findAll: jest.fn(),
findById: jest.fn(),
update: jest.fn(),
delete: jest.fn()
} as any;
// Create test app with mock model
app = createTestApp(mockInquiryModel, undefined);
});
describe('POST /api/inquiries', () => {
it('should create inquiry with valid data and return 201', async () => {
const validInquiry = {
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: '2026-06-01',
check_out_date: '2026-06-07',
guest_count: 4,
message: 'Looking forward to staying at your property!'
};
const createdInquiry = {
id: 1,
...validInquiry,
check_in_date: new Date('2026-06-01'),
check_out_date: new Date('2026-06-07'),
created_at: new Date(),
updated_at: new Date()
};
mockInquiryModel.create.mockResolvedValue(createdInquiry);
const response = await request(app)
.post('/api/inquiries')
.send(validInquiry)
.expect(201);
expect(response.body).toEqual({
success: true,
data: {
id: createdInquiry.id,
inquiry: expect.objectContaining({
id: createdInquiry.id,
name: validInquiry.name,
email: validInquiry.email,
phone: validInquiry.phone,
guest_count: validInquiry.guest_count,
message: validInquiry.message
})
},
message: 'Inquiry created successfully'
});
expect(mockInquiryModel.create).toHaveBeenCalledWith(expect.objectContaining({
name: validInquiry.name,
email: validInquiry.email,
phone: validInquiry.phone,
guest_count: validInquiry.guest_count,
message: validInquiry.message
}));
});
it('should return 400 for missing required fields', async () => {
const invalidInquiry = {
name: 'John Doe',
email: 'john@example.com'
// Missing required fields
};
mockInquiryModel.create.mockRejectedValue(
new Error('Validation failed: phone: Phone number is required, check_in_date: Check-in date is required')
);
const response = await request(app)
.post('/api/inquiries')
.send(invalidInquiry)
.expect(400);
expect(response.body).toEqual({
success: false,
error: 'Validation error',
message: expect.stringContaining('Validation failed')
});
});
it('should return 400 for invalid email format', async () => {
const invalidInquiry = {
name: 'John Doe',
email: 'invalid-email',
phone: '+1-555-1234',
check_in_date: '2026-06-01',
check_out_date: '2026-06-07',
guest_count: 4,
message: 'Test message'
};
mockInquiryModel.create.mockRejectedValue(
new Error('Validation failed: email: Valid email is required')
);
const response = await request(app)
.post('/api/inquiries')
.send(invalidInquiry)
.expect(400);
expect(response.body).toEqual({
success: false,
error: 'Validation error',
message: expect.stringContaining('Validation failed')
});
});
it('should return 400 for invalid date range (checkout before checkin)', async () => {
const invalidInquiry = {
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: '2026-06-07',
check_out_date: '2026-06-01', // Before check-in
guest_count: 4,
message: 'Test message'
};
mockInquiryModel.create.mockRejectedValue(
new Error('Validation failed: check_out_date: Check-out date must be after check-in date')
);
const response = await request(app)
.post('/api/inquiries')
.send(invalidInquiry)
.expect(400);
expect(response.body).toEqual({
success: false,
error: 'Validation error',
message: expect.stringContaining('Validation failed')
});
});
it('should return 400 for invalid guest count', async () => {
const invalidInquiry = {
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: '2026-06-01',
check_out_date: '2026-06-07',
guest_count: 0, // Invalid
message: 'Test message'
};
mockInquiryModel.create.mockRejectedValue(
new Error('Validation failed: guest_count: Guest count must be at least 1')
);
const response = await request(app)
.post('/api/inquiries')
.send(invalidInquiry)
.expect(400);
expect(response.body).toEqual({
success: false,
error: 'Validation error',
message: expect.stringContaining('Validation failed')
});
});
it('should return 500 for database errors', async () => {
const validInquiry = {
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: '2026-06-01',
check_out_date: '2026-06-07',
guest_count: 4,
message: 'Test message'
};
mockInquiryModel.create.mockRejectedValue(new Error('Database connection failed'));
const response = await request(app)
.post('/api/inquiries')
.send(validInquiry)
.expect(500);
expect(response.body).toEqual({
success: false,
error: 'Internal server error',
message: 'Failed to create inquiry'
});
});
it('should handle empty request body', async () => {
mockInquiryModel.create.mockRejectedValue(
new Error('Validation failed: name: Name is required, email: Valid email is required')
);
const response = await request(app)
.post('/api/inquiries')
.send({})
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Validation error');
});
});
describe('GET /api/inquiries', () => {
it('should return all inquiries with default pagination and 200 status', async () => {
const mockInquiries = [
{
id: 1,
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: new Date('2026-06-01'),
check_out_date: new Date('2026-06-07'),
guest_count: 4,
message: 'Test message 1',
created_at: new Date(),
updated_at: new Date()
},
{
id: 2,
name: 'Jane Smith',
email: 'jane@example.com',
phone: '+1-555-5678',
check_in_date: new Date('2026-07-01'),
check_out_date: new Date('2026-07-07'),
guest_count: 2,
message: 'Test message 2',
created_at: new Date(),
updated_at: new Date()
}
];
mockInquiryModel.findAll.mockResolvedValue(mockInquiries);
const response = await request(app)
.get('/api/inquiries')
.expect(200);
expect(response.body).toEqual({
success: true,
data: expect.arrayContaining([
expect.objectContaining({ id: 1, name: 'John Doe' }),
expect.objectContaining({ id: 2, name: 'Jane Smith' })
]),
count: mockInquiries.length
});
expect(mockInquiryModel.findAll).toHaveBeenCalledWith(100, 0);
});
it('should return inquiries with custom pagination parameters', async () => {
const mockInquiries = [
{
id: 3,
name: 'Bob Wilson',
email: 'bob@example.com',
phone: '+1-555-9999',
check_in_date: new Date('2026-08-01'),
check_out_date: new Date('2026-08-07'),
guest_count: 6,
message: 'Test message 3',
created_at: new Date(),
updated_at: new Date()
}
];
mockInquiryModel.findAll.mockResolvedValue(mockInquiries);
const response = await request(app)
.get('/api/inquiries')
.query({ limit: 10, offset: 5 })
.expect(200);
expect(response.body.count).toBe(1);
expect(mockInquiryModel.findAll).toHaveBeenCalledWith(10, 5);
});
it('should return empty array when no inquiries exist', async () => {
mockInquiryModel.findAll.mockResolvedValue([]);
const response = await request(app)
.get('/api/inquiries')
.expect(200);
expect(response.body).toEqual({
success: true,
data: [],
count: 0
});
});
it('should return 500 for database errors', async () => {
mockInquiryModel.findAll.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.get('/api/inquiries')
.expect(500);
expect(response.body).toEqual({
success: false,
error: 'Internal server error',
message: 'Failed to fetch inquiries'
});
});
});
describe('GET /api/inquiries/:id', () => {
it('should return inquiry by valid ID with 200 status', async () => {
const mockInquiry = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: new Date('2026-06-01'),
check_out_date: new Date('2026-06-07'),
guest_count: 4,
message: 'Test message',
created_at: new Date(),
updated_at: new Date()
};
mockInquiryModel.findById.mockResolvedValue(mockInquiry);
const response = await request(app)
.get('/api/inquiries/1')
.expect(200);
expect(response.body).toEqual({
success: true,
data: expect.objectContaining({
id: 1,
name: 'John Doe',
email: 'john@example.com'
})
});
expect(mockInquiryModel.findById).toHaveBeenCalledWith(1);
});
it('should return 400 for invalid ID format', async () => {
const response = await request(app)
.get('/api/inquiries/invalid')
.expect(400);
expect(response.body).toEqual({
success: false,
error: 'Invalid ID',
message: 'Inquiry ID must be a number'
});
expect(mockInquiryModel.findById).not.toHaveBeenCalled();
});
it('should return 404 for non-existent inquiry', async () => {
mockInquiryModel.findById.mockResolvedValue(null);
const response = await request(app)
.get('/api/inquiries/999')
.expect(404);
expect(response.body).toEqual({
success: false,
error: 'Not found',
message: 'Inquiry not found'
});
expect(mockInquiryModel.findById).toHaveBeenCalledWith(999);
});
it('should return 500 for database errors', async () => {
mockInquiryModel.findById.mockRejectedValue(new Error('Database error'));
const response = await request(app)
.get('/api/inquiries/1')
.expect(500);
expect(response.body).toEqual({
success: false,
error: 'Internal server error',
message: 'Failed to fetch inquiry'
});
});
});
describe('Response Structure Validation', () => {
it('should have correct structure for successful POST response', async () => {
const validInquiry = {
name: 'John Doe',
email: 'john@example.com',
phone: '+1-555-1234',
check_in_date: '2026-06-01',
check_out_date: '2026-06-07',
guest_count: 4,
message: 'Test'
};
const createdInquiry = {
id: 1,
...validInquiry,
check_in_date: new Date('2026-06-01'),
check_out_date: new Date('2026-06-07'),
created_at: new Date(),
updated_at: new Date()
};
mockInquiryModel.create.mockResolvedValue(createdInquiry);
const response = await request(app)
.post('/api/inquiries')
.send(validInquiry);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('data');
expect(response.body.data).toHaveProperty('id');
expect(response.body.data).toHaveProperty('inquiry');
expect(response.body).toHaveProperty('message');
});
it('should have correct structure for error responses', async () => {
mockInquiryModel.create.mockRejectedValue(
new Error('Validation failed: name: Name is required')
);
const response = await request(app)
.post('/api/inquiries')
.send({});
expect(response.body).toHaveProperty('success', false);
expect(response.body).toHaveProperty('error');
expect(response.body).toHaveProperty('message');
});
it('should verify response status codes are correct', async () => {
// Test 201 for successful creation
mockInquiryModel.create.mockResolvedValue({
id: 1,
name: 'Test',
email: 'test@example.com',
phone: '+1-555-1234',
check_in_date: new Date('2026-06-01'),
check_out_date: new Date('2026-06-07'),
guest_count: 4,
message: 'Test',
created_at: new Date(),
updated_at: new Date()
});
await request(app)
.post('/api/inquiries')
.send({
name: 'Test',
email: 'test@example.com',
phone: '+1-555-1234',
check_in_date: '2026-06-01',
check_out_date: '2026-06-07',
guest_count: 4,
message: 'Test'
})
.expect(201);
// Test 200 for GET
mockInquiryModel.findAll.mockResolvedValue([]);
await request(app).get('/api/inquiries').expect(200);
// Test 404 for not found
mockInquiryModel.findById.mockResolvedValue(null);
await request(app).get('/api/inquiries/999').expect(404);
// Test 400 for invalid ID
await request(app).get('/api/inquiries/invalid').expect(400);
});
});
});