68 lines
3.2 KiB
TypeScript
68 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|