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'); }); });