- 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
179 lines
5.1 KiB
TypeScript
179 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { isHttpError } from '@sveltejs/kit';
|
|
|
|
vi.mock('$lib/db', () => ({
|
|
prisma: {
|
|
proclamation: { findMany: vi.fn() },
|
|
licenseKey: { findUnique: vi.fn() },
|
|
user: { findUnique: vi.fn() },
|
|
userProclamations: { findMany: vi.fn() },
|
|
colorPalette: { findMany: vi.fn() }
|
|
}
|
|
}));
|
|
|
|
import { prisma } from '$lib/db';
|
|
import { GET } from './+server';
|
|
|
|
type GETEvent = Parameters<typeof GET>[0];
|
|
|
|
const mockProclamationFindMany = prisma.proclamation.findMany as ReturnType<typeof vi.fn>;
|
|
const mockLicenseKey = prisma.licenseKey.findUnique as ReturnType<typeof vi.fn>;
|
|
const mockUser = prisma.user.findUnique as ReturnType<typeof vi.fn>;
|
|
const mockUserProclamations = prisma.userProclamations.findMany as ReturnType<typeof vi.fn>;
|
|
const mockColorPalette = prisma.colorPalette.findMany as ReturnType<typeof vi.fn>;
|
|
|
|
const palettes = [
|
|
{
|
|
solfeggioFrequencyId: 5,
|
|
colors: ['#32CD32', '#3CB371', '#90EE90', '#98FB98', '#ADFFA9', '#C8FFD2', '#F0FFF0']
|
|
},
|
|
{
|
|
solfeggioFrequencyId: 7,
|
|
colors: ['#00BFFF', '#1E90FF', '#4169E1', '#6495ED', '#87CEEB', '#ADD8E6', '#F0F8FF']
|
|
}
|
|
];
|
|
|
|
const peaceProclamation = {
|
|
id: 'b40fdcb9-703c-420e-af32-a081964c6f08',
|
|
name: 'Peace & Relaxation',
|
|
content: '<p>The text content</p>',
|
|
categoryId: 5,
|
|
groupId: 1,
|
|
|
|
solfeggioFrequencyId: 5,
|
|
solfeggioFrequency: { id: 5, name: 'Solar Plexus', value: 528 }
|
|
};
|
|
|
|
const crown936 = {
|
|
id: '936-hz-proclamation',
|
|
name: 'Crown 936',
|
|
content: '<p>no canonical id</p>',
|
|
categoryId: 4,
|
|
|
|
solfeggioFrequencyId: 1,
|
|
solfeggioFrequency: { id: 1, name: 'Crown', value: 936 }
|
|
};
|
|
|
|
function getRequest(headers: Record<string, string> = {}): Request {
|
|
return new Request('http://localhost/api/v2/proclamations', { headers });
|
|
}
|
|
|
|
async function expectHttpError(promise: unknown, status: number, message: string) {
|
|
try {
|
|
await promise;
|
|
} catch (e) {
|
|
expect(isHttpError(e)).toBe(true);
|
|
const err = e as { status: number; body: { message: string } };
|
|
expect(err.status).toBe(status);
|
|
expect(err.body.message).toBe(message);
|
|
return;
|
|
}
|
|
throw new Error('expected handler to throw an HttpError');
|
|
}
|
|
|
|
describe('GET /api/v2/proclamations', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('returns full proclamation records with canonical ids and embedded palettes', async () => {
|
|
mockProclamationFindMany.mockResolvedValue([peaceProclamation]);
|
|
mockColorPalette.mockResolvedValue(palettes);
|
|
|
|
const res = await GET({ request: getRequest() } as GETEvent);
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(await res.json()).toEqual([
|
|
{
|
|
id: peaceProclamation.id,
|
|
name: peaceProclamation.name,
|
|
content: peaceProclamation.content,
|
|
categoryId: 5,
|
|
groupId: 1,
|
|
solfeggioFrequencyId: 5,
|
|
colorPalette: palettes[0].colors
|
|
}
|
|
]);
|
|
});
|
|
|
|
it('drops proclamations whose frequency has no canonical id (936 Hz)', async () => {
|
|
mockProclamationFindMany.mockResolvedValue([peaceProclamation, crown936]);
|
|
mockColorPalette.mockResolvedValue(palettes);
|
|
|
|
const res = await GET({ request: getRequest() } as GETEvent);
|
|
const body = (await res.json()) as { id: string }[];
|
|
|
|
expect(body).toHaveLength(1);
|
|
expect(body[0].id).toBe(peaceProclamation.id);
|
|
});
|
|
|
|
it('returns the entitled proclamations as full records when headers are supplied', async () => {
|
|
mockLicenseKey.mockResolvedValue({ id: 'key-1', key: 'ABCD-EFGH-IJKL-MNOP' });
|
|
mockUser.mockResolvedValue({
|
|
id: 'user-1',
|
|
email: 'user@example.com',
|
|
licenseKeys: [{ id: 'key-1', key: 'ABCD-EFGH-IJKL-MNOP' }]
|
|
});
|
|
mockUserProclamations.mockResolvedValue([
|
|
{ userId: 'user-1', proclamationId: peaceProclamation.id, proclamation: peaceProclamation }
|
|
]);
|
|
mockColorPalette.mockResolvedValue(palettes);
|
|
|
|
const res = await GET({
|
|
request: getRequest({
|
|
'x-license-key': 'ABCD-EFGH-IJKL-MNOP',
|
|
'x-email': 'user@example.com'
|
|
})
|
|
} as GETEvent);
|
|
|
|
expect(res.status).toBe(200);
|
|
const body = (await res.json()) as { id: string }[];
|
|
expect(body).toHaveLength(1);
|
|
expect(body[0].id).toBe(peaceProclamation.id);
|
|
expect(mockUserProclamations).toHaveBeenCalledWith(
|
|
expect.objectContaining({ where: { userId: 'user-1' } })
|
|
);
|
|
});
|
|
|
|
it('rejects when only one entitlement header is supplied', async () => {
|
|
await expectHttpError(
|
|
GET({ request: getRequest({ 'x-license-key': 'ABCD-EFGH-IJKL-MNOP' }) } as GETEvent),
|
|
400,
|
|
'not-authorized'
|
|
);
|
|
});
|
|
|
|
it('rejects an unknown license key', async () => {
|
|
mockLicenseKey.mockResolvedValue(null);
|
|
await expectHttpError(
|
|
GET({
|
|
request: getRequest({
|
|
'x-license-key': 'UNKNOWN-KEY',
|
|
'x-email': 'user@example.com'
|
|
})
|
|
} as GETEvent),
|
|
400,
|
|
'not-authorized'
|
|
);
|
|
});
|
|
|
|
it('rejects a key that does not belong to the user', async () => {
|
|
mockLicenseKey.mockResolvedValue({ id: 'key-2', key: 'OTHER-KEY' });
|
|
mockUser.mockResolvedValue({
|
|
id: 'user-1',
|
|
email: 'user@example.com',
|
|
licenseKeys: [{ id: 'key-1', key: 'ABCD-EFGH-IJKL-MNOP' }]
|
|
});
|
|
await expectHttpError(
|
|
GET({
|
|
request: getRequest({
|
|
'x-license-key': 'OTHER-KEY',
|
|
'x-email': 'user@example.com'
|
|
})
|
|
} as GETEvent),
|
|
400,
|
|
"this key doesn't appear to belong to you"
|
|
);
|
|
});
|
|
});
|