/** * plan-io.test — plan location/loading + the run-id/artifact-path helpers. */ import { describe, it, expect, afterEach } from 'vitest'; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { dump } from 'js-yaml'; import { defaultPlanLocation, loadPlan, locatePlan } from '../plan-io.js'; import { defaultRunId, pathSlug, runDirRelPath } from '../run-id.js'; const tmpDirs: string[] = []; const makeTmp = (): string => { const dir = mkdtempSync(join(tmpdir(), 'uat-planio-')); tmpDirs.push(dir); return dir; }; afterEach(() => { while (tmpDirs.length) rmSync(tmpDirs.pop()!, { recursive: true, force: true }); }); const MINIMAL_PLAN = { schema_version: '1.0.0', meta: { application: 'administration', path: 'administration' }, roles: ['admin', 'anonymous'], execution: { screenshots: { out_dir: 'shots' } }, routes: [ { id: 'ADMINISTRATION_USERS', route: '/administration/users', access: { admin: 'allowed', anonymous: 'redirect_login' }, }, ], endpoints: [], }; describe('defaultPlanLocation', () => { it('derives application + basename from the scope path', () => { const loc = defaultPlanLocation('/root', 'administration/users'); expect(loc.application).toBe('administration'); expect(loc.name).toBe('users'); expect(loc.relPath).toBe('.application-test/uat/administration/users.plantest.yml'); }); it('honors an explicit name', () => { expect(defaultPlanLocation('/root', 'administration', 'all').relPath).toBe( '.application-test/uat/administration/all.plantest.yml', ); }); }); describe('locatePlan / loadPlan', () => { it('locates by path, loads + validates, and reports invariant violations as strings', () => { const root = makeTmp(); const loc = defaultPlanLocation(root, 'administration'); mkdirSync(dirname(loc.absPath), { recursive: true }); writeFileSync(loc.absPath, dump(MINIMAL_PLAN)); const located = locatePlan({ projectRoot: root, path: 'administration' }); expect(located.ok).toBe(true); if (located.ok) { const loaded = loadPlan(located.location.absPath); expect(loaded.ok).toBe(true); if (loaded.ok) { expect(loaded.plan.routes).toHaveLength(1); expect(loaded.violations).toEqual([]); } } }); it('fails with a /uat plan hint when the plan is missing', () => { const located = locatePlan({ projectRoot: makeTmp(), path: 'administration' }); expect(located.ok).toBe(false); if (!located.ok) expect(located.error).toContain('/uat plan'); }); it('explicit planFile wins and must exist', () => { const root = makeTmp(); const abs = join(root, 'custom.plantest.yml'); writeFileSync(abs, dump(MINIMAL_PLAN)); const located = locatePlan({ projectRoot: root, planFile: 'custom.plantest.yml' }); expect(located.ok).toBe(true); if (located.ok) expect(located.location.name).toBe('custom'); expect(locatePlan({ projectRoot: root, planFile: 'nope.plantest.yml' }).ok).toBe(false); }); it('schema-invalid plans fail loudly', () => { const root = makeTmp(); const abs = join(root, 'bad.plantest.yml'); writeFileSync(abs, dump({ schema_version: '1.0.0' })); const loaded = loadPlan(abs); expect(loaded.ok).toBe(false); }); }); describe('run-id helpers', () => { it('defaultRunId formats UTC yyyyMMdd-HHmmss deterministically', () => { expect(defaultRunId(new Date(Date.UTC(2026, 5, 12, 14, 30, 5)))).toBe('20260612-143005'); }); it('runDirRelPath + pathSlug', () => { expect(runDirRelPath('administration', '20260612-143005')).toBe( '.application-test/uat/administration/runs/20260612-143005', ); expect(pathSlug('Super Admin')).toBe('super-admin'); expect(pathSlug('***')).toBe('x'); }); });