165 lines
6.0 KiB
TypeScript
165 lines
6.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
|
||
vi.mock('@/lib/env', () => ({
|
||
env: {
|
||
IMMICH_API_URL: 'http://localhost:2283',
|
||
IMMICH_API_KEY: 'test-key',
|
||
SITE_TITLE: 'Test',
|
||
SITE_SUBTITLE: '',
|
||
CACHE_TTL: 300,
|
||
RATE_LIMIT_RPM: 120,
|
||
},
|
||
normalizeApiUrl: (raw: string) => raw.replace(/\/+$/, ''),
|
||
}));
|
||
|
||
vi.mock('@/lib/secret', () => ({
|
||
resolveAuthSecret: () => 'test-auth-secret-32-chars-long-min',
|
||
}));
|
||
|
||
vi.mock('@/lib/config/parser', () => ({
|
||
loadYaml: vi.fn(),
|
||
clearYamlCache: vi.fn(),
|
||
validateUuid: (id: string) => id,
|
||
}));
|
||
|
||
import { deriveGallery, getConfig, invalidateConfigCache, resolveZoom } from '@/lib/config';
|
||
import { loadYaml } from '@/lib/config/parser';
|
||
import type { GalleryYaml } from '@/lib/config/schema';
|
||
|
||
const A = '11111111-1111-1111-1111-111111111111';
|
||
const B = '22222222-2222-2222-2222-222222222222';
|
||
|
||
/**
|
||
* Lightbox zoom (#467): a site-wide switch in settings.yaml, off by default,
|
||
* overridden by a subpage and then by an album, each in either direction.
|
||
*/
|
||
describe('resolveZoom', () => {
|
||
const config = (zoom: boolean, standaloneAlbumZoom: Record<string, boolean> = {}) => ({
|
||
zoom,
|
||
standaloneAlbumZoom,
|
||
});
|
||
|
||
it('follows the site setting when nothing more specific is set', () => {
|
||
expect(resolveZoom(config(false), A)).toBe(false);
|
||
expect(resolveZoom(config(true), A)).toBe(true);
|
||
expect(resolveZoom(config(true), A, {})).toBe(true);
|
||
});
|
||
|
||
it('lets a subpage override the site, both ways', () => {
|
||
expect(resolveZoom(config(false), A, { zoom: true })).toBe(true);
|
||
expect(resolveZoom(config(true), A, { zoom: false })).toBe(false);
|
||
});
|
||
|
||
it('lets an album entry override its subpage and the site, both ways', () => {
|
||
expect(resolveZoom(config(false), A, { zoom: false, albumZoom: { [A]: true } })).toBe(true);
|
||
expect(resolveZoom(config(true), A, { zoom: true, albumZoom: { [A]: false } })).toBe(false);
|
||
// Only that album.
|
||
expect(resolveZoom(config(true), B, { zoom: true, albumZoom: { [A]: false } })).toBe(true);
|
||
});
|
||
|
||
it('reads the standalone override only for the standalone route', () => {
|
||
const c = config(true, { [A]: false });
|
||
expect(resolveZoom(c, A)).toBe(false);
|
||
// On a subpage the standalone entry has no say.
|
||
expect(resolveZoom(c, A, {})).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('deriveGallery keeps album zoom per route (review of #830)', () => {
|
||
/*
|
||
* The reported leak: one album on an open page with zoom off and on a
|
||
* password page with zoom on. A single map let the last entry decide for
|
||
* both, so the open page served zoom its owner had switched off.
|
||
*/
|
||
const pages = (first: boolean) => {
|
||
const open = { name: 'Public', albums: [{ [A]: { zoom: false } }] };
|
||
const locked = { name: 'Client', password: 'pw', albums: [{ [A]: { zoom: true } }] };
|
||
return deriveGallery({ subpages: first ? [open, locked] : [locked, open] } as GalleryYaml);
|
||
};
|
||
|
||
it.each([true, false])('keeps each page’s own value (open page first: %s)', (openFirst) => {
|
||
const derived = pages(openFirst);
|
||
const open = derived.subpages.find((sp) => sp.slug === 'public')!;
|
||
const locked = derived.subpages.find((sp) => sp.slug === 'client')!;
|
||
const cfg = { zoom: false, standaloneAlbumZoom: derived.standaloneAlbumZoom };
|
||
expect(resolveZoom(cfg, A, open)).toBe(false);
|
||
expect(resolveZoom(cfg, A, locked)).toBe(true);
|
||
});
|
||
|
||
it('keeps a standalone entry apart from a subpage entry for the same album', () => {
|
||
const derived = deriveGallery({
|
||
albums: [{ [A]: { zoom: true } }],
|
||
subpages: [{ name: 'S', albums: [{ [A]: { zoom: false } }] }],
|
||
} as GalleryYaml);
|
||
expect(derived.standaloneAlbumZoom).toEqual({ [A]: true });
|
||
expect(derived.subpages[0].albumZoom).toEqual({ [A]: false });
|
||
});
|
||
|
||
it('collects section entries on their subpage, and false wins within one page', () => {
|
||
const derived = deriveGallery({
|
||
subpages: [
|
||
{
|
||
name: 'S',
|
||
albums: [{ [B]: { zoom: true } }],
|
||
sections: [
|
||
{ title: 'One', albums: [{ [A]: { zoom: true } }] },
|
||
{ title: 'Two', albums: [{ [A]: { zoom: false } }] },
|
||
],
|
||
},
|
||
{ name: 'T', albums: [A] },
|
||
],
|
||
} as GalleryYaml);
|
||
expect(derived.subpages[0].albumZoom).toEqual({ [A]: false, [B]: true });
|
||
expect(derived.subpages[1].albumZoom).toBeUndefined();
|
||
});
|
||
|
||
it('ignores anything but a boolean', () => {
|
||
const odd = deriveGallery({ albums: [{ [A]: { zoom: 'yes' } }] } as unknown as GalleryYaml);
|
||
expect(odd.standaloneAlbumZoom).toEqual({});
|
||
});
|
||
|
||
it('keeps a subpage’s zoom in both gallery.yaml shapes', () => {
|
||
const list = deriveGallery({
|
||
subpages: [
|
||
{ name: 'On', albums: [A], zoom: true },
|
||
{ name: 'Off', albums: [B], zoom: false },
|
||
{ name: 'Unset', albums: [A] },
|
||
],
|
||
} as GalleryYaml);
|
||
expect(list.subpages.map((sp) => sp.zoom)).toEqual([true, false, undefined]);
|
||
|
||
const record = deriveGallery({
|
||
subpages: { Travel: { albums: [{ [A]: { zoom: false } }], zoom: true } },
|
||
} as unknown as GalleryYaml);
|
||
expect(record.subpages[0].zoom).toBe(true);
|
||
expect(record.subpages[0].albumZoom).toEqual({ [A]: false });
|
||
});
|
||
});
|
||
|
||
describe('the site setting', () => {
|
||
beforeEach(() => invalidateConfigCache());
|
||
|
||
const withSettings = (settings: Record<string, unknown>) =>
|
||
vi.mocked(loadYaml).mockImplementation((filename: string) => {
|
||
if (filename === 'gallery.yaml') return { albums: [{ [A]: { zoom: false } }] };
|
||
if (filename === 'settings.yaml') return settings;
|
||
return null;
|
||
});
|
||
|
||
it('is off unless settings.yaml says true', () => {
|
||
withSettings({});
|
||
expect(getConfig().zoom).toBe(false);
|
||
invalidateConfigCache();
|
||
withSettings({ zoom: 'true' });
|
||
expect(getConfig().zoom).toBe(false);
|
||
invalidateConfigCache();
|
||
withSettings({ zoom: true });
|
||
expect(getConfig().zoom).toBe(true);
|
||
});
|
||
|
||
it('carries the standalone album overrides into the config', () => {
|
||
withSettings({ zoom: true });
|
||
expect(getConfig().standaloneAlbumZoom).toEqual({ [A]: false });
|
||
});
|
||
});
|