- Replace per-model admin forms with the shared generic AdminTable/AdminModal CRUD - Add ProclamationGroup model and admin + API v2 routes (replaces subscription levels) - Drop subscription levels and flatten proclamation categories - Update v2 serializers and seed data accordingly
228 lines
7.1 KiB
TypeScript
228 lines
7.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { createLocals, mockUser } from '$lib/test-utils';
|
|
import { getModelConfig } from './registry';
|
|
|
|
// Ensure superValidate can be imported from server path in Node.js/SSR context
|
|
import { superValidate } from 'sveltekit-superforms/server';
|
|
import { zod4 } from 'sveltekit-superforms/adapters';
|
|
|
|
vi.mock('$lib/db', () => {
|
|
const mkdel = () => ({
|
|
findMany: vi.fn(),
|
|
create: vi.fn(),
|
|
update: vi.fn(),
|
|
delete: vi.fn(),
|
|
findUnique: vi.fn(),
|
|
findFirst: vi.fn()
|
|
});
|
|
return {
|
|
prisma: {
|
|
userRole: mkdel(),
|
|
proclamation: mkdel(),
|
|
proclamationCategory: mkdel(),
|
|
proclamationGroup: mkdel(),
|
|
solfeggioFrequency: mkdel()
|
|
}
|
|
};
|
|
});
|
|
|
|
import { prisma } from '$lib/db';
|
|
import { loadRecords, upsertRecord, deleteRecord } from './crud';
|
|
|
|
const userRoleFindMany = prisma.userRole.findMany as ReturnType<typeof vi.fn>;
|
|
const create = prisma.userRole.create as ReturnType<typeof vi.fn>;
|
|
const update = prisma.userRole.update as ReturnType<typeof vi.fn>;
|
|
const deletes = prisma.userRole.delete as ReturnType<typeof vi.fn>;
|
|
const findUnique = prisma.userRole.findUnique as ReturnType<typeof vi.fn>;
|
|
const findFirst = prisma.userRole.findFirst as ReturnType<typeof vi.fn>;
|
|
const config = getModelConfig('user-roles')!;
|
|
|
|
describe('crud loadRecords', () => {
|
|
beforeEach(() => {
|
|
userRoleFindMany.mockReset();
|
|
});
|
|
|
|
it('returns records', async () => {
|
|
userRoleFindMany.mockResolvedValue([{ id: 1, name: 'Admin' }]);
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await loadRecords(locals, config);
|
|
|
|
expect(result.records).toEqual([{ id: 1, name: 'Admin' }]);
|
|
});
|
|
|
|
it('returns empty records array', async () => {
|
|
userRoleFindMany.mockResolvedValue([]);
|
|
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await loadRecords(locals, config);
|
|
|
|
expect(result.records).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('crud upsertRecord', () => {
|
|
beforeEach(() => {
|
|
create.mockReset();
|
|
update.mockReset();
|
|
findUnique.mockReset();
|
|
findFirst.mockReset();
|
|
});
|
|
|
|
it('creates a new record when no id', async () => {
|
|
create.mockResolvedValue({ id: 2, name: 'Editor' });
|
|
|
|
const fd = new FormData();
|
|
fd.set('name', 'Editor');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await upsertRecord(fd, config, locals);
|
|
|
|
expect(create).toHaveBeenCalledWith({ data: { name: 'Editor' } });
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
|
|
it('updates an existing record when id is present', async () => {
|
|
findUnique.mockResolvedValue({ id: 1, name: 'Old Name' });
|
|
|
|
const fd = new FormData();
|
|
fd.set('id', '1');
|
|
fd.set('name', 'New Name');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await upsertRecord(fd, config, locals);
|
|
|
|
expect(update).toHaveBeenCalledWith({ where: { id: 1 }, data: { name: 'New Name' } });
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
|
|
it('returns error when updating non-existent record', async () => {
|
|
findUnique.mockResolvedValue(null);
|
|
|
|
const fd = new FormData();
|
|
fd.set('id', '999');
|
|
fd.set('name', 'Ghost');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await upsertRecord(fd, config, locals);
|
|
|
|
expect(result).toEqual({ success: false, error: 'Record not found' });
|
|
});
|
|
});
|
|
|
|
describe('crud deleteRecord', () => {
|
|
beforeEach(() => {
|
|
deletes.mockReset();
|
|
});
|
|
|
|
it('deletes a record by id', async () => {
|
|
deletes.mockResolvedValue({ id: 1, name: 'Admin' });
|
|
|
|
const fd = new FormData();
|
|
fd.set('id', '1');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await deleteRecord(fd, config, locals);
|
|
|
|
expect(deletes).toHaveBeenCalledWith({ where: { id: 1 } });
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
|
|
it('returns error when id is missing', async () => {
|
|
const fd = new FormData();
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await deleteRecord(fd, config, locals);
|
|
|
|
expect(result).toEqual({ success: false, error: 'Missing id' });
|
|
});
|
|
});
|
|
|
|
describe('P2002 unique constraint handling', () => {
|
|
beforeEach(() => {
|
|
create.mockReset();
|
|
});
|
|
|
|
it('returns error with field names when meta.target is populated', async () => {
|
|
const p2002 = new Error('Unique constraint failed');
|
|
(p2002 as any).code = 'P2002';
|
|
(p2002 as any).meta = { target: ['name'], modelName: 'UserRole' };
|
|
create.mockRejectedValue(p2002);
|
|
|
|
const fd = new FormData();
|
|
fd.set('name', 'Admin');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await upsertRecord(fd, config, locals);
|
|
|
|
expect(result).toEqual({
|
|
success: false,
|
|
error: 'A record with this name already exists.',
|
|
fieldErrors: { name: 'Already taken' }
|
|
});
|
|
});
|
|
|
|
it('returns generic message when meta.target is empty', async () => {
|
|
const p2002 = new Error('Unique constraint failed');
|
|
(p2002 as any).code = 'P2002';
|
|
(p2002 as any).meta = { target: [], modelName: 'UserRole' };
|
|
create.mockRejectedValue(p2002);
|
|
|
|
const fd = new FormData();
|
|
fd.set('name', 'Admin');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await upsertRecord(fd, config, locals);
|
|
|
|
expect(result).toEqual({
|
|
success: false,
|
|
error: 'A database constraint violation occurred. The record could not be created.'
|
|
});
|
|
});
|
|
|
|
it('returns generic message when meta.target is missing', async () => {
|
|
const p2002 = new Error('Unique constraint failed');
|
|
(p2002 as any).code = 'P2002';
|
|
(p2002 as any).meta = { modelName: 'UserRole' };
|
|
create.mockRejectedValue(p2002);
|
|
|
|
const fd = new FormData();
|
|
fd.set('name', 'Admin');
|
|
const locals = createLocals(mockUser() as any);
|
|
const result = await upsertRecord(fd, config, locals);
|
|
|
|
expect(result).toEqual({
|
|
success: false,
|
|
error: 'A database constraint violation occurred. The record could not be created.'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('sveltekit-superforms import compatibility', () => {
|
|
it('imports superValidate from server path without pulling in SuperDebug.svelte', async () => {
|
|
expect(superValidate).toBeDefined();
|
|
});
|
|
|
|
it('calls superValidate with a Request and validates against a Zod schema', async () => {
|
|
const { z } = await import('zod');
|
|
const schema = z.object({ name: z.string().min(1) });
|
|
const body = new URLSearchParams({ name: 'test' });
|
|
const request = new Request('http://localhost', {
|
|
method: 'POST',
|
|
body,
|
|
headers: { 'content-type': 'application/x-www-form-urlencoded' }
|
|
});
|
|
const form = await superValidate(request, zod4(schema));
|
|
expect(form.valid).toBe(true);
|
|
expect(form.data).toEqual({ name: 'test' });
|
|
});
|
|
});
|
|
|
|
describe('prisma client version consistency', () => {
|
|
it('generated client references existing query compiler files', async () => {
|
|
const fs = await import('fs');
|
|
const path = await import('path');
|
|
const runtimeDir = path.resolve('node_modules/@prisma/client/runtime');
|
|
|
|
const generatedFile = path.resolve('src/lib/generated/prisma/internal/class.ts');
|
|
const content = fs.readFileSync(generatedFile, 'utf-8');
|
|
const compilerMatch = content.match(/import\(["']@prisma\/client\/runtime\/([^"']+)["']\)/);
|
|
expect(compilerMatch).not.toBeNull();
|
|
const compilerPath = path.join(runtimeDir, compilerMatch![1]);
|
|
expect(fs.existsSync(compilerPath)).toBe(true);
|
|
});
|
|
});
|