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:
@@ -0,0 +1,109 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const { getSession } = vi.hoisted(() => ({
|
||||
getSession: vi.fn()
|
||||
}));
|
||||
|
||||
const { svelteKitHandler } = vi.hoisted(() => ({
|
||||
svelteKitHandler: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$app/environment', () => ({
|
||||
building: false
|
||||
}));
|
||||
|
||||
vi.mock('better-auth/svelte-kit', () => ({
|
||||
svelteKitHandler
|
||||
}));
|
||||
|
||||
vi.mock('$lib/auth', () => ({
|
||||
auth: {
|
||||
api: {
|
||||
getSession
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
import { handle } from './hooks.server';
|
||||
|
||||
function createMockEvent() {
|
||||
return {
|
||||
request: new Request('http://localhost/dashboard'),
|
||||
locals: {} as Record<string, unknown>,
|
||||
resolve: vi.fn()
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe('hooks.server handle', () => {
|
||||
beforeEach(() => {
|
||||
getSession.mockReset();
|
||||
svelteKitHandler.mockReset();
|
||||
});
|
||||
|
||||
it('sets session and user on locals when authenticated', async () => {
|
||||
const sessionData = {
|
||||
session: { id: 'sess-1', userId: 'user-1', expiresAt: new Date() },
|
||||
user: { id: 'user-1', name: 'Jane', email: 'jane@example.com' }
|
||||
};
|
||||
getSession.mockResolvedValue(sessionData);
|
||||
|
||||
const event = createMockEvent();
|
||||
await handle({ event, resolve: event.resolve });
|
||||
|
||||
expect(event.locals.session).toEqual(sessionData.session);
|
||||
expect(event.locals.user).toEqual(sessionData.user);
|
||||
expect(svelteKitHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
event,
|
||||
resolve: event.resolve,
|
||||
building: false
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not set locals when not authenticated', async () => {
|
||||
getSession.mockResolvedValue(null);
|
||||
|
||||
const event = createMockEvent();
|
||||
await handle({ event, resolve: event.resolve });
|
||||
|
||||
expect(event.locals.session).toBeUndefined();
|
||||
expect(event.locals.user).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not set locals when getSession returns undefined', async () => {
|
||||
getSession.mockResolvedValue(undefined);
|
||||
|
||||
const event = createMockEvent();
|
||||
await handle({ event, resolve: event.resolve });
|
||||
|
||||
expect(event.locals.session).toBeUndefined();
|
||||
expect(event.locals.user).toBeUndefined();
|
||||
});
|
||||
|
||||
it('passes building flag from $app/environment to svelteKitHandler', async () => {
|
||||
getSession.mockResolvedValue(null);
|
||||
|
||||
const event = createMockEvent();
|
||||
await handle({ event, resolve: event.resolve });
|
||||
|
||||
expect(svelteKitHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ building: false })
|
||||
);
|
||||
});
|
||||
|
||||
it('calls svelteKitHandler with auth instance', async () => {
|
||||
getSession.mockResolvedValue(null);
|
||||
|
||||
const event = createMockEvent();
|
||||
await handle({ event, resolve: event.resolve });
|
||||
|
||||
expect(svelteKitHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auth: expect.objectContaining({
|
||||
api: expect.objectContaining({ getSession })
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const { sendMail } = vi.hoisted(() => ({
|
||||
sendMail: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$env/static/private', () => ({
|
||||
BETTER_AUTH_URL: 'http://localhost:5173'
|
||||
}));
|
||||
|
||||
vi.mock('./resend', () => ({
|
||||
sendMail
|
||||
}));
|
||||
|
||||
import { sendVerificationEmail } from './email-verification';
|
||||
|
||||
describe('sendVerificationEmail', () => {
|
||||
beforeEach(() => {
|
||||
sendMail.mockReset();
|
||||
});
|
||||
|
||||
it('calls sendMail with correct email and template', async () => {
|
||||
sendMail.mockResolvedValue(undefined);
|
||||
|
||||
await sendVerificationEmail({
|
||||
email: 'jane@example.com',
|
||||
token: 'abc123'
|
||||
});
|
||||
|
||||
expect(sendMail).toHaveBeenCalledWith({
|
||||
to: 'jane@example.com',
|
||||
templateId: 'email-verification-email',
|
||||
data: {
|
||||
target_email_address: 'jane@example.com',
|
||||
verification_link:
|
||||
'http://localhost:5173/api/auth/verify-email?token=abc123&callbackURL=/login?verified=true'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('constructs verification URL with the given token', async () => {
|
||||
sendMail.mockResolvedValue(undefined);
|
||||
|
||||
await sendVerificationEmail({
|
||||
email: 'test@example.com',
|
||||
token: 'xyz789'
|
||||
});
|
||||
|
||||
const call = sendMail.mock.calls[0][0];
|
||||
expect(call.data.verification_link).toContain('xyz789');
|
||||
expect(call.data.verification_link).toContain('/login?verified=true');
|
||||
});
|
||||
|
||||
it('throws when sendMail throws', async () => {
|
||||
sendMail.mockRejectedValue(new Error('Resend error'));
|
||||
|
||||
await expect(
|
||||
sendVerificationEmail({
|
||||
email: 'jane@example.com',
|
||||
token: 'abc123'
|
||||
})
|
||||
).rejects.toThrow('Resend error');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { isActionFailure } from '@sveltejs/kit';
|
||||
import { createFormRequest } from '$lib/test-utils';
|
||||
|
||||
const { requestPasswordReset } = vi.hoisted(() => ({
|
||||
requestPasswordReset: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$lib/db', () => ({
|
||||
prisma: {}
|
||||
}));
|
||||
|
||||
vi.mock('$lib/auth', () => ({
|
||||
auth: {
|
||||
api: {
|
||||
requestPasswordReset
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
import { actions } from './+page.server';
|
||||
|
||||
const invokeDefault = (request: Request) => actions.default({ request } as any);
|
||||
|
||||
describe('forgot-password page server action', () => {
|
||||
beforeEach(() => {
|
||||
requestPasswordReset.mockReset();
|
||||
});
|
||||
|
||||
it('returns fail(400) when email is empty', async () => {
|
||||
const request = createFormRequest({ email: '' });
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Email is required.' });
|
||||
}
|
||||
expect(requestPasswordReset).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns fail(400) when email is missing from form', async () => {
|
||||
const request = createFormRequest({});
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Email is required.' });
|
||||
}
|
||||
expect(requestPasswordReset).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns success on successful request', async () => {
|
||||
requestPasswordReset.mockResolvedValue(undefined);
|
||||
|
||||
const request = createFormRequest({ email: 'jane@example.com' });
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(requestPasswordReset).toHaveBeenCalledWith({
|
||||
body: {
|
||||
email: 'jane@example.com',
|
||||
redirectTo: '/reset-password'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns fail(400) with error message when requestPasswordReset throws', async () => {
|
||||
requestPasswordReset.mockRejectedValue(new Error('User not found'));
|
||||
|
||||
const request = createFormRequest({ email: 'jane@example.com' });
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'User not found' });
|
||||
}
|
||||
});
|
||||
|
||||
it('returns fail(400) with default message when non-Error is thrown', async () => {
|
||||
requestPasswordReset.mockRejectedValue('unexpected');
|
||||
|
||||
const request = createFormRequest({ email: 'jane@example.com' });
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Failed to send reset email.' });
|
||||
}
|
||||
});
|
||||
|
||||
it('returns fail(400) with default message when thrown value is null', async () => {
|
||||
requestPasswordReset.mockRejectedValue(null);
|
||||
|
||||
const request = createFormRequest({ email: 'jane@example.com' });
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Failed to send reset email.' });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,9 @@ describe('register page server action', () => {
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'passwords do not match' });
|
||||
}
|
||||
expect(signUpEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -54,9 +57,16 @@ describe('register page server action', () => {
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(result).toEqual({ success: true });
|
||||
expect(signUpEmail).toHaveBeenCalledWith({
|
||||
body: {
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com',
|
||||
password: 'secret123'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns fail(400) when signUpEmail throws', async () => {
|
||||
it('returns fail(400) with error message when signUpEmail throws', async () => {
|
||||
signUpEmail.mockRejectedValue(new Error('email already in use'));
|
||||
|
||||
const request = createFormRequest({
|
||||
@@ -69,5 +79,44 @@ describe('register page server action', () => {
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'email already in use' });
|
||||
}
|
||||
});
|
||||
|
||||
it('returns fail(400) with generic message when non-Error is thrown', async () => {
|
||||
signUpEmail.mockRejectedValue('string error');
|
||||
|
||||
const request = createFormRequest({
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
email: 'jane@example.com',
|
||||
password: 'secret123',
|
||||
password2: 'secret123'
|
||||
});
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'An unexpected error occurred' });
|
||||
}
|
||||
});
|
||||
|
||||
it('returns fail(400) with generic message when thrown value is null', async () => {
|
||||
signUpEmail.mockRejectedValue(null);
|
||||
|
||||
const request = createFormRequest({
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
email: 'jane@example.com',
|
||||
password: 'secret123',
|
||||
password2: 'secret123'
|
||||
});
|
||||
|
||||
const result = await invokeDefault(request);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'An unexpected error occurred' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { isRedirect, isActionFailure } from '@sveltejs/kit';
|
||||
import { createFormRequest, createUrl } from '$lib/test-utils';
|
||||
|
||||
const { resetPassword } = vi.hoisted(() => ({
|
||||
resetPassword: vi.fn()
|
||||
}));
|
||||
|
||||
const { findFirst } = vi.hoisted(() => ({
|
||||
findFirst: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$lib/db', () => ({
|
||||
prisma: {
|
||||
verification: {
|
||||
findFirst
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('$lib/auth', () => ({
|
||||
auth: {
|
||||
api: {
|
||||
resetPassword
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
import { load, actions } from './+page.server';
|
||||
|
||||
describe('reset-password page server load', () => {
|
||||
beforeEach(() => {
|
||||
findFirst.mockReset();
|
||||
});
|
||||
|
||||
it('redirects to / when token is missing', async () => {
|
||||
const url = createUrl('/reset-password');
|
||||
|
||||
const err = await Promise.resolve(load({ url } as any)).catch((e: unknown) => e);
|
||||
expect(isRedirect(err)).toBe(true);
|
||||
if (isRedirect(err)) {
|
||||
expect(err.location).toBe('/');
|
||||
}
|
||||
expect(findFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('redirects to / when verification record is not found', async () => {
|
||||
findFirst.mockResolvedValue(null);
|
||||
|
||||
const url = createUrl('/reset-password', { token: 'invalid-token' });
|
||||
|
||||
const err = await Promise.resolve(load({ url } as any)).catch((e: unknown) => e);
|
||||
expect(isRedirect(err)).toBe(true);
|
||||
if (isRedirect(err)) {
|
||||
expect(err.location).toBe('/');
|
||||
}
|
||||
expect(findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
value: 'invalid-token',
|
||||
expiresAt: { gt: expect.any(Date) }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined when token is valid (verification found)', async () => {
|
||||
findFirst.mockResolvedValue({ id: 'v-1', value: 'valid-token' });
|
||||
|
||||
const url = createUrl('/reset-password', { token: 'valid-token' });
|
||||
|
||||
const result = await load({ url } as any);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('redirects to / when verification is expired', async () => {
|
||||
findFirst.mockResolvedValue(null);
|
||||
|
||||
const url = createUrl('/reset-password', { token: 'expired-token' });
|
||||
|
||||
const err = await Promise.resolve(load({ url } as any)).catch((e: unknown) => e);
|
||||
expect(isRedirect(err)).toBe(true);
|
||||
if (isRedirect(err)) {
|
||||
expect(err.location).toBe('/');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset-password page server action', () => {
|
||||
beforeEach(() => {
|
||||
resetPassword.mockReset();
|
||||
});
|
||||
|
||||
const invokeDefault = (url: URL, request: Request) =>
|
||||
actions.default({ request, url } as any);
|
||||
|
||||
it('returns fail(400) when token is missing', async () => {
|
||||
const url = createUrl('/reset-password');
|
||||
|
||||
const result = await invokeDefault(url, createFormRequest({}));
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Invalid or missing reset token.' });
|
||||
}
|
||||
expect(resetPassword).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns fail(400) when password is too short', async () => {
|
||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||
|
||||
const result = await invokeDefault(
|
||||
url,
|
||||
createFormRequest({ password: 'short', password2: 'short' })
|
||||
);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Password must be at least 8 characters.' });
|
||||
}
|
||||
expect(resetPassword).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns fail(400) when password is empty', async () => {
|
||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||
|
||||
const result = await invokeDefault(
|
||||
url,
|
||||
createFormRequest({ password: '', password2: '' })
|
||||
);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Password must be at least 8 characters.' });
|
||||
}
|
||||
expect(resetPassword).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns fail(400) when passwords do not match', async () => {
|
||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||
|
||||
const result = await invokeDefault(
|
||||
url,
|
||||
createFormRequest({ password: 'longenough1', password2: 'longenough2' })
|
||||
);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Passwords do not match.' });
|
||||
}
|
||||
expect(resetPassword).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns fail(400) when resetPassword throws', async () => {
|
||||
resetPassword.mockRejectedValue(new Error('Token expired'));
|
||||
|
||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||
|
||||
const result = await invokeDefault(
|
||||
url,
|
||||
createFormRequest({ password: 'longenough1', password2: 'longenough1' })
|
||||
);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Token expired' });
|
||||
}
|
||||
});
|
||||
|
||||
it('returns fail(400) with default message when non-Error is thrown', async () => {
|
||||
resetPassword.mockRejectedValue(null);
|
||||
|
||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||
|
||||
const result = await invokeDefault(
|
||||
url,
|
||||
createFormRequest({ password: 'longenough1', password2: 'longenough1' })
|
||||
);
|
||||
expect(isActionFailure(result)).toBe(true);
|
||||
if (isActionFailure(result)) {
|
||||
expect(result.data).toEqual({ error: 'Failed to reset password.' });
|
||||
}
|
||||
});
|
||||
|
||||
it('redirects to /login on successful reset', async () => {
|
||||
resetPassword.mockResolvedValue(undefined);
|
||||
|
||||
const url = createUrl('/reset-password', { token: 'token-1' });
|
||||
|
||||
const err = await Promise.resolve(
|
||||
invokeDefault(
|
||||
url,
|
||||
createFormRequest({ password: 'longenough1', password2: 'longenough1' })
|
||||
)
|
||||
).catch((e: unknown) => e);
|
||||
expect(isRedirect(err)).toBe(true);
|
||||
if (isRedirect(err)) {
|
||||
expect(err.status).toBe(303);
|
||||
expect(err.location).toBe('/login');
|
||||
}
|
||||
expect(resetPassword).toHaveBeenCalledWith({
|
||||
body: {
|
||||
newPassword: 'longenough1',
|
||||
token: 'token-1'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user