import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; import { parsePortfolioContent, parsePublicAssetManifest, parseSiteConfig, validateProductionInputs, } from '@/content/schemas'; const fixture = (name: string): unknown => JSON.parse(readFileSync(resolve(process.cwd(), 'tests/fixtures', name), 'utf8')); const validContent = fixture('portfolio-content.valid.json'); const validManifest = fixture('asset-manifest.valid.json'); const validSite = fixture('site-config.preview.json'); type MutableContentFixture = { navigation: Array<{ id: string }>; profile: { introduction: string }; contact: { social: Array<{ service: string; url: string }> }; projects: { selected: Array<{ visibility: string; url?: string; assetIds: string[]; title: string }>; additional: Array<{ assetIds: string[] }>; }; toolkit: Array<{ technologies: Array<{ assetId?: string }> }>; metadata: { socialPreviewAssetId?: string }; }; const cloneContent = (): MutableContentFixture => structuredClone(validContent) as MutableContentFixture; describe('content schemas', () => { it('accepts the complete synthetic public input boundary', () => { expect(parsePortfolioContent(validContent).identity.name).toBe('Luca Pîslaru'); expect(parsePublicAssetManifest(validManifest).assets).toHaveLength(5); expect(parseSiteConfig(validSite).environment).toBe('preview'); }); it('accepts an empty manifest before Phase 3 assets are prepared', () => { expect(parsePublicAssetManifest({ assets: [] })).toEqual({ assets: [] }); }); it('accepts the approved generic private project titles', () => { for (const title of [ 'Private self-hosted medication tracker', 'Private healthcare platform', 'Private game prototype', ]) { const content = cloneContent(); content.projects.selected[0]!.title = title; expect(() => parsePortfolioContent(content)).not.toThrow(); } }); it('permits omitted Phase 3 asset references while retaining supplied-reference checks', () => { const content = cloneContent(); content.projects.selected[0]!.title = 'Private self-hosted medication tracker'; for (const project of [...content.projects.selected, ...content.projects.additional]) project.assetIds = []; delete content.toolkit[0]!.technologies[0]!.assetId; delete content.metadata.socialPreviewAssetId; expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/'))).not.toThrow(); }); it('rejects supplied toolkit and metadata asset references absent from the manifest', () => { const content = cloneContent(); content.projects.selected[0]!.title = 'Private self-hosted medication tracker'; for (const project of [...content.projects.selected, ...content.projects.additional]) project.assetIds = []; content.toolkit[0]!.technologies[0]!.assetId = 'missing-toolkit-asset'; expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/'))) .toThrow(/unmanifested asset reference: missing-toolkit-asset/i); delete content.toolkit[0]!.technologies[0]!.assetId; content.metadata.socialPreviewAssetId = 'missing-social-preview'; expect(() => validateProductionInputs(content, { assets: [] }, validSite, new URL('file:///prepared/'))) .toThrow(/unmanifested asset reference: missing-social-preview/i); }); it('rejects a public link on a private project', () => { const input = cloneContent(); input.projects.selected[0]!.visibility = 'private'; input.projects.selected[0]!.url = 'https://example.test/private'; expect(() => parsePortfolioContent(input)).toThrow(/private project.*url/i); }); it('rejects unknown site configuration keys', () => { expect(() => parseSiteConfig({ environment: 'preview', canonicalUrl: 'https://portfolio.example.test', buildId: 'preview-1', contactEmail: 'forbidden@example.test', })).toThrow(/unrecognized/i); }); it('accepts only the fixed preview canonical URL and no production hostname', () => { expect(() => parseSiteConfig({ environment: 'preview', canonicalUrl: 'https://portfolio.example.test/', buildId: 'preview-1', })).toThrow(/portfolio\.localhost/i); expect(() => parseSiteConfig({ environment: 'preview', canonicalUrl: 'https://portfolio.localhost:444/', buildId: 'preview-1', })).toThrow(/portfolio\.localhost/i); expect(() => parseSiteConfig({ environment: 'production', canonicalUrl: 'https://portfolio.localhost/', buildId: 'production-1', })).toThrow(/production hostname is not approved/i); }); it('rejects preview URL normalization aliases instead of accepting their normalized value', () => { for (const canonicalUrl of [ 'https://portfolio.localhost/.', 'https://portfolio.localhost/%2e', 'https://portfolio.localhost/?', ]) { expect(() => parseSiteConfig({ environment: 'preview', canonicalUrl, buildId: 'preview-1' })) .toThrow(/exactly https:\/\/portfolio\.localhost\//i); } }); it('rejects controls, non-NFC URLs, and ports at all public URL boundaries', () => { expect(() => parseSiteConfig({ environment: 'preview', canonicalUrl: 'https://portfolio.localhost/\n', buildId: 'preview-1', })).toThrow(/url/i); expect(() => parseSiteConfig({ environment: 'preview', canonicalUrl: 'https://portfolio.localhost:443/', buildId: 'preview-1', })).toThrow(/url/i); const content = cloneContent(); content.contact.social[0]!.url = 'https://github.com:443/example'; expect(() => parsePortfolioContent(content)).toThrow(/url/i); const project = cloneContent(); project.projects.selected[1]!.url = 'https://example.test/cafe\u0301'; expect(() => parsePortfolioContent(project)).toThrow(/url/i); }); it('rejects visible copy containing an unpaired UTF-16 surrogate', () => { const content = cloneContent(); content.profile.introduction = 'Broken \ud800 copy'; expect(() => parsePortfolioContent(content)).toThrow(/visible copy/i); const wellFormedContent = cloneContent(); wellFormedContent.profile.introduction = 'Well-formed 🚗 copy'; expect(() => parsePortfolioContent(wellFormedContent)).not.toThrow(); }); it('rejects an asset reference absent from the manifest', () => { const content = cloneContent(); content.projects.selected[0]!.assetIds = ['missing-asset']; expect(() => validateProductionInputs(content, validManifest, validSite, new URL('file:///prepared/'))) .toThrow(/unmanifested asset/i); }); it('requires duplicate-free set equality between content references and manifest assets', () => { const duplicateReference = cloneContent(); duplicateReference.projects.selected[1]!.assetIds = ['selected-build-one']; expect(() => validateProductionInputs(duplicateReference, validManifest, validSite, new URL('file:///prepared/'))) .toThrow(/duplicate public asset reference/i); const unreferencedManifestAsset = cloneContent(); unreferencedManifestAsset.projects.selected[0]!.assetIds = []; expect(() => validateProductionInputs(unreferencedManifestAsset, validManifest, validSite, new URL('file:///prepared/'))) .toThrow(/unreferenced manifest asset/i); }); it('rejects duplicate manifest asset IDs and unsafe generated filenames', () => { const manifest = structuredClone(validManifest) as { assets: Array<{ id: string; fileName: string }> }; manifest.assets[1]!.id = manifest.assets[0]!.id; manifest.assets[2]!.fileName = '../original.webp'; expect(() => parsePublicAssetManifest(manifest)).toThrow(/duplicate asset id|generated safe filename/i); }); it('requires canonical navigation and generic private titles', () => { const content = cloneContent(); content.navigation[7]!.id = 'profile'; content.projects.selected[0]!.title = 'Private medical tracker'; expect(() => parsePortfolioContent(content)).toThrow(/navigation|private project title/i); }); });