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 });
|
|||
|
|
});
|
|||
|
|
});
|