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' } }); }); });