IcostPro/__tests__/api/ingredients-id.test.ts
2026-02-12 21:40:57 +01:00

194 lines
6.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { GET, PUT, DELETE } from '@/app/api/ingredients/[id]/route';
const mockNext = vi.fn();
const mockAggregate = vi.fn(() => ({ next: mockNext }));
const mockFindOneAndUpdate = vi.fn();
const mockDeleteOne = vi.fn();
const mockCollection = vi.fn(() => ({
aggregate: mockAggregate,
findOneAndUpdate: mockFindOneAndUpdate,
deleteOne: mockDeleteOne,
}));
const mockDb = { collection: mockCollection };
vi.mock('@/lib/mongodb', () => ({
getDb: vi.fn(() => Promise.resolve(mockDb)),
}));
const validId = '507f1f77bcf86cd799439011';
function makeParams(id: string) {
return { params: Promise.resolve({ id }) };
}
describe('GET /api/ingredients/[id]', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns single ingredient with lookups', async () => {
const ingredient = {
_id: validId,
code: 'ING-001',
name: 'Butter',
category: 'Dairy',
subcategory: 'Fresh',
supplier: 'Acme',
};
mockNext.mockResolvedValue(ingredient);
const request = new Request(`http://localhost/api/ingredients/${validId}`);
const response = await GET(request, makeParams(validId));
const data = await response.json();
expect(response.status).toBe(200);
expect(data.name).toBe('Butter');
expect(data.category).toBe('Dairy');
});
it('returns 400 for invalid ID format', async () => {
const request = new Request('http://localhost/api/ingredients/invalid-id');
const response = await GET(request, makeParams('invalid-id'));
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe('Invalid ID');
});
it('returns 404 for non-existent ID', async () => {
mockNext.mockResolvedValue(null);
const request = new Request(`http://localhost/api/ingredients/${validId}`);
const response = await GET(request, makeParams(validId));
const data = await response.json();
expect(response.status).toBe(404);
expect(data.error).toBe('Ingredient not found');
});
});
describe('PUT /api/ingredients/[id]', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('updates specific fields and sets updatedAt', async () => {
const updated = { _id: validId, name: 'Updated Butter', updatedAt: new Date() };
mockFindOneAndUpdate.mockResolvedValue(updated);
const request = new Request(`http://localhost/api/ingredients/${validId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Updated Butter' }),
});
const response = await PUT(request, makeParams(validId));
const data = await response.json();
expect(response.status).toBe(200);
expect(data.name).toBe('Updated Butter');
const updateArg = mockFindOneAndUpdate.mock.calls[0][1].$set;
expect(updateArg.name).toBe('Updated Butter');
expect(updateArg.updatedAt).toBeDefined();
});
it('returns 400 for invalid ID', async () => {
const request = new Request('http://localhost/api/ingredients/bad', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'x' }),
});
const response = await PUT(request, makeParams('bad'));
expect(response.status).toBe(400);
});
it('returns 404 for non-existent ID', async () => {
mockFindOneAndUpdate.mockResolvedValue(null);
const request = new Request(`http://localhost/api/ingredients/${validId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'x' }),
});
const response = await PUT(request, makeParams(validId));
expect(response.status).toBe(404);
});
it('empty body only sets updatedAt', async () => {
const updated = { _id: validId, updatedAt: new Date() };
mockFindOneAndUpdate.mockResolvedValue(updated);
const request = new Request(`http://localhost/api/ingredients/${validId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
});
const response = await PUT(request, makeParams(validId));
expect(response.status).toBe(200);
const updateArg = mockFindOneAndUpdate.mock.calls[0][1].$set;
expect(Object.keys(updateArg)).toEqual(['updatedAt']);
});
it('converts ObjectId fields (categoryId, subcategoryId, supplierId)', async () => {
const catId = '507f1f77bcf86cd799439012';
const subId = '507f1f77bcf86cd799439013';
const supId = '507f1f77bcf86cd799439014';
mockFindOneAndUpdate.mockResolvedValue({ _id: validId });
const request = new Request(`http://localhost/api/ingredients/${validId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ categoryId: catId, subcategoryId: subId, supplierId: supId }),
});
const response = await PUT(request, makeParams(validId));
expect(response.status).toBe(200);
const updateArg = mockFindOneAndUpdate.mock.calls[0][1].$set;
expect(updateArg.categoryId.toString()).toBe(catId);
expect(updateArg.subcategoryId.toString()).toBe(subId);
expect(updateArg.supplierId.toString()).toBe(supId);
});
});
describe('DELETE /api/ingredients/[id]', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('removes ingredient and returns success', async () => {
mockDeleteOne.mockResolvedValue({ deletedCount: 1 });
const request = new Request(`http://localhost/api/ingredients/${validId}`, { method: 'DELETE' });
const response = await DELETE(request, makeParams(validId));
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
});
it('returns 400 for invalid ID', async () => {
const request = new Request('http://localhost/api/ingredients/bad', { method: 'DELETE' });
const response = await DELETE(request, makeParams('bad'));
expect(response.status).toBe(400);
});
it('returns 404 for non-existent ID', async () => {
mockDeleteOne.mockResolvedValue({ deletedCount: 0 });
const request = new Request(`http://localhost/api/ingredients/${validId}`, { method: 'DELETE' });
const response = await DELETE(request, makeParams(validId));
const data = await response.json();
expect(response.status).toBe(404);
expect(data.error).toBe('Ingredient not found');
});
});