import { strict as assert } from 'node:assert';
import { readFile, rm } from 'node:fs/promises';
import path from 'node:path';
import { afterEach, beforeEach, describe, it } from 'node:test';
import type { FastifyInstance } from 'fastify';
import { SESSION_COOKIE, createSessionToken, csrfToken } from '../src/auth.ts';
import { parseSiteConfig } from '../src/config.ts';
import { ICON_URL, IdentityError, IdentityStore, sanitiseIcon } from '../src/storage/identity.ts';
import { buildApp } from '../src/server.ts';
import { FakePipeline, tempDir } from './support/harness.ts';
const BRAND = { name: 'Hops n Grains', tagline: 'Handcrafted Microbrewery', logo: '/assets/favicon.svg' };
const SECRET = 'identity-test-secret';
const SVG = '';
let dataDir: string;
beforeEach(async () => {
dataDir = await tempDir('identity');
});
afterEach(async () => {
await rm(dataDir, { recursive: true, force: true });
});
function config() {
return parseSiteConfig({
site: 'menu.example.test',
brand: BRAND,
sections: [{ slug: 'bar', label: 'Bar', order: 1 }],
});
}
async function loaded() {
const store = new IdentityStore({ dataDir, seed: { ...BRAND } });
await store.load();
return store;
}
describe('sanitiseIcon', () => {
it('strips script, event handlers and remote references but keeps the drawing', () => {
const dirty = ``;
const clean = sanitiseIcon(dirty);
assert.doesNotMatch(clean, /script/i);
assert.doesNotMatch(clean, /onload|onclick/i);
assert.doesNotMatch(clean, /foreignObject/i);
assert.doesNotMatch(clean, /javascript:/i);
assert.doesNotMatch(clean, //);
});
});
describe('IdentityStore', () => {
it('seeds from the config the first time and writes the file', async () => {
const store = await loaded();
assert.deepEqual(store.view(), { name: BRAND.name, tagline: BRAND.tagline, showDates: true, hasIcon: false });
const file = JSON.parse(await readFile(path.join(dataDir, 'site.json'), 'utf8'));
assert.equal(file.name, BRAND.name);
});
it('changes the name, tagline and date switch without touching the config', async () => {
const store = await loaded();
const updated = await store.update({ name: ' Hops and Grains ', tagline: '', showDates: false });
assert.deepEqual(updated, { name: 'Hops and Grains', tagline: null, showDates: false, hasIcon: false });
const applied = store.apply(config());
assert.equal(applied.brand.name, 'Hops and Grains');
assert.equal(applied.brand.tagline, null);
assert.equal(applied.display.showDates, false);
assert.equal(config().brand.name, 'Hops n Grains', 'the config is only the seed');
});
it('rejects an empty name', async () => {
const store = await loaded();
await assert.rejects(() => store.update({ name: ' ' }), IdentityError);
});
it('takes an uploaded icon and points the Site at it', async () => {
const store = await loaded();
await store.putIcon(Buffer.from(SVG));
assert.equal(store.view().hasIcon, true);
assert.equal(store.apply(config()).brand.logo, ICON_URL);
assert.match(await readFile(store.iconPath(), 'utf8'), /