#!/usr/bin/env node // [unit->REQ-AST-01] // tools/scripts/lint-asset-pipeline.test.mjs // 5 fixture-driven tests per plan 06-08 Task 1 : // 1. valid manifest (sha matches) → exit 0, "lint-asset-pipeline: OK" // 2. schema_version=2 → exit 1, "schema_version must be 1" // 3. missing atlases.atlas-mvp → exit 1 // 4. on-disk PNG sha256 ≠ manifest.sha256_png → exit 1, "atlas integrity: sha256 mismatch" // 5. manifest file absent → exit 1, "pipeline-manifest.json not found at " import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, writeFileSync, rmSync, mkdirSync, } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; const SCRIPT = resolve(process.cwd(), 'tools/scripts/lint-asset-pipeline.mjs'); const isWindows = process.platform === 'win32'; function makeFixtureDir(prefix = 'lint-asset-pipeline-') { return mkdtempSync(join(tmpdir(), prefix)); } function writeManifest(dir, manifest) { const path = join(dir, 'pipeline-manifest.json'); writeFileSync(path, JSON.stringify(manifest)); return path; } function writeAtlas(dir, bytes) { const staticDir = join(dir, 'public'); mkdirSync(staticDir, { recursive: true }); writeFileSync(join(staticDir, 'atlas-mvp.png'), bytes); return staticDir; } function runLint(env) { return spawnSync('node', [SCRIPT], { env: { ...process.env, ...env }, encoding: 'utf-8', shell: isWindows, }); } test('1. valid manifest with matching atlas sha256 → exit 0, OK', () => { const dir = makeFixtureDir(); try { const png = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // PNG magic const sha = createHash('sha256').update(png).digest('hex'); const manifest = { schema_version: 1, sprites: { '0000-NaviStandD': { sprite_id: '0000-NaviStandD', frame_w: 36, frame_h: 48, frame_count: 1, origin: { x: 0, y: 0 }, gaps: [], atlas_ref: '0000-NaviStandD_000', sha256: 'a'.repeat(64), }, }, atlases: { 'atlas-mvp': { json: '/atlas-mvp.json', png: '/atlas-mvp.png', sha256_png: sha, }, }, }; const manifestPath = writeManifest(dir, manifest); const staticDir = writeAtlas(dir, png); const r = runLint({ PIPELINE_MANIFEST_PATH: manifestPath, STATIC_BUNDLE_DIR: staticDir, }); assert.equal(r.status, 0, `expected exit 0, got ${r.status}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`); assert.match(r.stdout, /lint-asset-pipeline: OK/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('2. schema_version=2 → exit 1, schema_version must be 1', () => { const dir = makeFixtureDir(); try { const manifest = { schema_version: 2, sprites: {}, atlases: { 'atlas-mvp': { json: '/atlas-mvp.json', png: '/atlas-mvp.png', sha256_png: 'a'.repeat(64) } }, }; const manifestPath = writeManifest(dir, manifest); const r = runLint({ PIPELINE_MANIFEST_PATH: manifestPath, STATIC_BUNDLE_DIR: join(dir, 'no-such-dir') }); assert.equal(r.status, 1, `expected exit 1, got ${r.status}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`); assert.match(r.stderr, /schema_version must be 1/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('3. missing atlases.atlas-mvp → exit 1', () => { const dir = makeFixtureDir(); try { const manifest = { schema_version: 1, sprites: {}, atlases: {}, }; const manifestPath = writeManifest(dir, manifest); const r = runLint({ PIPELINE_MANIFEST_PATH: manifestPath, STATIC_BUNDLE_DIR: join(dir, 'no-such-dir') }); assert.equal(r.status, 1); assert.match(r.stderr, /atlases\.atlas-mvp/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('4. on-disk PNG sha mismatch → exit 1, atlas integrity: sha256 mismatch', () => { const dir = makeFixtureDir(); try { const realBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const wrongSha = 'b'.repeat(64); const manifest = { schema_version: 1, sprites: {}, atlases: { 'atlas-mvp': { json: '/atlas-mvp.json', png: '/atlas-mvp.png', sha256_png: wrongSha }, }, }; const manifestPath = writeManifest(dir, manifest); const staticDir = writeAtlas(dir, realBytes); const r = runLint({ PIPELINE_MANIFEST_PATH: manifestPath, STATIC_BUNDLE_DIR: staticDir }); assert.equal(r.status, 1); assert.match(r.stderr, /atlas integrity: sha256 mismatch/); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('5. manifest absent → exit 1, pipeline-manifest.json not found', () => { const dir = makeFixtureDir(); try { const missingPath = join(dir, 'does-not-exist.json'); const r = runLint({ PIPELINE_MANIFEST_PATH: missingPath }); assert.equal(r.status, 1); assert.match(r.stderr, /pipeline-manifest\.json not found at/); } finally { rmSync(dir, { recursive: true, force: true }); } });