feat(planner): add server.test.ts for GET /planner, fix sort + add error handling

- Sort uses scoreDelta instead of removed simulatedScore
- try/catch degrades gracefully to suggestions=[] on backend errors
- 6 tests cover: missing params, success, backend error, network throw, empty result

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 11:39:50 +02:00
committed by marcel
parent 8234c2f162
commit a751b0758a
2 changed files with 103 additions and 8 deletions

View File

@@ -11,14 +11,18 @@ export const GET: RequestHandler = async ({ fetch, url }) => {
return json({ suggestions: [] }); return json({ suggestions: [] });
} }
const api = apiClient(fetch); try {
const { data } = await api.GET('/v1/week-plans/{id}/suggestions', { const api = apiClient(fetch);
params: { path: { id: planId }, query: { slotDate: date } } const { data } = await api.GET('/v1/week-plans/{id}/suggestions', {
}); params: { path: { id: planId }, query: { slotDate: date } }
});
const suggestions = (data?.suggestions ?? []).sort( const suggestions = (data?.suggestions ?? []).sort(
(a: any, b: any) => (b.simulatedScore ?? 0) - (a.simulatedScore ?? 0) (a: any, b: any) => (b.scoreDelta ?? 0) - (a.scoreDelta ?? 0)
); );
return json({ suggestions }); return json({ suggestions });
} catch {
return json({ suggestions: [] });
}
}; };

View File

@@ -0,0 +1,91 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
vi.mock('$env/dynamic/private', () => ({
env: { BACKEND_URL: 'http://localhost:8080' }
}));
const mockGet = vi.fn();
vi.mock('$lib/server/api', () => ({
apiClient: () => ({ GET: mockGet })
}));
const PLAN_UUID = '11111111-1111-1111-1111-111111111111';
const DATE = '2026-04-09';
const mockSuggestions = [
{ recipe: { id: 'r1', name: 'Lachsfilet', effort: 'easy', cookTimeMin: 25 }, scoreDelta: 0.0, hasConflict: true },
{ recipe: { id: 'r2', name: 'Nudeln', effort: 'easy', cookTimeMin: 20 }, scoreDelta: -1.5, hasConflict: true }
];
describe('GET /planner — suggestions route handler', () => {
let GET: any;
beforeEach(async () => {
mockGet.mockReset();
vi.resetModules();
const mod = await import('./+server');
GET = mod.GET;
});
it('returns { suggestions: [] } when planId is missing', async () => {
const url = new URL('http://localhost/planner?date=' + DATE);
const response = await GET({ fetch: vi.fn(), url });
const body = await response.json();
expect(body).toEqual({ suggestions: [] });
expect(mockGet).not.toHaveBeenCalled();
});
it('returns { suggestions: [] } when date is missing', async () => {
const url = new URL('http://localhost/planner?planId=' + PLAN_UUID);
const response = await GET({ fetch: vi.fn(), url });
const body = await response.json();
expect(body).toEqual({ suggestions: [] });
expect(mockGet).not.toHaveBeenCalled();
});
it('returns sorted suggestions from backend on success', async () => {
mockGet.mockResolvedValueOnce({ data: { suggestions: mockSuggestions }, error: undefined });
const url = new URL(`http://localhost/planner?planId=${PLAN_UUID}&date=${DATE}`);
const response = await GET({ fetch: vi.fn(), url });
const body = await response.json();
expect(mockGet).toHaveBeenCalledWith('/v1/week-plans/{id}/suggestions', expect.objectContaining({
params: { path: { id: PLAN_UUID }, query: { slotDate: DATE } }
}));
expect(body.suggestions).toHaveLength(2);
// sorted by scoreDelta desc: 0.0 before -1.5
expect(body.suggestions[0].recipe.name).toBe('Lachsfilet');
expect(body.suggestions[1].recipe.name).toBe('Nudeln');
});
it('returns { suggestions: [] } when backend returns error', async () => {
mockGet.mockResolvedValueOnce({ data: undefined, error: { status: 500 } });
const url = new URL(`http://localhost/planner?planId=${PLAN_UUID}&date=${DATE}`);
const response = await GET({ fetch: vi.fn(), url });
const body = await response.json();
expect(body).toEqual({ suggestions: [] });
});
it('returns { suggestions: [] } when backend throws (network error)', async () => {
mockGet.mockRejectedValueOnce(new Error('Network error'));
const url = new URL(`http://localhost/planner?planId=${PLAN_UUID}&date=${DATE}`);
const response = await GET({ fetch: vi.fn(), url });
const body = await response.json();
expect(body).toEqual({ suggestions: [] });
});
it('returns empty suggestions when backend returns empty array', async () => {
mockGet.mockResolvedValueOnce({ data: { suggestions: [] }, error: undefined });
const url = new URL(`http://localhost/planner?planId=${PLAN_UUID}&date=${DATE}`);
const response = await GET({ fetch: vi.fn(), url });
const body = await response.json();
expect(body).toEqual({ suggestions: [] });
});
});