// tests/lib/test-lock.test.mjs // K8 — the test lock CLI (lib/util/test-lock.mjs). The executor (wave-1 order // 1) locks a step's test files once the test is written and seen failing, and // unlocks them when the test passes. The lock lives inside the repository's // git directory, so it can never be committed. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { makeRepo, gitIn, tmp } from '../hooks/guard-helpers.mjs'; import { childEnv } from '../helpers/child-env.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const CLI = join(HERE, '..', '..', 'lib', 'util', 'test-lock.mjs'); function run(cwd, args, env = {}) { const r = spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8', env: childEnv({ ...env }) }); return { code: r.status, stdout: r.stdout, stderr: r.stderr }; } function repoWithTests() { const repo = makeRepo('main'); mkdirSync(join(repo, 'tests'), { recursive: true }); writeFileSync(join(repo, 'tests', 'a.test.js'), 'a\n'); writeFileSync(join(repo, 'tests', 'b.test.js'), 'b\n'); return repo; } test('K8 lock: exit 0, status names the step and the files', () => { const repo = repoWithTests(); const r = run(repo, ['lock', '--files', 'tests/a.test.js,tests/b.test.js', '--step', '3']); assert.equal(r.code, 0, r.stderr); const s = run(repo, ['status']); assert.equal(s.code, 0); const st = JSON.parse(s.stdout); assert.equal(st.locked, true); assert.equal(st.step, 3); assert.equal(st.files.length, 2); assert.ok(st.files.every((f) => f.endsWith('.test.js'))); }); test('K8 lock: stored outside what can be committed (git status stays clean)', () => { const repo = repoWithTests(); gitIn(repo, 'add', '-A'); gitIn(repo, 'commit', '-q', '-m', 'tests'); assert.equal(run(repo, ['lock', '--files', 'tests/a.test.js', '--step', '1']).code, 0); assert.equal(gitIn(repo, 'status', '--porcelain'), '', 'the lock must not appear as a change in the repo'); const st = JSON.parse(run(repo, ['status']).stdout); assert.ok(st.lock_path.includes(`${'.git'}`), `lock path should be inside the git dir: ${st.lock_path}`); }); test('K8 lock records the Claude Code session it was taken in', () => { const repo = repoWithTests(); run(repo, ['lock', '--files', 'tests/a.test.js', '--step', '2'], { CLAUDE_CODE_SESSION_ID: 'sess-123' }); assert.equal(JSON.parse(run(repo, ['status']).stdout).session, 'sess-123'); }); test('K8 unlock: exit 0, status unlocked', () => { const repo = repoWithTests(); run(repo, ['lock', '--files', 'tests/a.test.js', '--step', '1']); assert.equal(run(repo, ['unlock']).code, 0); assert.equal(JSON.parse(run(repo, ['status']).stdout).locked, false); }); test('K8 lock refuses (exit 1) while another step holds the lock', () => { const repo = repoWithTests(); run(repo, ['lock', '--files', 'tests/a.test.js', '--step', '1']); const r = run(repo, ['lock', '--files', 'tests/b.test.js', '--step', '2']); assert.equal(r.code, 1); assert.match(r.stderr, /step 1/); }); test('K8: --cwd points at another directory', () => { const repo = repoWithTests(); assert.equal(run(tmp(), ['lock', '--cwd', repo, '--files', 'tests/a.test.js', '--step', '4']).code, 0); assert.equal(JSON.parse(run(tmp(), ['status', '--cwd', repo]).stdout).step, 4); }); test('K8 could not run (exit 2): not a git repository', () => { const r = run(tmp(), ['lock', '--files', 'x.test.js', '--step', '1']); assert.equal(r.code, 2); }); test('K8 could not run (exit 2): bad usage', () => { const repo = repoWithTests(); assert.equal(run(repo, ['lock', '--step', '1']).code, 2, 'no --files'); assert.equal(run(repo, ['lock', '--files', 'tests/a.test.js']).code, 2, 'no --step'); assert.equal(run(repo, ['lock', '--files', 'tests/a.test.js', '--step', 'x']).code, 2, 'step not a number'); assert.equal(run(repo, ['frobnicate']).code, 2); }); test('K8 status on a corrupt lock: exit 2, never "unlocked"', () => { const repo = repoWithTests(); run(repo, ['lock', '--files', 'tests/a.test.js', '--step', '1']); const path = JSON.parse(run(repo, ['status']).stdout).lock_path; writeFileSync(path, '{not json'); const s = run(repo, ['status']); assert.equal(s.code, 2); assert.ok(existsSync(path)); assert.ok(!/"locked":\s*false/.test(s.stdout)); assert.ok(readFileSync(path, 'utf8').startsWith('{not'), 'status never rewrites the lock'); });