test: add unit tests for all auth backend logic
- Register: edge cases for non-Error throws, error message assertions, and verify signUpEmail was called with correct payload - Forgot-password: empty/missing email, Error/non-Error throws - Reset-password: load (missing/expired/valid token) and action (short password, mismatch, throw, success redirect) - Account setup: load (unauthorized, returns user), action (redirect, falsy create, throws, isRedirect passthrough) - Email-verification: correct URL construction, sendMail delegation, throw propagation - Hooks: sets/omits locals, passes building/auth to svelteKitHandler - Fix Svelte 5 warning on setup page by using with one-time guard instead of top-level prop read
This commit is contained in:
@@ -18,10 +18,8 @@
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
const userFullName = data.user?.name;
|
||||
let firstName = $state(splitName(userFullName).first);
|
||||
let lastName = $state(splitName(userFullName).last);
|
||||
let phoneNumber = $state('');
|
||||
let firstName = $state('');
|
||||
let lastName = $state('');
|
||||
|
||||
function splitName(fullName: string | null | undefined): { first: string; last: string } {
|
||||
if (!fullName) return { first: '', last: '' };
|
||||
@@ -29,6 +27,15 @@
|
||||
return { first: parts[0] ?? '', last: parts.slice(1).join(' ') };
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (data.user?.name && !firstName) {
|
||||
const names = splitName(data.user.name);
|
||||
firstName = names.first;
|
||||
lastName = names.last;
|
||||
}
|
||||
});
|
||||
let phoneNumber = $state('');
|
||||
|
||||
function handlePhoneInput(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
phoneNumber = clientFormatPhoneNumber(input.value);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { isHttpError, isRedirect, isActionFailure, redirect } from '@sveltejs/kit';
|
||||
import { createLocals, mockUser, createFormRequest } from '$lib/test-utils';
|
||||
import { serverFormatPhoneNumber } from '$lib/utils';
|
||||
|
||||
const { create, findUnique } = vi.hoisted(() => ({
|
||||
create: vi.fn(),
|
||||
findUnique: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$lib/db', () => ({
|
||||
prisma: {
|
||||
user: {
|
||||
findUnique
|
||||
},
|
||||
profile: {
|
||||
create
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('$lib/utils', () => ({
|
||||
serverFormatPhoneNumber: vi.fn((val: string) => `formatted-${val}`)
|
||||
}));
|
||||
|
||||
import { load, actions } from './+page.server';
|
||||
|
||||
const invokeDefault = (request: Request) => actions.default({ request } as any);
|
||||
|
||||
describe('account setup page server load', () => {
|
||||
beforeEach(() => {
|
||||
create.mockReset();
|
||||
});
|
||||
|
||||
it('throws 403 when user is not logged in', async () => {
|
||||
const err = await Promise.resolve(
|
||||
load({ locals: createLocals() } as any)
|
||||
).catch((e: unknown) => e);
|
||||
expect(isHttpError(err, 403)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns user data when logged in', async () => {
|
||||
const user = mockUser({ id: 'user-1', name: 'Jane Doe' });
|
||||
findUnique.mockResolvedValue(user);
|
||||
|
||||
const result = await load({ locals: createLocals(user as any) } as any);
|
||||
expect(result).toEqual({ user });
|
||||
});
|
||||
});
|
||||
|
||||
describe('account setup page server action', () => {
|
||||
beforeEach(() => {
|
||||
create.mockReset();
|
||||
});
|
||||
|
||||
it('redirects to /dashboard on successful profile creation', async () => {
|
||||
create.mockResolvedValue({ id: 'profile-1', userId: 'user-1' });
|
||||
|
||||
const request = createFormRequest({
|
||||
userId: 'user-1',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
phoneNumber: '555-0100'
|
||||
});
|
||||
|
||||
const err = await Promise.resolve(invokeDefault(request)).catch((e: unknown) => e);
|
||||
expect(isRedirect(err)).toBe(true);
|
||||
if (isRedirect(err)) {
|
||||
expect(err.status).toBe(303);
|
||||
expect(err.location).toBe('/dashboard');
|
||||
}
|
||||
expect(serverFormatPhoneNumber).toHaveBeenCalledWith('555-0100');
|
||||
expect(create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
userId: 'user-1',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
phoneNumber: 'formatted-555-0100'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns fail(400) when profile.create returns falsy', async () => {
|
||||
create.mockResolvedValue(null);
|
||||
|
||||
const request = createFormRequest({
|
||||
userId: 'user-1',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
phoneNumber: '555-0100'
|
||||
});
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
});
|
||||
|
||||
it('throws 400 error when profile.create throws', async () => {
|
||||
create.mockRejectedValue(new Error('db error'));
|
||||
|
||||
const request = createFormRequest({
|
||||
userId: 'user-1',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
phoneNumber: '555-0100'
|
||||
});
|
||||
|
||||
const err = await Promise.resolve(invokeDefault(request)).catch((e: unknown) => e);
|
||||
expect(isHttpError(err, 400)).toBe(true);
|
||||
if (isHttpError(err)) {
|
||||
expect(err.body.message).toBe('bad request');
|
||||
}
|
||||
});
|
||||
|
||||
it('re-throws redirect errors from profile.create', async () => {
|
||||
let redirectError: unknown;
|
||||
try {
|
||||
redirect(303, '/dashboard');
|
||||
} catch (e) {
|
||||
redirectError = e;
|
||||
}
|
||||
create.mockRejectedValue(redirectError);
|
||||
|
||||
const request = createFormRequest({
|
||||
userId: 'user-1',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
phoneNumber: '555-0100'
|
||||
});
|
||||
|
||||
const err = await Promise.resolve(invokeDefault(request)).catch((e: unknown) => e);
|
||||
expect(err).toBe(redirectError);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user