content: add approved public portfolio projection

This commit is contained in:
2026-09-02 01:00:36 +03:00
parent 6428b5dcd6
commit 49a4f75c8d
11 changed files with 460 additions and 11 deletions

View File

@@ -47,7 +47,7 @@
"selected": [
{
"id": "private-build-one",
"title": "Private project",
"title": "Private self-hosted medication tracker",
"visibility": "private",
"maturity": "active",
"summary": "A private multi-device application.",

View File

@@ -0,0 +1,64 @@
import { execFile as execFileCallback } from 'node:child_process';
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { promisify } from 'node:util';
import { afterEach, describe, expect, it } from 'vitest';
import { parsePortfolioContent, parsePublicAssetManifest, parseSiteConfig } from '@/content/schemas';
const execFile = promisify(execFileCallback);
const repositoryRoot = process.cwd();
const readJson = async (relativePath: string): Promise<unknown> =>
JSON.parse(await readFile(`${repositoryRoot}/${relativePath}`, 'utf8')) as unknown;
const preparedAssetsPath = `${repositoryRoot}/inputs/prepared-assets`;
afterEach(async () => {
await rm(preparedAssetsPath, { recursive: true, force: true });
});
describe('approved public portfolio projection', () => {
it('contains approved identity and balanced evidence without prohibited surfaces', async () => {
const content = parsePortfolioContent(await readJson('content/public/portfolio-content.json'));
const manifest = parsePublicAssetManifest(await readJson('content/public/asset-manifest.json'));
const site = parseSiteConfig(await readJson('config/site.preview.json'));
expect(content.identity).toEqual(expect.objectContaining({
name: 'Luca Pîslaru',
role: 'System Administrator & Full-Stack Developer',
}));
expect(content.systemsOperations.title).toBe('Professional systems operations');
expect(content.infrastructure.title).toBe('Secure self-hosted platform and lifecycle');
expect(content.projects.selected).toHaveLength(3);
expect(content.projects.selected.filter((project) => project.visibility === 'private')
.every((project) => project.url === undefined)).toBe(true);
expect(content.projects.selected.find((project) => project.visibility === 'private')?.title)
.toBe('Private self-hosted medication tracker');
expect(content.projects.additional.find((project) => project.id === 'private-game-prototype')?.title)
.toBe('Private game prototype');
expect([...content.projects.selected, ...content.projects.additional]
.every((project) => project.assetIds.length === 0)).toBe(true);
expect(content.toolkit.flatMap((group) => group.technologies).map((technology) => technology.name))
.toEqual(expect.arrayContaining(['Linux', 'Docker', 'Python', 'Vue.js', 'Java']));
expect(content.contact).toEqual(expect.objectContaining({ email: 'luca.pislaru@gmail.com' }));
expect(content.contact.social.map((link) => link.url)).toEqual([
'https://github.com/LucaPisl',
'https://gitlab.com/luca_pisl',
'https://linkedin.com/in/luca-pisl',
]);
expect(manifest.assets).toEqual([]);
expect(site).toEqual({
environment: 'preview',
canonicalUrl: 'https://portfolio.localhost/',
buildId: 'preview-semantic-v1',
});
expect(JSON.stringify(content)).not.toMatch(/Arch Linux|CV download|heatmap|expert|percent/i);
});
it('materializes an empty prepared-asset directory and rejects unexpected members', async () => {
await execFile(process.execPath, ['scripts/materialize-empty-asset-set.mjs'], { cwd: repositoryRoot });
expect(await readdir(preparedAssetsPath)).toEqual([]);
await mkdir(preparedAssetsPath, { recursive: true });
await writeFile(`${preparedAssetsPath}/unexpected.webp`, 'not an asset');
await expect(execFile(process.execPath, ['scripts/materialize-empty-asset-set.mjs'], { cwd: repositoryRoot }))
.rejects.toThrow(/must be empty/i);
});
});

View File

@@ -20,7 +20,10 @@ type MutableContentFixture = {
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;
@@ -32,6 +35,45 @@ describe('content schemas', () => {
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';