fix: harden empty asset materialization
This commit is contained in:
58
tests/integration/materialize-empty-asset-set.test.mjs
Normal file
58
tests/integration/materialize-empty-asset-set.test.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
import { lstat, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
assertPreparedAssetDirectoryIdentity,
|
||||
materializeEmptyAssetSet,
|
||||
} from '../../scripts/materialize-empty-asset-set.mjs';
|
||||
|
||||
const temporaryRoots = [];
|
||||
|
||||
const createScratchRepository = async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'portfolio-empty-assets-'));
|
||||
temporaryRoots.push(root);
|
||||
await mkdir(join(root, 'content/public'), { recursive: true });
|
||||
await writeFile(join(root, 'content/public/asset-manifest.json'), '{"assets":[]}');
|
||||
return root;
|
||||
};
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('empty asset materialization', () => {
|
||||
it('creates an empty prepared-asset directory in the supplied repository root', async () => {
|
||||
const root = await createScratchRepository();
|
||||
await materializeEmptyAssetSet(root);
|
||||
expect((await lstat(join(root, 'inputs/prepared-assets'))).isDirectory()).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects a symbolic-link prepared-asset directory', async () => {
|
||||
const root = await createScratchRepository();
|
||||
const target = join(root, 'outside');
|
||||
await mkdir(target);
|
||||
await mkdir(join(root, 'inputs'), { recursive: true });
|
||||
await symlink(target, join(root, 'inputs/prepared-assets'));
|
||||
await expect(materializeEmptyAssetSet(root)).rejects.toThrow(/non-symlink directory/i);
|
||||
});
|
||||
|
||||
it('rejects unexpected prepared-asset members in its supplied repository root', async () => {
|
||||
const root = await createScratchRepository();
|
||||
const preparedAssetsPath = join(root, 'inputs/prepared-assets');
|
||||
await mkdir(preparedAssetsPath, { recursive: true });
|
||||
await writeFile(join(preparedAssetsPath, 'unexpected.webp'), 'not an asset');
|
||||
await expect(materializeEmptyAssetSet(root)).rejects.toThrow(/must be empty/i);
|
||||
});
|
||||
|
||||
it('rejects a prepared-asset directory replaced after its identity was captured', async () => {
|
||||
const root = await createScratchRepository();
|
||||
const preparedAssetsPath = join(root, 'inputs/prepared-assets');
|
||||
await mkdir(preparedAssetsPath, { recursive: true });
|
||||
const original = await lstat(preparedAssetsPath);
|
||||
await rm(preparedAssetsPath, { recursive: true });
|
||||
await mkdir(preparedAssetsPath);
|
||||
await expect(assertPreparedAssetDirectoryIdentity(preparedAssetsPath, original))
|
||||
.rejects.toThrow(/identity changed/i);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,10 @@
|
||||
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 { readFile } from 'node:fs/promises';
|
||||
import { 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 () => {
|
||||
@@ -51,14 +43,4 @@ describe('approved public portfolio projection', () => {
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user