import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; import { createPageMetadata, createStaticHostArtifacts } from '@/content/metadata'; import { parsePortfolioContent, parsePublicAssetManifest } from '@/content/schemas'; import { serializeJsonLd } from '@/security/serialize-json-ld'; import type { SiteConfig } from '@/content/types'; const fixture = (path: string): unknown => JSON.parse(readFileSync(resolve(process.cwd(), path), 'utf8')) as unknown; const content = parsePortfolioContent(fixture('content/public/portfolio-content.json')); const manifest = parsePublicAssetManifest(fixture('content/public/asset-manifest.json')); const previewSite: SiteConfig = { environment: 'preview', canonicalUrl: 'https://portfolio.localhost/', buildId: 'preview-test', }; const productionSite: SiteConfig = { environment: 'production', canonicalUrl: 'https://portfolio.example.test/', buildId: 'production-test', }; describe('safe metadata', () => { it('escapes every HTML-significant and JavaScript line-separator code point', () => { const serialized = serializeJsonLd({ value: '&\u2028\u2029' }); expect(serialized).toBe('{"value":"\\u003c/script\\u003e\\u003ctag\\u003e\\u0026\\u2028\\u2029"}'); expect(serialized.toLowerCase()).not.toContain(' { const metadata = createPageMetadata(content, manifest, productionSite); expect(metadata.title).toBe('Luca Pîslaru — System Administrator & Full-Stack Developer'); expect(metadata.description).toBe( 'Portfolio of Luca Pîslaru, a Bucharest-based System Administrator and Full-Stack Developer working across Linux infrastructure and end-to-end software delivery.', ); expect(metadata.canonicalUrl).toBe('https://portfolio.example.test/'); const previewAsset = manifest.assets.find(({ id }) => id === content.metadata.socialPreviewAssetId)!; expect(metadata.openGraph.image).toBe(`https://portfolio.example.test/assets/${previewAsset.fileName}`); expect(metadata.twitter).toEqual({ card: 'summary_large_image', title: metadata.title, description: metadata.description, image: `https://portfolio.example.test/assets/${previewAsset.fileName}`, }); expect(metadata.jsonLd).toEqual({ '@context': 'https://schema.org', '@type': 'ProfilePage', name: 'Luca Pîslaru', description: metadata.description, url: 'https://portfolio.example.test/', mainEntity: { '@type': 'Person', name: 'Luca Pîslaru', jobTitle: 'System Administrator & Full-Stack Developer', description: metadata.description, url: 'https://portfolio.example.test/', email: 'mailto:luca.pislaru@gmail.com', homeLocation: { '@type': 'Place', address: { '@type': 'PostalAddress', addressLocality: 'Bucharest', addressCountry: 'RO', }, }, sameAs: [ 'https://github.com/LucaPisl', 'https://gitlab.com/luca_pisl', 'https://linkedin.com/in/luca-pisl', ], }, }); expect(JSON.stringify(metadata.jsonLd)).not.toMatch( /streetAddress|postalCode|telephone|interactionStatistic|"image"/iu, ); const expectedHash = createHash('sha256').update(metadata.serializedJsonLd).digest('base64'); expect(metadata.jsonLdCspHash).toBe(`sha256-${expectedHash}`); }); it('uses a summary card without an image when no social preview is approved', () => { const withoutImage = structuredClone(content); delete withoutImage.metadata.socialPreviewAssetId; const metadata = createPageMetadata(withoutImage, manifest, productionSite); expect(metadata.openGraph).not.toHaveProperty('image'); expect(metadata.twitter).toEqual({ card: 'summary', title: 'Luca Pîslaru — System Administrator & Full-Stack Developer', description: content.metadata.description, }); }); it.each([ { name: 'audio', mediaType: 'audio/mpeg' as const, fileName: 'project-social-preview.mp3', width: null, height: null, durationMs: 1000 }, { name: 'video', mediaType: 'video/mp4' as const, fileName: 'project-social-preview.mp4', width: 1200, height: 630, durationMs: 1000 }, { name: 'vector image', mediaType: 'image/svg+xml' as const, fileName: 'project-social-preview.svg', width: 1200, height: 630, durationMs: null }, { name: 'wrong-size raster', mediaType: 'image/webp' as const, fileName: 'project-social-preview.webp', width: 1199, height: 630, durationMs: null }, ])('falls back to a summary card when the configured preview is $name', ({ mediaType, fileName, width, height, durationMs }) => { const unsafeManifest = structuredClone(manifest); const previewAsset = unsafeManifest.assets.find(({ id }) => id === content.metadata.socialPreviewAssetId)!; previewAsset.mediaType = mediaType; previewAsset.fileName = fileName; previewAsset.width = width; previewAsset.height = height; previewAsset.durationMs = durationMs; const metadata = createPageMetadata(content, unsafeManifest, productionSite); expect(metadata.openGraph).not.toHaveProperty('image'); expect(metadata.twitter).toEqual({ card: 'summary', title: 'Luca Pîslaru — System Administrator & Full-Stack Developer', description: content.metadata.description, }); }); it('omits sameAs when no verified social profiles exist', () => { const withoutSocial = structuredClone(content); withoutSocial.contact.social = []; const metadata = createPageMetadata(withoutSocial, manifest, productionSite); expect(metadata.jsonLd.mainEntity).not.toHaveProperty('sameAs'); }); it('rejects metadata title copy that diverges from the canonical identity and role', () => { const inconsistent = structuredClone(content); inconsistent.metadata.title = 'Luca Pîslaru — Developer'; expect(() => createPageMetadata(inconsistent, manifest, productionSite)).toThrow(/canonical identity.*role/i); }); }); describe('environment-aware crawl and response artifacts', () => { it('keeps preview private, noindex, sitemap-free, and without HSTS', () => { const artifacts = createStaticHostArtifacts(createPageMetadata(content, manifest, previewSite), previewSite); expect(artifacts.robots).toBe('User-agent: *\nDisallow: /\n'); expect(artifacts.sitemap).toBeUndefined(); expect(artifacts.headers).toContain('Cache-Control: private, no-store'); expect(artifacts.headers).toContain('X-Robots-Tag: noindex, nofollow, noarchive'); expect(artifacts.headers).not.toContain('Strict-Transport-Security'); expect(artifacts.headers).not.toContain('Sitemap:'); expect(artifacts.redirects).not.toContain('portfolio.example.test'); }); it('generates production-only sitemap, allow policy, HSTS, and no preview directives', () => { const metadata = createPageMetadata(content, manifest, productionSite); const artifacts = createStaticHostArtifacts(metadata, productionSite); expect(artifacts.robots).toBe( 'User-agent: *\nAllow: /\nSitemap: https://portfolio.example.test/sitemap.xml\n', ); expect(artifacts.sitemap).toContain('https://portfolio.example.test/'); expect(artifacts.headers).toContain('Strict-Transport-Security: max-age=31536000'); expect(artifacts.headers).not.toContain('X-Robots-Tag'); expect(artifacts.headers).not.toContain('private, no-store'); expect(artifacts.headers).toBe([ '/assets/*', ' Cache-Control: public, max-age=31536000, immutable', '', '/*', ` Content-Security-Policy: default-src 'self'; script-src 'self' '${metadata.jsonLdCspHash}'; style-src 'self'; img-src 'self' data:; media-src 'self'; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests`, ' X-Content-Type-Options: nosniff', ' Referrer-Policy: no-referrer', ' Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=()', ' X-Frame-Options: DENY', ' Cache-Control: public, max-age=0, must-revalidate', ' Strict-Transport-Security: max-age=31536000', '', ].join('\n')); }); it('emits the least-privilege browser security baseline', () => { const artifacts = createStaticHostArtifacts(createPageMetadata(content, manifest, previewSite), previewSite); expect(artifacts.headers).toContain("default-src 'self'"); expect(artifacts.headers).toContain("script-src 'self' 'sha256-"); expect(artifacts.headers).toContain("connect-src 'self'"); expect(artifacts.headers).toContain("frame-ancestors 'none'"); expect(artifacts.headers).toContain('X-Frame-Options: DENY'); expect(artifacts.headers).toContain('X-Content-Type-Options: nosniff'); expect(artifacts.headers).toContain('Referrer-Policy: no-referrer'); expect(artifacts.headers).toContain( 'Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=()', ); }); });