Implement end-to-end registration: POST /api/auth/register creates a user, returns a JWT, and the frontend RegisterPage stores the token and redirects to home. Backend: - Add AuthController with POST /api/auth/register endpoint - Add RegisterRequest record (@Email, @NotBlank, @Size(min=8)) - Add AuthResponse and ErrorResponse DTOs - Add GlobalExceptionHandler (@RestControllerAdvice with logging) - EmailAlreadyExistsException -> 409 (Swedish message) - MethodArgumentNotValidException -> 400 (field errors) - Generic Exception -> 500 (Swedish message + server-side log) Frontend: - Add api/client.ts: centralized fetch wrapper with Bearer token interceptor, ApiError class, JSON error parsing - Add api/auth.ts: register() function - Add stores/authStore.ts: Pinia store with token persistence via localStorage, registerUser/logout/isAuthenticated - Add pages/RegisterPage.vue: email + password + confirm password form with client-side validation, submit handler, error display, redirect to home on success - Add route /registrera pointing to RegisterPage - Add 'Registrera' link to AppHeader navigation Infrastructure: - Add __tests__/setup.ts: localStorage polyfill for jsdom 29 (jsdom 29 lacks standard Storage method implementations) - Register polyfill via vitest config setupFiles Tests (17 new, 2 extended): - AuthControllerTest (@SpringBootTest + @AutoConfigureMockMvc): 5 backend tests (success 201, duplicate 409, invalid email 400, short password 400, missing email 400) - authStore.spec.ts: 5 tests (unauthenticated start, localStorage restore, register success, register failure, logout) - RegisterPage.spec.ts: 12 tests (render, validation, submit, redirect, error display, login link) - AppHeader.spec.ts: added 'Registrera' link test - Router.spec.ts: added /registrera route resolution test Build: 95 tests pass (57 frontend + 38 backend), lint clean.
25 lines
625 B
TypeScript
25 lines
625 B
TypeScript
const store: Record<string, string> = {}
|
|
|
|
globalThis.localStorage = {
|
|
getItem(key: string): string | null {
|
|
return Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null
|
|
},
|
|
setItem(key: string, value: string): void {
|
|
store[key] = String(value)
|
|
},
|
|
removeItem(key: string): void {
|
|
delete store[key]
|
|
},
|
|
clear(): void {
|
|
const keys = Object.keys(store)
|
|
for (const key of keys) {
|
|
delete store[key]
|
|
}
|
|
},
|
|
get length(): number {
|
|
return Object.keys(store).length
|
|
},
|
|
key(index: number): string | null {
|
|
return Object.keys(store)[index] ?? null
|
|
},
|
|
} as Storage
|