Files
Portfolio/tests/unit/content-schemas.test.ts

122 lines
5.1 KiB
TypeScript

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