import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; import { readProductionInputsSnapshot } from '@/entry-client'; import { serializeJsonLd } from '@/security/serialize-json-ld'; import type { ProductionInputs } from '@/content/types'; const validInputs = (): ProductionInputs => ({ content: JSON.parse(readFileSync(resolve(process.cwd(), 'content/public/portfolio-content.json'), 'utf8')), manifest: JSON.parse(readFileSync(resolve(process.cwd(), 'content/public/asset-manifest.json'), 'utf8')), site: JSON.parse(readFileSync(resolve(process.cwd(), 'config/site.preview.json'), 'utf8')), }) as ProductionInputs; const snapshotDocument = (value: unknown): Document => new DOMParser().parseFromString( ``, 'text/html', ); describe('client production-input snapshot', () => { it('schema-validates and recursively freezes the complete browser snapshot', () => { const inputs = readProductionInputsSnapshot(snapshotDocument(validInputs())); expect(Object.isFrozen(inputs)).toBe(true); expect(Object.isFrozen(inputs.content.projects.selected)).toBe(true); expect(Object.isFrozen(inputs.content.projects.selected[0]?.technicalDecisions)).toBe(true); expect(Object.isFrozen(inputs.manifest.assets)).toBe(true); expect(Object.isFrozen(inputs.site)).toBe(true); expect(() => { (inputs.content.identity as { role: string }).role = 'mutated'; }).toThrow(); }); it('rejects JSON that does not satisfy the production-input schema', () => { const invalid = validInputs() as ProductionInputs & { unexpected?: string }; invalid.unexpected = 'not allowed'; expect(() => readProductionInputsSnapshot(snapshotDocument(invalid))).toThrow(/unrecognized|invalid|expected/i); }); it('rejects a supplied asset reference absent from the supplied public manifest', () => { const invalid = validInputs(); invalid.content.projects.selected[0]!.assetIds = ['unmanifested-snapshot-asset']; expect(() => readProductionInputsSnapshot(snapshotDocument(invalid))).toThrow(/unmanifested asset reference/i); }); });