Files
immich-folio/lib/__tests__/essay-source.test.ts
Ralksta ad8b09700b release: v0.19.0
Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
2026-09-29 22:50:30 +02:00

103 lines
3.5 KiB
TypeScript

import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
import fs from 'fs';
import os from 'os';
import path from 'path';
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'essay-source-'));
vi.mock('@/lib/config', () => ({
getConfig: () => ({
authSecret: 'test-auth-secret-32-chars-long-min',
subpages: [],
albumPasswords: {},
}),
}));
// Journal files come from a temp dir instead of content/journal/. Both the
// loader and lib/auth's password lookup go through this module, so the gate
// under test reads the same file the essay does.
vi.mock('@/lib/admin/journal-service', async () => {
const { parseJournalMarkdown } = await import('@/lib/journal');
const file = (slug: string) => path.join(dir, `${slug}.md`);
return {
resolveJournalFilePath: (slug: string) => file(slug),
loadEssayFromFile: (slug: string) =>
fs.existsSync(file(slug)) ? parseJournalMarkdown(fs.readFileSync(file(slug), 'utf8')) : null,
};
});
import { resolveEssayFile, generatedEssayCaption } from '@/lib/essaySource';
import { authenticate } from '@/lib/auth';
import type { ImmichAsset } from '@/lib/immich';
const write = (slug: string, frontmatter: string) =>
fs.writeFileSync(path.join(dir, `${slug}.md`), `---\n${frontmatter}\n---\n\nBody text.\n`);
const anonymous = { isAdmin: false, getCookie: () => undefined };
beforeAll(() => {
write('open', 'title: "Open"');
write('draft', 'title: "Draft"\ndraft: true');
write('locked', 'title: "Locked"\npassword: "letmein"');
});
afterAll(() => fs.rmSync(dir, { recursive: true, force: true }));
describe('resolveEssayFile', () => {
it('returns a public entry', () => {
const result = resolveEssayFile('open', anonymous);
expect(result.status).toBe('open');
});
it('treats a missing entry as missing', () => {
expect(resolveEssayFile('nope', anonymous)).toEqual({ status: 'missing' });
});
it('hides a draft from visitors', () => {
expect(resolveEssayFile('draft', anonymous)).toEqual({ status: 'missing' });
});
it('shows a draft to the admin', () => {
expect(resolveEssayFile('draft', { ...anonymous, isAdmin: true }).status).toBe('open');
});
it('locks a password-protected entry without a cookie', () => {
expect(resolveEssayFile('locked', anonymous)).toEqual({ status: 'locked' });
});
it('keeps the entry locked for the admin, like /journal/<slug> does', () => {
expect(resolveEssayFile('locked', { ...anonymous, isAdmin: true }).status).toBe('locked');
});
it('opens a protected entry with the journal cookie', async () => {
const setCookie = await authenticate('locked', 'letmein', 'journal');
expect(setCookie).not.toBeNull();
const [pair] = setCookie!.split(';');
const [name, value] = pair.split('=');
const result = resolveEssayFile('locked', {
isAdmin: false,
getCookie: (n) => (n === name ? value : undefined),
});
expect(result.status).toBe('open');
});
});
describe('generatedEssayCaption', () => {
const asset = (description?: string) =>
({ exifInfo: { description } }) as Pick<ImmichAsset, 'exifInfo'>;
it('escapes markup in an Immich description', () => {
expect(generatedEssayCaption(asset('<b>hi</b> & "you"'), true)).toBe(
'&lt;b&gt;hi&lt;/b&gt; &amp; &quot;you&quot;',
);
});
it('follows the exif.caption toggle', () => {
expect(generatedEssayCaption(asset('A caption'), false)).toBeUndefined();
});
it('leaves no caption for an empty description', () => {
expect(generatedEssayCaption(asset(' '), true)).toBeUndefined();
});
});