Remove the generic $lib/svelte-admin CRUD layer and AdminTable.svelte; each admin route now owns its own form/modal logic. Add DataTable.svelte, superform-errors.ts, and profile-specific modal/field components. Flatten proclamation categories/groups and drop subscription levels; add the v2 groups API; update v1/v2 endpoints, serializers, auth, and regenerated Prisma client with the new migrations. Document the overhaul plan in docs/ADMIN_OVERHAUL.md.
258 lines
7.7 KiB
TypeScript
258 lines
7.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { isHttpError, isActionFailure } from '@sveltejs/kit';
|
|
import {
|
|
createLocals,
|
|
createFormRequest,
|
|
mockUser,
|
|
mockUserWithProfileAndAddresses
|
|
} from '$lib/test-utils';
|
|
|
|
vi.mock('$lib/db', () => ({
|
|
prisma: {
|
|
user: { findUnique: vi.fn() },
|
|
profile: { findFirst: vi.fn(), updateMany: vi.fn() },
|
|
address: { create: vi.fn() }
|
|
}
|
|
}));
|
|
|
|
import { prisma } from '$lib/db';
|
|
import { load, actions } from './+page.server';
|
|
|
|
const userFindUnique = prisma.user.findUnique as ReturnType<typeof vi.fn>;
|
|
const profileFindFirst = prisma.profile.findFirst as ReturnType<typeof vi.fn>;
|
|
const profileUpdateMany = prisma.profile.updateMany as ReturnType<typeof vi.fn>;
|
|
const addressCreate = prisma.address.create as ReturnType<typeof vi.fn>;
|
|
|
|
const invokeAction = (name: string, request: Request, locals: App.Locals) =>
|
|
(actions as any)[name]({ request, locals } as any);
|
|
|
|
describe('(protected)/account page server', () => {
|
|
beforeEach(() => {
|
|
userFindUnique.mockReset();
|
|
profileFindFirst.mockReset();
|
|
profileUpdateMany.mockReset();
|
|
addressCreate.mockReset();
|
|
});
|
|
|
|
describe('load', () => {
|
|
it('throws 403 when locals.user is undefined', async () => {
|
|
const err = await Promise.resolve(load({ locals: createLocals() } as any)).catch(
|
|
(e: unknown) => e
|
|
);
|
|
expect(isHttpError(err, 403)).toBe(true);
|
|
});
|
|
|
|
it('throws 403 when user is not found in the database', async () => {
|
|
userFindUnique.mockResolvedValue(null);
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const err = await Promise.resolve(load({ locals } as any)).catch((e: unknown) => e);
|
|
expect(isHttpError(err, 403)).toBe(true);
|
|
});
|
|
|
|
it('returns user with profile and addresses', async () => {
|
|
const { profile, ...user } = mockUserWithProfileAndAddresses() as any;
|
|
userFindUnique.mockResolvedValue(user);
|
|
profileFindFirst.mockResolvedValue(profile);
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = (await load({ locals } as any)) as any;
|
|
expect(result.user).toEqual({ ...user, profile });
|
|
expect(result.user.profile.addresses).toHaveLength(1);
|
|
});
|
|
|
|
it('loads the profile with addresses by user id', async () => {
|
|
userFindUnique.mockResolvedValue(mockUser());
|
|
profileFindFirst.mockResolvedValue({ id: 'profile-1' });
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
await load({ locals } as any);
|
|
|
|
expect(profileFindFirst).toHaveBeenCalledWith({
|
|
where: { userId: 'user-1' },
|
|
include: { addresses: true }
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('updateProfile action', () => {
|
|
it('throws 403 when locals.user is undefined', async () => {
|
|
const request = createFormRequest({
|
|
firstName: 'Jane',
|
|
lastName: 'Doe',
|
|
phoneNumber: '(555) 010-0100'
|
|
});
|
|
|
|
const err = await Promise.resolve(
|
|
invokeAction('updateProfile', request, createLocals())
|
|
).catch((e: unknown) => e);
|
|
expect(isHttpError(err, 403)).toBe(true);
|
|
});
|
|
|
|
it('returns fail(400) when firstName is missing', async () => {
|
|
const request = createFormRequest({
|
|
firstName: '',
|
|
lastName: 'Doe',
|
|
phoneNumber: '(555) 010-0100'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('updateProfile', request, locals);
|
|
expect(isActionFailure(result)).toBe(true);
|
|
expect((result as any).data.error).toBe('All fields are required.');
|
|
});
|
|
|
|
it('returns fail(400) when lastName is missing', async () => {
|
|
const request = createFormRequest({
|
|
firstName: 'Jane',
|
|
lastName: '',
|
|
phoneNumber: '(555) 010-0100'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('updateProfile', request, locals);
|
|
expect(isActionFailure(result)).toBe(true);
|
|
});
|
|
|
|
it('returns fail(400) when phoneNumber is missing', async () => {
|
|
const request = createFormRequest({
|
|
firstName: 'Jane',
|
|
lastName: 'Doe',
|
|
phoneNumber: ''
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('updateProfile', request, locals);
|
|
expect(isActionFailure(result)).toBe(true);
|
|
});
|
|
|
|
it('updates profile with server-formatted phone number on success', async () => {
|
|
profileUpdateMany.mockResolvedValue({});
|
|
|
|
const request = createFormRequest({
|
|
firstName: 'Jane',
|
|
lastName: 'Smith',
|
|
phoneNumber: '(817) 555-1234'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('updateProfile', request, locals);
|
|
expect(result).toEqual({ success: true, action: 'updateProfile' });
|
|
expect(profileUpdateMany).toHaveBeenCalledWith({
|
|
where: { userId: 'user-1' },
|
|
data: { firstName: 'Jane', lastName: 'Smith', phoneNumber: '8175551234' }
|
|
});
|
|
});
|
|
|
|
it('returns fail(500) when prisma update throws', async () => {
|
|
profileUpdateMany.mockRejectedValue(new Error('db error'));
|
|
|
|
const request = createFormRequest({
|
|
firstName: 'Jane',
|
|
lastName: 'Doe',
|
|
phoneNumber: '(555) 010-0100'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('updateProfile', request, locals);
|
|
expect(isActionFailure(result)).toBe(true);
|
|
expect((result as any).status).toBe(500);
|
|
});
|
|
});
|
|
|
|
describe('addAddress action', () => {
|
|
it('throws 403 when locals.user is undefined', async () => {
|
|
const request = createFormRequest({
|
|
street: '123 Main St',
|
|
city: 'Dallas',
|
|
state: 'TX',
|
|
postalCode: '75001',
|
|
country: 'US'
|
|
});
|
|
|
|
const err = await Promise.resolve(invokeAction('addAddress', request, createLocals())).catch(
|
|
(e: unknown) => e
|
|
);
|
|
expect(isHttpError(err, 403)).toBe(true);
|
|
});
|
|
|
|
it('returns fail(400) when a required field is missing', async () => {
|
|
const request = createFormRequest({
|
|
street: '123 Main St',
|
|
city: '',
|
|
state: 'TX',
|
|
postalCode: '75001',
|
|
country: 'US'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('addAddress', request, locals);
|
|
expect(isActionFailure(result)).toBe(true);
|
|
expect((result as any).data.error).toBe('All address fields are required.');
|
|
});
|
|
|
|
it('throws 400 when profile is not found', async () => {
|
|
profileFindFirst.mockResolvedValue(null);
|
|
|
|
const request = createFormRequest({
|
|
street: '123 Main St',
|
|
city: 'Dallas',
|
|
state: 'TX',
|
|
postalCode: '75001',
|
|
country: 'US'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const err = await Promise.resolve(invokeAction('addAddress', request, locals)).catch(
|
|
(e: unknown) => e
|
|
);
|
|
expect(isHttpError(err, 400)).toBe(true);
|
|
});
|
|
|
|
it('creates address linked to profile on success', async () => {
|
|
profileFindFirst.mockResolvedValue({ id: 'profile-1' });
|
|
addressCreate.mockResolvedValue({});
|
|
|
|
const request = createFormRequest({
|
|
street: '456 Oak Ave',
|
|
city: 'Austin',
|
|
state: 'TX',
|
|
postalCode: '73301',
|
|
country: 'US'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('addAddress', request, locals);
|
|
expect(result).toEqual({ success: true, action: 'addAddress' });
|
|
expect(addressCreate).toHaveBeenCalledWith({
|
|
data: {
|
|
profileId: 'profile-1',
|
|
street: '456 Oak Ave',
|
|
city: 'Austin',
|
|
state: 'TX',
|
|
postalCode: '73301',
|
|
country: 'US'
|
|
}
|
|
});
|
|
});
|
|
|
|
it('returns fail(500) when prisma create throws', async () => {
|
|
profileFindFirst.mockResolvedValue({ id: 'profile-1' });
|
|
addressCreate.mockRejectedValue(new Error('db error'));
|
|
|
|
const request = createFormRequest({
|
|
street: '123 Main St',
|
|
city: 'Dallas',
|
|
state: 'TX',
|
|
postalCode: '75001',
|
|
country: 'US'
|
|
});
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await invokeAction('addAddress', request, locals);
|
|
expect(isActionFailure(result)).toBe(true);
|
|
expect((result as any).status).toBe(500);
|
|
});
|
|
});
|
|
});
|