118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({
|
|
mockGet: vi.fn(),
|
|
mockPost: vi.fn(),
|
|
mockPatch: vi.fn(),
|
|
mockDelete: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../../src/services/api-client', () => ({
|
|
apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete },
|
|
}));
|
|
|
|
import {
|
|
listRecipes,
|
|
getRecipe,
|
|
createRecipe,
|
|
updateRecipe,
|
|
deleteRecipe,
|
|
scaleRecipe,
|
|
importRecipeFromText,
|
|
importRecipeFromUrl,
|
|
listRecipesByProduct,
|
|
} from '../../src/services/recipes';
|
|
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
describe('recipes service', () => {
|
|
it('listRecipes with no query', async () => {
|
|
mockGet.mockResolvedValue({ data: [] });
|
|
await listRecipes('hh1');
|
|
expect(mockGet).toHaveBeenCalledWith('/households/hh1/recipes');
|
|
});
|
|
|
|
it('listRecipes builds query string', async () => {
|
|
mockGet.mockResolvedValue({ data: [] });
|
|
await listRecipes('hh1', {
|
|
q: 'pasta',
|
|
tags: 'italian',
|
|
cuisine: 'Italian',
|
|
maxCalories: 500,
|
|
isFavorite: true,
|
|
cursor: 'cur1',
|
|
limit: 10,
|
|
});
|
|
const url = mockGet.mock.calls[0][0] as string;
|
|
expect(url).toContain('q=pasta');
|
|
expect(url).toContain('tags=italian');
|
|
expect(url).toContain('cuisine=Italian');
|
|
expect(url).toContain('maxCalories=500');
|
|
expect(url).toContain('isFavorite=true');
|
|
expect(url).toContain('cursor=cur1');
|
|
expect(url).toContain('limit=10');
|
|
});
|
|
|
|
it('getRecipe calls GET', async () => {
|
|
mockGet.mockResolvedValue({ _id: 'r1' });
|
|
await getRecipe('hh1', 'r1');
|
|
expect(mockGet).toHaveBeenCalledWith('/households/hh1/recipes/r1');
|
|
});
|
|
|
|
it('createRecipe calls POST', async () => {
|
|
mockPost.mockResolvedValue({ _id: 'r1' });
|
|
const data = { name: 'Pasta' };
|
|
await createRecipe('hh1', data as never);
|
|
expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes', data);
|
|
});
|
|
|
|
it('updateRecipe calls PATCH', async () => {
|
|
mockPatch.mockResolvedValue({ _id: 'r1' });
|
|
await updateRecipe('hh1', 'r1', { name: 'Updated' } as never);
|
|
expect(mockPatch).toHaveBeenCalledWith('/households/hh1/recipes/r1', { name: 'Updated' });
|
|
});
|
|
|
|
it('deleteRecipe calls DELETE', async () => {
|
|
mockDelete.mockResolvedValue(undefined);
|
|
await deleteRecipe('hh1', 'r1');
|
|
expect(mockDelete).toHaveBeenCalledWith('/households/hh1/recipes/r1');
|
|
});
|
|
|
|
it('scaleRecipe calls POST', async () => {
|
|
mockPost.mockResolvedValue({ _id: 'r1' });
|
|
await scaleRecipe('hh1', 'r1', { targetServings: 8 });
|
|
expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes/r1/scale', {
|
|
targetServings: 8,
|
|
});
|
|
});
|
|
|
|
it('importRecipeFromText calls POST', async () => {
|
|
mockPost.mockResolvedValue({ available: false });
|
|
await importRecipeFromText('hh1', { text: 'recipe text' } as never);
|
|
expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes/import-text', {
|
|
text: 'recipe text',
|
|
});
|
|
});
|
|
|
|
it('importRecipeFromUrl calls POST', async () => {
|
|
mockPost.mockResolvedValue({ available: false });
|
|
await importRecipeFromUrl('hh1', { url: 'http://example.com' } as never);
|
|
expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes/import-url', {
|
|
url: 'http://example.com',
|
|
});
|
|
});
|
|
|
|
it('listRecipesByProduct with no query', async () => {
|
|
mockGet.mockResolvedValue({ data: [] });
|
|
await listRecipesByProduct('hh1', 'p1');
|
|
expect(mockGet).toHaveBeenCalledWith('/households/hh1/recipes/by-product/p1');
|
|
});
|
|
|
|
it('listRecipesByProduct with query', async () => {
|
|
mockGet.mockResolvedValue({ data: [] });
|
|
await listRecipesByProduct('hh1', 'p1', { cursor: 'c1', limit: 5 });
|
|
const url = mockGet.mock.calls[0][0] as string;
|
|
expect(url).toContain('cursor=c1');
|
|
expect(url).toContain('limit=5');
|
|
});
|
|
});
|