194 lines
6.3 KiB
TypeScript
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');
|
|
});
|
|
});
|