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('planner page — load', () => { let load: any; beforeEach(async () => { mockGet.mockReset(); mockPost.mockReset(); vi.resetModules(); const mod = await import('./+page.server'); load = mod.load; }); const mockWeekPlan = { id: 'plan-1', weekStart: '2026-03-30', status: 'draft', slots: [ { id: 's1', slotDate: '2026-03-30', recipe: { id: 'r1', name: 'Pasta', effort: 'Easy', cookTimeMin: 30 } }, { id: 's2', slotDate: '2026-03-31', recipe: { id: 'r2', name: 'Curry', effort: 'Medium', cookTimeMin: 45 } } ] }; it('fetches week plan for the current week by default', async () => { mockGet.mockResolvedValueOnce({ data: mockWeekPlan, error: undefined }); mockGet.mockResolvedValueOnce({ data: { score: 7.5, ingredientOverlaps: [], tagRepeats: [], recentRepeats: [], duplicatesInPlan: [] }, error: undefined }); const url = new URL('http://localhost/planner'); await load({ fetch: vi.fn(), url }); expect(mockGet).toHaveBeenCalledWith('/v1/week-plans', expect.objectContaining({ params: expect.objectContaining({ query: expect.objectContaining({ weekStart: expect.any(String) }) }) })); }); it('uses weekStart from URL search params if provided', async () => { mockGet.mockResolvedValueOnce({ data: mockWeekPlan, error: undefined }); mockGet.mockResolvedValueOnce({ data: { score: 8 }, error: undefined }); const url = new URL('http://localhost/planner?week=2026-03-30'); await load({ fetch: vi.fn(), url }); expect(mockGet).toHaveBeenCalledWith('/v1/week-plans', expect.objectContaining({ params: expect.objectContaining({ query: { weekStart: '2026-03-30' } }) })); }); it('returns weekPlan with slots in page data', async () => { mockGet.mockResolvedValueOnce({ data: mockWeekPlan, error: undefined }); mockGet.mockResolvedValueOnce({ data: { score: 7.5 }, error: undefined }); const url = new URL('http://localhost/planner'); const result = await load({ fetch: vi.fn(), url }); expect(result.weekPlan).toBeDefined(); expect(result.weekPlan.id).toBe('plan-1'); expect(result.weekPlan.slots).toHaveLength(2); }); it('returns variety score in page data', async () => { mockGet.mockResolvedValueOnce({ data: mockWeekPlan, error: undefined }); mockGet.mockResolvedValueOnce({ data: { score: 7.5, ingredientOverlaps: [{ ingredientName: 'Tomate', days: ['2026-03-30', '2026-03-31'] }] }, error: undefined }); const url = new URL('http://localhost/planner'); const result = await load({ fetch: vi.fn(), url }); expect(result.varietyScore.score).toBe(7.5); expect(result.varietyScore.ingredientOverlaps).toHaveLength(1); }); it('returns null weekPlan when API returns 404', async () => { mockGet.mockResolvedValueOnce({ data: undefined, error: { status: 404 } }); const url = new URL('http://localhost/planner'); const result = await load({ fetch: vi.fn(), url }); expect(result.weekPlan).toBeNull(); expect(result.varietyScore).toBeNull(); }); it('returns the weekStart used for the query', async () => { mockGet.mockResolvedValueOnce({ data: mockWeekPlan, error: undefined }); mockGet.mockResolvedValueOnce({ data: { score: 6 }, error: undefined }); const url = new URL('http://localhost/planner?week=2026-03-30'); const result = await load({ fetch: vi.fn(), url }); expect(result.weekStart).toBe('2026-03-30'); }); it('creates week plan if not found and fetches variety score after creation', async () => { // When no plan exists (404), load should return null weekPlan — creation is done via a POST action, not in load mockGet.mockResolvedValueOnce({ data: undefined, error: { status: 404 } }); const url = new URL('http://localhost/planner'); const result = await load({ fetch: vi.fn(), url }); expect(result.weekPlan).toBeNull(); }); }); describe('planner page — actions', () => { let actions: any; beforeEach(async () => { mockGet.mockReset(); mockPost.mockReset(); vi.resetModules(); const mod = await import('./+page.server'); actions = mod.actions; }); it('createPlan action calls POST /v1/week-plans', async () => { mockPost.mockResolvedValue({ data: { id: 'plan-new', weekStart: '2026-03-30', slots: [] }, error: undefined }); const formData = new FormData(); formData.set('weekStart', '2026-03-30'); const result = await actions.createPlan({ fetch: vi.fn(), request: { formData: async () => formData }, locals: { benutzer: { id: 'u1', name: 'Test', rolle: 'planer' }, haushalt: { id: 'h1', name: 'Test' } } }); expect(mockPost).toHaveBeenCalledWith('/v1/week-plans', expect.objectContaining({ body: { weekStart: '2026-03-30' } })); expect(result).toEqual({ success: true }); }); it('createPlan action returns error when API fails', async () => { mockPost.mockResolvedValue({ data: undefined, error: { message: 'Server error' } }); const formData = new FormData(); formData.set('weekStart', '2026-03-30'); const result = await actions.createPlan({ fetch: vi.fn(), request: { formData: async () => formData }, locals: { benutzer: { id: 'u1', name: 'Test', rolle: 'planer' }, haushalt: { id: 'h1', name: 'Test' } } }); expect(result).toEqual({ success: false, error: expect.any(String) }); }); it('createPlan action returns error for invalid weekStart format', async () => { const formData = new FormData(); formData.set('weekStart', 'not-a-date'); const result = await actions.createPlan({ fetch: vi.fn(), request: { formData: async () => formData }, locals: { benutzer: { id: 'u1', name: 'Test', rolle: 'planer' }, haushalt: { id: 'h1', name: 'Test' } } }); expect(result).toEqual({ success: false, error: 'Ungültiges Datum.' }); expect(mockPost).not.toHaveBeenCalled(); }); it('createPlan action returns error when weekStart is missing', async () => { const formData = new FormData(); const result = await actions.createPlan({ fetch: vi.fn(), request: { formData: async () => formData }, locals: { benutzer: { id: 'u1', name: 'Test', rolle: 'planer' }, haushalt: { id: 'h1', name: 'Test' } } }); expect(result).toEqual({ success: false, error: 'Ungültiges Datum.' }); }); it('createPlan action returns permission error for member role', async () => { const formData = new FormData(); formData.set('weekStart', '2026-03-30'); const result = await actions.createPlan({ fetch: vi.fn(), request: { formData: async () => formData }, locals: { benutzer: { id: 'u1', name: 'Test', rolle: 'mitglied' }, haushalt: { id: 'h1', name: 'Test' } } }); expect(result).toEqual({ success: false, error: 'Keine Berechtigung.' }); expect(mockPost).not.toHaveBeenCalled(); }); }); describe('planner page — variety score partial failure', () => { let load: any; beforeEach(async () => { mockGet.mockReset(); mockPost.mockReset(); vi.resetModules(); const mod = await import('./+page.server'); load = mod.load; }); const mockWeekPlan = { id: 'plan-1', weekStart: '2026-03-30', status: 'draft', slots: [] }; it('returns weekPlan even when variety score API fails', async () => { mockGet.mockResolvedValueOnce({ data: mockWeekPlan, error: undefined }); mockGet.mockResolvedValueOnce({ data: undefined, error: { status: 500 } }); const url = new URL('http://localhost/planner'); const result = await load({ fetch: vi.fn(), url }); expect(result.weekPlan).toBeDefined(); expect(result.weekPlan.id).toBe('plan-1'); expect(result.varietyScore).toBeNull(); }); });