From 2bbee9859459f944fef494d4e7fe3e50cf411c18 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Thu, 10 Sep 2026 16:19:53 -0500 Subject: [PATCH] Add dedicated v2 proclamation category/group endpoints and catalog specs --- src/routes/api/v2/categories/server.spec.ts | 55 +++++++++++++++++++ .../api/v2/color-palettes/server.spec.ts | 50 +++++++++++++++++ src/routes/api/v2/frequencies/server.spec.ts | 23 ++++++++ .../api/v2/proclamation-categories/+server.ts | 3 + .../v2/proclamation-categories/server.spec.ts | 45 +++++++++++++++ .../api/v2/proclamation-groups/+server.ts | 3 + .../api/v2/proclamation-groups/server.spec.ts | 45 +++++++++++++++ 7 files changed, 224 insertions(+) create mode 100644 src/routes/api/v2/categories/server.spec.ts create mode 100644 src/routes/api/v2/color-palettes/server.spec.ts create mode 100644 src/routes/api/v2/frequencies/server.spec.ts create mode 100644 src/routes/api/v2/proclamation-categories/+server.ts create mode 100644 src/routes/api/v2/proclamation-categories/server.spec.ts create mode 100644 src/routes/api/v2/proclamation-groups/+server.ts create mode 100644 src/routes/api/v2/proclamation-groups/server.spec.ts diff --git a/src/routes/api/v2/categories/server.spec.ts b/src/routes/api/v2/categories/server.spec.ts new file mode 100644 index 0000000..78481c1 --- /dev/null +++ b/src/routes/api/v2/categories/server.spec.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('$lib/db', () => ({ + prisma: { + proclamationCategory: { findMany: vi.fn() } + } +})); + +import { prisma } from '$lib/db'; +import { GET } from './+server'; + +type GETEvent = Parameters[0]; + +const mockFindMany = prisma.proclamationCategory.findMany as ReturnType; + +describe('GET /api/v2/categories', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns serialized categories', async () => { + mockFindMany.mockResolvedValue([ + { id: 1, name: 'Abundance', sortOrder: 0 }, + { id: 2, name: 'Health', sortOrder: 1 } + ]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual([ + { id: 1, name: 'Abundance', sortOrder: 0 }, + { id: 2, name: 'Health', sortOrder: 1 } + ]); + expect(mockFindMany).toHaveBeenCalledTimes(1); + }); + + it('returns an empty array when there are no categories', async () => { + mockFindMany.mockResolvedValue([]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual([]); + }); + + it('only exposes the v2 fields, dropping relations and extra columns', async () => { + mockFindMany.mockResolvedValue([ + { id: 3, name: 'Love', sortOrder: 2, proclamations: [{ id: 'x' }] } + ]); + + const res = await GET({} as GETEvent); + + expect(await res.json()).toEqual([{ id: 3, name: 'Love', sortOrder: 2 }]); + }); +}); diff --git a/src/routes/api/v2/color-palettes/server.spec.ts b/src/routes/api/v2/color-palettes/server.spec.ts new file mode 100644 index 0000000..4f358c9 --- /dev/null +++ b/src/routes/api/v2/color-palettes/server.spec.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('$lib/db', () => ({ + prisma: { + colorPalette: { findMany: vi.fn() } + } +})); + +import { prisma } from '$lib/db'; +import { CANONICAL_FREQUENCIES, CANONICAL_PALETTES } from '$lib/server/v2-serializers'; +import { GET } from './+server'; + +type GETEvent = Parameters[0]; + +const mockFindMany = prisma.colorPalette.findMany as ReturnType; + +type Palette = { solfeggioFrequencyId: number; colors: string[] }; + +describe('GET /api/v2/color-palettes', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('prefers database colors and falls back to the canonical palette', async () => { + const dbColors = ['#111111', '#222222', '#333333', '#444444', '#555555', '#666666', '#777777']; + mockFindMany.mockResolvedValue([{ solfeggioFrequencyId: 5, colors: dbColors }]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + const body = (await res.json()) as Palette[]; + expect(body).toHaveLength(9); + expect(body.find((p) => p.solfeggioFrequencyId === 5)?.colors).toEqual(dbColors); + expect(body.find((p) => p.solfeggioFrequencyId === 1)?.colors).toEqual(CANONICAL_PALETTES[1]); + }); + + it('returns the canonical fallback for every id when the table is empty', async () => { + mockFindMany.mockResolvedValue([]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + const body = (await res.json()) as Palette[]; + expect(body).toHaveLength(9); + expect(body.map((p) => p.solfeggioFrequencyId)).toEqual(CANONICAL_FREQUENCIES.map((f) => f.id)); + for (const palette of body) { + expect(palette.colors).toEqual(CANONICAL_PALETTES[palette.solfeggioFrequencyId]); + } + }); +}); diff --git a/src/routes/api/v2/frequencies/server.spec.ts b/src/routes/api/v2/frequencies/server.spec.ts new file mode 100644 index 0000000..99c8f1d --- /dev/null +++ b/src/routes/api/v2/frequencies/server.spec.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { CANONICAL_FREQUENCIES } from '$lib/server/v2-serializers'; +import { GET } from './+server'; + +type GETEvent = Parameters[0]; + +describe('GET /api/v2/frequencies', () => { + it('returns the nine canonical solfeggio frequencies', async () => { + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual(CANONICAL_FREQUENCIES); + }); + + it('exposes sequential canonical ids 1-9', async () => { + const res = await GET({} as GETEvent); + const body = (await res.json()) as { id: number; name: string; value: number }[]; + + expect(body).toHaveLength(9); + expect(body.map((f) => f.id)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(body.every((f) => f.name.length > 0 && f.value > 0)).toBe(true); + }); +}); diff --git a/src/routes/api/v2/proclamation-categories/+server.ts b/src/routes/api/v2/proclamation-categories/+server.ts new file mode 100644 index 0000000..dd619b3 --- /dev/null +++ b/src/routes/api/v2/proclamation-categories/+server.ts @@ -0,0 +1,3 @@ +// Dedicated alias for the proclamation categories collection. The canonical +// handler lives at /api/v2/categories; both paths return the same payload. +export { GET } from '../categories/+server'; diff --git a/src/routes/api/v2/proclamation-categories/server.spec.ts b/src/routes/api/v2/proclamation-categories/server.spec.ts new file mode 100644 index 0000000..ba269cc --- /dev/null +++ b/src/routes/api/v2/proclamation-categories/server.spec.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('$lib/db', () => ({ + prisma: { + proclamationCategory: { findMany: vi.fn() } + } +})); + +import { prisma } from '$lib/db'; +import { GET } from './+server'; + +type GETEvent = Parameters[0]; + +const mockFindMany = prisma.proclamationCategory.findMany as ReturnType; + +describe('GET /api/v2/proclamation-categories', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns the same serialized categories as /api/v2/categories', async () => { + mockFindMany.mockResolvedValue([ + { id: 1, name: 'Abundance', sortOrder: 0 }, + { id: 4, name: 'Protection', sortOrder: 3 } + ]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual([ + { id: 1, name: 'Abundance', sortOrder: 0 }, + { id: 4, name: 'Protection', sortOrder: 3 } + ]); + expect(mockFindMany).toHaveBeenCalledTimes(1); + }); + + it('returns an empty array when there are no categories', async () => { + mockFindMany.mockResolvedValue([]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual([]); + }); +}); diff --git a/src/routes/api/v2/proclamation-groups/+server.ts b/src/routes/api/v2/proclamation-groups/+server.ts new file mode 100644 index 0000000..e8b88b5 --- /dev/null +++ b/src/routes/api/v2/proclamation-groups/+server.ts @@ -0,0 +1,3 @@ +// Dedicated alias for the proclamation groups collection. The canonical +// handler lives at /api/v2/groups; both paths return the same payload. +export { GET } from '../groups/+server'; diff --git a/src/routes/api/v2/proclamation-groups/server.spec.ts b/src/routes/api/v2/proclamation-groups/server.spec.ts new file mode 100644 index 0000000..3dad2d1 --- /dev/null +++ b/src/routes/api/v2/proclamation-groups/server.spec.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('$lib/db', () => ({ + prisma: { + proclamationGroup: { findMany: vi.fn() } + } +})); + +import { prisma } from '$lib/db'; +import { GET } from './+server'; + +type GETEvent = Parameters[0]; + +const mockFindMany = prisma.proclamationGroup.findMany as ReturnType; + +describe('GET /api/v2/proclamation-groups', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns groups ordered by sortOrder', async () => { + mockFindMany.mockResolvedValue([ + { id: 1, name: 'General', sortOrder: 0 }, + { id: 2, name: 'Specific', sortOrder: 1 } + ]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual([ + { id: 1, name: 'General', sortOrder: 0 }, + { id: 2, name: 'Specific', sortOrder: 1 } + ]); + expect(mockFindMany).toHaveBeenCalledWith({ orderBy: { sortOrder: 'asc' } }); + }); + + it('returns an empty array when there are no groups', async () => { + mockFindMany.mockResolvedValue([]); + + const res = await GET({} as GETEvent); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual([]); + }); +});