import { describe, it, expect, vi, beforeEach } from 'vitest'; vi.mock('$env/dynamic/private', () => ({ env: { BACKEND_URL: 'http://localhost:8080' } })); const mockPost = vi.fn(); vi.mock('$lib/server/api', () => ({ apiClient: () => ({ POST: mockPost }) })); describe('login form action', () => { let actions: any; beforeEach(async () => { mockPost.mockReset(); const mod = await import('./+page.server'); actions = mod.actions; }); function createEvent(formData: Record, searchParams = '') { const fd = new FormData(); for (const [key, value] of Object.entries(formData)) { fd.append(key, value); } return { request: { formData: () => Promise.resolve(fd) }, url: new URL(`http://localhost/login${searchParams}`), fetch: vi.fn(), cookies: { get: vi.fn(), set: vi.fn() } } as any; } function mockSuccess() { return { data: { data: { id: '123' } }, error: undefined, response: { headers: { get: vi.fn().mockReturnValue(null) } } }; } it('calls POST /v1/auth/login with form data', async () => { mockPost.mockResolvedValue(mockSuccess()); try { await actions.default(createEvent({ email: 'sarah@example.com', password: 'password123' })); } catch { // redirect throws } expect(mockPost).toHaveBeenCalledWith('/v1/auth/login', { body: { email: 'sarah@example.com', password: 'password123' } }); }); it('redirects to /planner on success by default', async () => { mockPost.mockResolvedValue(mockSuccess()); try { await actions.default(createEvent({ email: 'sarah@example.com', password: 'password123' })); expect.unreachable(); } catch (e: any) { expect(e.status).toBe(303); expect(e.location).toBe('/planner'); } }); it('redirects to ?redirect param when present', async () => { mockPost.mockResolvedValue(mockSuccess()); try { await actions.default(createEvent( { email: 'sarah@example.com', password: 'password123' }, '?redirect=%2Frecipes%2Fabc' )); expect.unreachable(); } catch (e: any) { expect(e.status).toBe(303); expect(e.location).toBe('/recipes/abc'); } }); it('falls back to /planner when ?redirect= is an absolute URL', async () => { mockPost.mockResolvedValue(mockSuccess()); try { await actions.default(createEvent( { email: 'sarah@example.com', password: 'password123' }, '?redirect=https%3A%2F%2Fevil.com' )); expect.unreachable(); } catch (e: any) { expect(e.status).toBe(303); expect(e.location).toBe('/planner'); } }); it('falls back to /planner when ?redirect= is a protocol-relative URL', async () => { mockPost.mockResolvedValue(mockSuccess()); try { await actions.default(createEvent( { email: 'sarah@example.com', password: 'password123' }, '?redirect=%2F%2Fevil.com' )); expect.unreachable(); } catch (e: any) { expect(e.status).toBe(303); expect(e.location).toBe('/planner'); } }); it('sets JSESSIONID cookie on successful login', async () => { mockPost.mockResolvedValue({ data: { data: { id: '123' } }, error: undefined, response: { headers: { get: vi.fn().mockReturnValue('JSESSIONID=abc123; Path=/; HttpOnly') } } }); const event = createEvent({ email: 'sarah@example.com', password: 'password123' }); try { await actions.default(event); } catch { // redirect throws } expect(event.cookies.set).toHaveBeenCalledWith('JSESSIONID', 'abc123', expect.objectContaining({ path: '/', secure: true })); }); it('rejects empty email with validation error', async () => { const result = await actions.default(createEvent({ email: '', password: 'password123' })); expect(result.status).toBe(400); expect(result.data.errors.email).toBe('Ungültige E-Mail-Adresse'); expect(mockPost).not.toHaveBeenCalled(); }); it('rejects empty password with validation error', async () => { const result = await actions.default(createEvent({ email: 'sarah@example.com', password: '' })); expect(result.status).toBe(400); expect(result.data.errors.password).toBe('Passwort ist erforderlich'); expect(mockPost).not.toHaveBeenCalled(); }); it('returns fail with form error on API error', async () => { mockPost.mockResolvedValue({ data: undefined, error: { status: 401, message: 'Invalid credentials' } }); const result = await actions.default(createEvent({ email: 'sarah@example.com', password: 'password123' })); expect(result.status).toBe(400); expect(result.data.errors.form).toBe('E-Mail oder Passwort ist falsch.'); }); });