- Add try-catch around JSON.parse with fail(400) for malformed input - Validate effort against allowed values ['Easy','Medium','Hard'] - Fix NaN risk: Number(serves)||undefined instead of Number(serves) - Add action tests for create/update: validation, JSON.parse crash, success, API error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
vi.mock('$env/dynamic/private', () => ({
|
|
env: { BACKEND_URL: 'http://localhost:8080' }
|
|
}));
|
|
|
|
const mockGet = vi.fn();
|
|
const mockPost = vi.fn();
|
|
vi.mock('$lib/server/api', () => ({
|
|
apiClient: () => ({ GET: mockGet, POST: mockPost })
|
|
}));
|
|
|
|
describe('new recipe page — load', () => {
|
|
let load: any;
|
|
|
|
beforeEach(async () => {
|
|
mockGet.mockReset();
|
|
mockPost.mockReset();
|
|
vi.resetModules();
|
|
const mod = await import('./+page.server');
|
|
load = mod.load;
|
|
});
|
|
|
|
const mockTags = [
|
|
{ id: 't1', name: 'Pasta', tagType: 'category' },
|
|
{ id: 't2', name: 'Fleisch', tagType: 'category' }
|
|
];
|
|
|
|
it('fetches tags from GET /v1/tags', async () => {
|
|
mockGet.mockResolvedValue({ data: mockTags, error: undefined });
|
|
await load({ fetch: vi.fn() } as any);
|
|
expect(mockGet).toHaveBeenCalledWith('/v1/tags', expect.anything());
|
|
});
|
|
|
|
it('returns categories filtered from tags', async () => {
|
|
mockGet.mockResolvedValue({ data: mockTags, error: undefined });
|
|
const result = await load({ fetch: vi.fn() } as any);
|
|
expect(result.categories).toHaveLength(2);
|
|
expect(result.categories[0].name).toBe('Pasta');
|
|
});
|
|
|
|
it('returns empty categories when API fails', async () => {
|
|
mockGet.mockResolvedValue({ data: undefined, error: { status: 500 } });
|
|
const result = await load({ fetch: vi.fn() } as any);
|
|
expect(result.categories).toEqual([]);
|
|
});
|
|
|
|
it('returns null recipe for new form', async () => {
|
|
mockGet.mockResolvedValue({ data: mockTags, error: undefined });
|
|
const result = await load({ fetch: vi.fn() } as any);
|
|
expect(result.recipe).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('new recipe page — create action', () => {
|
|
let actions: any;
|
|
|
|
const makeFormData = (overrides: Record<string, string | string[]> = {}) => {
|
|
const base: Record<string, string | string[]> = {
|
|
name: 'Test Rezept',
|
|
effort: 'Easy',
|
|
tagIds: ['t1'],
|
|
ingredientsJson: '[]',
|
|
stepsJson: '[]',
|
|
...overrides
|
|
};
|
|
const fd = new FormData();
|
|
for (const [key, val] of Object.entries(base)) {
|
|
if (Array.isArray(val)) {
|
|
for (const v of val) fd.append(key, v);
|
|
} else {
|
|
fd.append(key, val);
|
|
}
|
|
}
|
|
return fd;
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
mockGet.mockReset();
|
|
mockPost.mockReset();
|
|
vi.resetModules();
|
|
const mod = await import('./+page.server');
|
|
actions = mod.actions;
|
|
});
|
|
|
|
it('returns fail(422) when name is missing', async () => {
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData({ name: '' }) },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(422);
|
|
});
|
|
|
|
it('returns fail(422) when effort is missing', async () => {
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData({ effort: '' }) },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(422);
|
|
});
|
|
|
|
it('returns fail(422) when effort is not a valid value', async () => {
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData({ effort: 'InvalidEffort' }) },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(422);
|
|
});
|
|
|
|
it('returns fail(422) when no tagIds', async () => {
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData({ tagIds: [] }) },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(422);
|
|
});
|
|
|
|
it('returns fail(400) when ingredientsJson is invalid JSON', async () => {
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData({ ingredientsJson: 'not-json' }) },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(400);
|
|
});
|
|
|
|
it('returns fail(400) when stepsJson is invalid JSON', async () => {
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData({ stepsJson: '{broken' }) },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(400);
|
|
});
|
|
|
|
it('calls POST /v1/recipes with correct body on success', async () => {
|
|
mockPost.mockResolvedValue({ error: undefined });
|
|
const fd = makeFormData({
|
|
ingredientsJson: JSON.stringify([{ name: 'Spaghetti', quantity: 200, unit: 'g' }]),
|
|
stepsJson: JSON.stringify(['Kochen'])
|
|
});
|
|
await actions.create({ request: { formData: async () => fd }, fetch: vi.fn() } as any).catch(
|
|
() => {}
|
|
);
|
|
expect(mockPost).toHaveBeenCalledWith('/v1/recipes', expect.objectContaining({ body: expect.objectContaining({ name: 'Test Rezept', effort: 'Easy' }) }));
|
|
});
|
|
|
|
it('returns fail(500) when API returns error', async () => {
|
|
mockPost.mockResolvedValue({ error: { status: 500 } });
|
|
const result = await actions.create({
|
|
request: { formData: async () => makeFormData() },
|
|
fetch: vi.fn()
|
|
} as any);
|
|
expect(result.status).toBe(500);
|
|
});
|
|
});
|