import { describe, it, expect, vi } from 'vitest'; vi.mock('@/lib/env', () => ({ env: { IMMICH_API_URL: 'http://localhost:2283', IMMICH_API_KEY: 'test-key', SITE_TITLE: 'Test Gallery', SITE_SUBTITLE: '', CACHE_TTL: 300, IMMICH_TIMEOUT_MS: 15000, RATE_LIMIT_RPM: 120, TRUSTED_PROXY_HOPS: 0, }, })); vi.mock('@/lib/config/parser', () => ({ loadYaml: vi.fn(), clearYamlCache: vi.fn(), validateUuid: (id: string) => id, })); import { deriveGallery } from '@/lib/config'; import type { GalleryYaml } from '@/lib/config/schema'; const A = '11111111-1111-1111-1111-111111111111'; const B = '22222222-2222-2222-2222-222222222222'; /** * The admin PUT calls this before writing, so that a save the site cannot load * is rejected with a 400 instead of reported as successful. These are the exact * shapes the page builder can produce. */ describe('deriveGallery rejects what the site cannot load', () => { it('rejects a gallery with neither albums nor subpages', () => { expect(() => deriveGallery({ albums: [] } as GalleryYaml)).toThrow(/at least one album/i); }); it('rejects a subpage with no name', () => { expect(() => deriveGallery({ albums: [A], subpages: [{ albums: [A] }] } as unknown as GalleryYaml), ).toThrow(/must have a name/i); }); it('rejects a subpage with neither albums nor sections', () => { expect(() => deriveGallery({ albums: [A], subpages: [{ name: 'Empty', albums: [] }], } as unknown as GalleryYaml), ).toThrow(/Empty/); }); // The message reaches the admin UI as the 400 body, so it has to identify // which subpage is at fault — "invalid gallery" would be useless in a builder // with a dozen subpages. it('names the offending subpage so the error is actionable', () => { expect(() => deriveGallery({ albums: [A], subpages: [ { name: 'Fine', albums: [A] }, { name: 'Broken One', albums: [] }, ], } as unknown as GalleryYaml), ).toThrow(/Broken One/); }); }); describe('deriveGallery accepts valid structures', () => { it('derives standalone albums', () => { const result = deriveGallery({ albums: [A, B] } as GalleryYaml); expect(result.albums).toEqual([A, B]); expect(result.standaloneAlbums).toEqual([A, B]); expect(result.subpages).toEqual([]); }); it('treats an album inside a subpage as no longer standalone', () => { const result = deriveGallery({ albums: [A], subpages: [{ name: 'Trips', albums: [B] }], } as unknown as GalleryYaml); expect(result.albums).toEqual([A, B]); expect(result.standaloneAlbums).toEqual([A]); expect(result.subpages[0].slug).toBe('trips'); }); it('accepts a subpage that has sections instead of albums', () => { const result = deriveGallery({ subpages: [{ name: 'Work', sections: [{ title: 'Recent Work', albums: [A] }] }], } as unknown as GalleryYaml); expect(result.subpages[0].sections?.[0].slug).toBe('recent-work'); expect(result.albums).toEqual([A]); }); it('collects per-album overrides and hero images', () => { const result = deriveGallery({ albums: [{ [A]: { title: 'Renamed', description: 'A note', heroImage: B } }], } as unknown as GalleryYaml); expect(result.albumOverrides[A]).toBe('Renamed'); expect(result.albumDescriptions[A]).toBe('A note'); expect(result.albumHeroImages[A]).toBe(B); }); it('parses enabled property on subpages correctly', () => { const result = deriveGallery({ subpages: [ { name: 'Active Page', albums: [A], enabled: true }, { name: 'Disabled Page', albums: [B], enabled: false }, ], } as unknown as GalleryYaml); expect(result.subpages[0].enabled).toBe(true); expect(result.subpages[1].enabled).toBe(false); }); });