feat: define closed portfolio input contract

This commit is contained in:
2026-09-02 00:31:43 +03:00
parent 51af112e21
commit 4a704d89f7
20 changed files with 819 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
{
"assets": [
{ "id": "selected-build-one", "fileName": "selected-build-one.webp", "sha256": "623e4a6498ce0604d88667669b732afdc42bec62b50929d73380e7cc717fede4", "mediaType": "image/webp", "width": 1, "height": 1, "durationMs": null, "rightsHolder": "Example Rights Holder", "license": "CC0-1.0", "attribution": "Example Rights Holder" },
{ "id": "selected-build-two", "fileName": "selected-build-two.webp", "sha256": "623e4a6498ce0604d88667669b732afdc42bec62b50929d73380e7cc717fede4", "mediaType": "image/webp", "width": 1, "height": 1, "durationMs": null, "rightsHolder": "Example Rights Holder", "license": "CC0-1.0", "attribution": "Example Rights Holder" },
{ "id": "research-panel", "fileName": "research-panel.webp", "sha256": "623e4a6498ce0604d88667669b732afdc42bec62b50929d73380e7cc717fede4", "mediaType": "image/webp", "width": 1, "height": 1, "durationMs": null, "rightsHolder": "Example Rights Holder", "license": "CC0-1.0", "attribution": "Example Rights Holder" },
{ "id": "toolkit-linux", "fileName": "toolkit-linux.webp", "sha256": "623e4a6498ce0604d88667669b732afdc42bec62b50929d73380e7cc717fede4", "mediaType": "image/webp", "width": 1, "height": 1, "durationMs": null, "rightsHolder": "Example Rights Holder", "license": "CC0-1.0", "attribution": "Example Rights Holder" },
{ "id": "social-preview", "fileName": "social-preview.webp", "sha256": "623e4a6498ce0604d88667669b732afdc42bec62b50929d73380e7cc717fede4", "mediaType": "image/webp", "width": 1, "height": 1, "durationMs": null, "rightsHolder": "Example Rights Holder", "license": "CC0-1.0", "attribution": "Example Rights Holder" }
]
}

View File

@@ -0,0 +1,114 @@
{
"identity": {
"name": "Luca Pîslaru",
"role": "System Administrator & Full-Stack Developer",
"valueProposition": "Building and operating reliable systems and software."
},
"navigation": [
{ "id": "start", "label": "Start" },
{ "id": "profile", "label": "Profile" },
{ "id": "experience", "label": "Experience" },
{ "id": "infrastructure", "label": "Infrastructure" },
{ "id": "selected-builds", "label": "Selected builds" },
{ "id": "additional-work", "label": "Additional work" },
{ "id": "toolkit", "label": "Toolkit" },
{ "id": "contact", "label": "Contact" }
],
"profile": {
"introduction": "A systems and software practitioner who learns by building.",
"screeningFacts": [{ "label": "Work preference", "value": "Remote" }]
},
"experience": [{
"id": "experience-operations",
"role": "Systems Administrator",
"organization": "Example Systems",
"dates": "Jan 2024 – Present",
"location": "Remote",
"highlights": ["Operated Linux services.", "Maintained deployment workflows."]
}],
"systemsOperations": {
"id": "systems-operations",
"title": "Professional systems operations",
"summary": "Verified operational work across service delivery.",
"evidence": ["Deployment lifecycle and troubleshooting."]
},
"infrastructure": {
"id": "infrastructure-case-study",
"title": "Secure self-hosted platform and lifecycle",
"summary": "A compact, privacy-respecting infrastructure case study.",
"bands": [
{ "id": "ingress-tls", "title": "Ingress and TLS", "summary": "Managed encrypted ingress." },
{ "id": "identity-access", "title": "Identity and access", "summary": "Protected service access." },
{ "id": "service-lifecycle", "title": "Service lifecycle", "summary": "Maintained representative services." },
{ "id": "operations", "title": "Operations", "summary": "Resolved routine service issues." }
]
},
"projects": {
"selected": [
{
"id": "private-build-one",
"title": "Private project",
"visibility": "private",
"maturity": "active",
"summary": "A private multi-device application.",
"problem": "A recurring workflow needed reliable tracking.",
"contribution": "Designed and maintained the application.",
"technicalDecisions": ["Used a synchronised client and server design."],
"outcome": "The work remains private.",
"assetIds": ["selected-build-one"]
},
{
"id": "public-build-two",
"title": "Footy Tracker",
"visibility": "public",
"maturity": "work-in-progress",
"summary": "A computer-vision pipeline for football footage.",
"problem": "Footage needs repeatable player tracking.",
"contribution": "Built the detection and tracking pipeline.",
"technicalDecisions": ["Separated detection from jersey-recognition experiments."],
"outcome": "Jersey recognition remains ongoing work.",
"url": "https://github.com/example/footy-tracker",
"assetIds": ["selected-build-two"]
},
{
"id": "research-panel",
"title": "Opinion Dynamics Simulation",
"visibility": "public",
"maturity": "experimental",
"summary": "An experimental agent-based simulation.",
"problem": "Explore changing group opinions.",
"contribution": "Built the experimental model and charts.",
"technicalDecisions": ["Used deterministic simulation runs."],
"outcome": "Results are presented as research evidence.",
"assetIds": ["research-panel"]
}
],
"additional": [{
"id": "additional-public-work",
"title": "Currency Converter",
"visibility": "public",
"maturity": "learning",
"summary": "A modest desktop learning project.",
"problem": "Practice currency conversion workflows.",
"contribution": "Implemented the application.",
"technicalDecisions": ["Used a typed desktop UI."],
"outcome": "A focused learning exercise.",
"assetIds": []
}]
},
"toolkit": [{
"title": "Systems & delivery",
"technologies": [{ "name": "Linux", "assetId": "toolkit-linux" }]
}],
"contact": {
"email": "hello@example.test",
"social": [{ "service": "GitHub", "url": "https://github.com/example" }],
"cvMessage": "For a current CV or role-specific information, contact me directly."
},
"metadata": {
"title": "Luca Pîslaru — System Administrator & Full-Stack Developer",
"description": "Portfolio of Luca Pîslaru.",
"socialPreviewAssetId": "social-preview"
},
"sceneDescription": "A retro-futuristic night drive that supports the reading journey."
}

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1 @@
synthetic-public-asset

View File

@@ -0,0 +1,5 @@
{
"environment": "preview",
"canonicalUrl": "https://portfolio.example.test",
"buildId": "preview-1"
}

View File

@@ -0,0 +1,69 @@
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 }>;
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('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);
});
});

View File

@@ -0,0 +1,67 @@
import { readFileSync } from 'node:fs';
import { cp, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { afterEach, describe, expect, it } from 'vitest';
import { assetUrl } from '@/content/asset-url';
import { loadProductionInputs } from '@/content/load-production-inputs';
import { parsePublicAssetManifest } from '@/content/schemas';
const fixtureUrl = (name: string) => pathToFileURL(resolve(process.cwd(), 'tests/fixtures', name));
const fixtureUrls = {
valid: {
contentJson: fixtureUrl('portfolio-content.valid.json'),
assetManifestJson: fixtureUrl('asset-manifest.valid.json'),
preparedAssetRoot: fixtureUrl('prepared-assets.valid/'),
siteConfigJson: fixtureUrl('site-config.preview.json'),
},
extraPreparedFile: {
contentJson: fixtureUrl('portfolio-content.valid.json'),
assetManifestJson: fixtureUrl('asset-manifest.valid.json'),
preparedAssetRoot: fixtureUrl('prepared-assets.extra/'),
siteConfigJson: fixtureUrl('site-config.preview.json'),
},
};
const temporaryDirectories: string[] = [];
afterEach(async () => {
await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })));
});
describe('production inputs', () => {
it('rejects an extra prepared file', async () => {
await expect(loadProductionInputs(fixtureUrls.extraPreparedFile)).rejects.toThrow(/extra prepared asset/i);
});
it('returns deeply frozen inputs', async () => {
const inputs = await loadProductionInputs(fixtureUrls.valid);
expect(Object.isFrozen(inputs.content.projects.selected)).toBe(true);
expect(() => { (inputs.site as { buildId: string }).buildId = 'mutated'; }).toThrow();
});
it('rejects a symlinked prepared member', async () => {
const directory = await mkdtemp(join(tmpdir(), 'portfolio-inputs-'));
temporaryDirectories.push(directory);
await cp(fixtureUrls.valid.preparedAssetRoot, directory, { recursive: true });
await rm(join(directory, 'selected-build-one.webp'));
await symlink(join(directory, 'selected-build-two.webp'), join(directory, 'selected-build-one.webp'));
await expect(loadProductionInputs({ ...fixtureUrls.valid, preparedAssetRoot: new URL(`file://${directory}/`) }))
.rejects.toThrow(/symlink|special file/i);
});
it('rejects duplicate JSON keys before schema validation', async () => {
const directory = await mkdtemp(join(tmpdir(), 'portfolio-inputs-'));
temporaryDirectories.push(directory);
const contentPath = join(directory, 'content.json');
await writeFile(contentPath, '{"identity":{},"identity":{}}');
await expect(loadProductionInputs({ ...fixtureUrls.valid, contentJson: new URL(`file://${contentPath}`) }))
.rejects.toThrow(/duplicate JSON key/i);
});
it('resolves only an exact manifest asset ID to an encoded deterministic URL', () => {
const manifest = parsePublicAssetManifest(JSON.parse(readFileSync(fixtureUrl('asset-manifest.valid.json'), 'utf8')));
expect(assetUrl(manifest, 'selected-build-one')).toBe('/assets/selected-build-one.webp');
expect(() => assetUrl(manifest, 'missing')).toThrow(/unknown asset/i);
});
});