← back to Wine Finder Next
__tests__/integration/api-bottles.test.ts
260 lines
// Integration Tests for Bottles API
import { NextRequest } from 'next/server';
import { GET, POST } from '@/app/api/membership/bottles/route';
// Mock the dependencies
jest.mock('@/lib/membershipDatabase');
jest.mock('@/lib/rateLimit');
jest.mock('@/lib/auth');
jest.mock('@/lib/securityMonitoring');
import { bottleDb } from '@/lib/membershipDatabase';
import { checkRateLimit, getClientIdentifier } from '@/lib/rateLimit';
import { logAudit } from '@/lib/auth';
import { analyzeRequest } from '@/lib/securityMonitoring';
describe('Bottles API Integration Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
(checkRateLimit as jest.Mock).mockReturnValue({
success: true,
limit: 100,
remaining: 99,
reset: Date.now() + 60000,
});
(getClientIdentifier as jest.Mock).mockReturnValue('127.0.0.1');
(logAudit as jest.Mock).mockReturnValue(undefined);
(analyzeRequest as jest.Mock).mockReturnValue({ safe: true, threats: [] });
});
describe('GET /api/membership/bottles', () => {
it('should return all bottles successfully', async () => {
const mockBottles = [
{
id: 'bottle_1',
name: 'Test Wine 1',
vintage: 2010,
producer: 'Test Producer',
region: 'Test Region',
size: '750ml',
totalMembershipUnits: 1000,
availableMembershipUnits: 500,
estimatedValue: 50000,
provenance: 'Test',
status: 'active',
createdAt: new Date(),
updatedAt: new Date(),
},
];
(bottleDb.getAll as jest.Mock).mockReturnValue(mockBottles);
const request = new NextRequest('http://localhost:3000/api/membership/bottles');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.data).toHaveLength(1);
expect(data.data[0].name).toBe('Test Wine 1');
});
it('should handle rate limiting', async () => {
(checkRateLimit as jest.Mock).mockReturnValue({
success: false,
limit: 100,
remaining: 0,
reset: Date.now() + 60000,
});
const request = new NextRequest('http://localhost:3000/api/membership/bottles');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(429);
expect(data.success).toBe(false);
expect(data.error).toContain('Rate limit exceeded');
});
it('should include rate limit headers', async () => {
(bottleDb.getAll as jest.Mock).mockReturnValue([]);
const request = new NextRequest('http://localhost:3000/api/membership/bottles');
const response = await GET(request);
expect(response.headers.get('X-RateLimit-Limit')).toBe('100');
expect(response.headers.get('X-RateLimit-Remaining')).toBe('99');
expect(response.headers.has('X-RateLimit-Reset')).toBe(true);
});
it('should log audit trail', async () => {
(bottleDb.getAll as jest.Mock).mockReturnValue([]);
const request = new NextRequest('http://localhost:3000/api/membership/bottles');
await GET(request);
expect(logAudit).toHaveBeenCalledWith({
action: 'BOTTLES_LIST',
resource: 'bottles',
ipAddress: '127.0.0.1',
userAgent: expect.any(String),
success: true,
metadata: { count: 0 },
});
});
});
describe('POST /api/membership/bottles', () => {
it('should create a new bottle successfully', async () => {
const newBottle = {
id: 'bottle_new',
name: 'New Test Wine',
vintage: 2015,
producer: 'New Producer',
region: 'New Region',
size: '750ml',
totalMembershipUnits: 1000,
availableMembershipUnits: 1000,
estimatedValue: 75000,
provenance: 'New provenance',
status: 'active',
createdAt: new Date(),
updatedAt: new Date(),
};
(bottleDb.create as jest.Mock).mockReturnValue(newBottle);
const requestBody = {
name: 'New Test Wine',
vintage: 2015,
producer: 'New Producer',
region: 'New Region',
size: '750ml',
totalMembershipUnits: 1000,
availableMembershipUnits: 1000,
estimatedValue: 75000,
provenance: 'New provenance',
status: 'active',
};
const request = new NextRequest('http://localhost:3000/api/membership/bottles', {
method: 'POST',
body: JSON.stringify(requestBody),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.data.name).toBe('New Test Wine');
expect(data.message).toBe('Bottle created successfully');
});
it('should detect security threats', async () => {
(analyzeRequest as jest.Mock).mockReturnValue({
safe: false,
threats: ['XSS_DETECTED'],
});
const maliciousBody = {
name: '<script>alert("XSS")</script>',
vintage: 2015,
producer: 'Test',
region: 'Test',
size: '750ml',
totalMembershipUnits: 1000,
availableMembershipUnits: 1000,
estimatedValue: 75000,
provenance: 'Test',
status: 'active',
};
const request = new NextRequest('http://localhost:3000/api/membership/bottles', {
method: 'POST',
body: JSON.stringify(maliciousBody),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(403);
expect(data.success).toBe(false);
expect(data.error).toContain('Security threat detected');
});
it('should sanitize input strings', async () => {
const newBottle = {
id: 'bottle_sanitized',
name: 'Test Wine',
vintage: 2015,
producer: 'Test',
region: 'Test',
size: '750ml',
totalMembershipUnits: 1000,
availableMembershipUnits: 1000,
estimatedValue: 75000,
provenance: 'Test',
status: 'active',
createdAt: new Date(),
updatedAt: new Date(),
};
(bottleDb.create as jest.Mock).mockImplementation((data) => ({
...newBottle,
name: data.name,
}));
const requestBody = {
name: '<script>Test Wine</script>',
vintage: 2015,
producer: 'Test',
region: 'Test',
size: '750ml',
totalMembershipUnits: 1000,
availableMembershipUnits: 1000,
estimatedValue: 75000,
provenance: 'Test',
status: 'active',
};
const request = new NextRequest('http://localhost:3000/api/membership/bottles', {
method: 'POST',
body: JSON.stringify(requestBody),
});
await POST(request);
// Verify sanitization was applied
expect(bottleDb.create).toHaveBeenCalledWith(
expect.objectContaining({
name: expect.not.stringContaining('<script>'),
})
);
});
it('should handle rate limiting on POST', async () => {
(checkRateLimit as jest.Mock).mockReturnValue({
success: false,
limit: 100,
remaining: 0,
reset: Date.now() + 60000,
});
const request = new NextRequest('http://localhost:3000/api/membership/bottles', {
method: 'POST',
body: JSON.stringify({ name: 'Test' }),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(429);
expect(data.success).toBe(false);
});
});
});