diff --git a/frontend/src/routes/(public)/signup/+page.server.ts b/frontend/src/routes/(public)/signup/+page.server.ts new file mode 100644 index 0000000..383c855 --- /dev/null +++ b/frontend/src/routes/(public)/signup/+page.server.ts @@ -0,0 +1,23 @@ +import { redirect, fail } from '@sveltejs/kit'; +import { apiClient } from '$lib/server/api'; +import type { Actions } from './$types'; + +export const actions = { + default: async ({ request, fetch }) => { + const formData = await request.formData(); + const displayName = formData.get('displayName') as string; + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + const api = apiClient(fetch); + const { data, error } = await api.POST('/v1/auth/signup', { + body: { displayName, email, password } + }); + + if (error) { + return fail(400, { error: 'Registrierung fehlgeschlagen. Bitte versuche es erneut.' }); + } + + redirect(303, '/household/setup'); + } +} satisfies Actions; diff --git a/frontend/src/routes/(public)/signup/+page.svelte b/frontend/src/routes/(public)/signup/+page.svelte new file mode 100644 index 0000000..70027d2 --- /dev/null +++ b/frontend/src/routes/(public)/signup/+page.svelte @@ -0,0 +1,14 @@ + + + +
+ +
+
+ +
+
+
diff --git a/frontend/src/routes/(public)/signup/page.server.test.ts b/frontend/src/routes/(public)/signup/page.server.test.ts new file mode 100644 index 0000000..0a18c07 --- /dev/null +++ b/frontend/src/routes/(public)/signup/page.server.test.ts @@ -0,0 +1,85 @@ +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('signup form action', () => { + let actions: any; + + beforeEach(async () => { + mockPost.mockReset(); + const mod = await import('./+page.server'); + actions = mod.actions; + }); + + function createRequest(formData: Record) { + const fd = new FormData(); + for (const [key, value] of Object.entries(formData)) { + fd.append(key, value); + } + return { + request: { formData: () => Promise.resolve(fd) }, + fetch: vi.fn(), + cookies: { get: vi.fn(), set: vi.fn() } + } as any; + } + + it('calls POST /v1/auth/signup with form data', async () => { + mockPost.mockResolvedValue({ data: { data: { id: '123' } }, error: undefined }); + + try { + await actions.default(createRequest({ + displayName: 'Sarah', + email: 'sarah@example.com', + password: 'password123' + })); + } catch { + // redirect throws + } + + expect(mockPost).toHaveBeenCalledWith('/v1/auth/signup', { + body: { + displayName: 'Sarah', + email: 'sarah@example.com', + password: 'password123' + } + }); + }); + + it('redirects to /household/setup on success', async () => { + mockPost.mockResolvedValue({ data: { data: { id: '123' } }, error: undefined }); + + try { + await actions.default(createRequest({ + displayName: 'Sarah', + email: 'sarah@example.com', + password: 'password123' + })); + expect.unreachable(); + } catch (e: any) { + expect(e.status).toBe(303); + expect(e.location).toBe('/household/setup'); + } + }); + + it('returns fail with error message on API error', async () => { + mockPost.mockResolvedValue({ + data: undefined, + error: { status: 409, message: 'Email already registered' } + }); + + const result = await actions.default(createRequest({ + displayName: 'Sarah', + email: 'sarah@example.com', + password: 'password123' + })); + + expect(result.status).toBe(400); + }); +});