// tests/lib/repo-tools.test.mjs // lib/verification/repo-tools.mjs finds the target repo's OWN formatter, // linter and type checker. A tool counts only when the repo configures it AND // it is installed locally (node_modules/.bin) or on PATH; nothing is ever // downloaded. Exit codes are mapped per tool to clean / finding / error, and an // error is never read as clean. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { writeFileSync, mkdirSync, realpathSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { makeRepo, fakeTool, tmp } from '../hooks/guard-helpers.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const mod = await import(join(HERE, '..', '..', 'lib', 'verification', 'repo-tools.mjs')); const { resolveRepo, detectTools, toolsForFile, runTool } = mod; test('resolveRepo: finds the root and git dir from a file path that does not exist yet', () => { const repo = makeRepo('main'); const r = resolveRepo(join(repo, 'src', 'new', 'file.js')); assert.equal(r.root, realpathSync(repo)); assert.equal(r.gitDir, realpathSync(join(repo, '.git'))); }); test('resolveRepo: null outside a repository', () => { assert.equal(resolveRepo(tmp()), null); }); test('detectTools: prettier counts only with config AND a local binary', () => { const repo = makeRepo('main'); writeFileSync(join(repo, '.prettierrc'), '{}'); let tools = detectTools(repo, { env: { PATH: '/nonexistent' } }); const p = tools.find((t) => t.name === 'prettier'); assert.ok(p && !p.available, 'configured but not installed is listed as unavailable'); fakeTool(join(repo, 'node_modules', '.bin'), 'prettier'); tools = detectTools(repo, { env: { PATH: '/nonexistent' } }); assert.ok(tools.find((t) => t.name === 'prettier').available); }); test('detectTools: a repo with nothing configured has no tools', () => { assert.deepEqual(detectTools(makeRepo('main'), { env: { PATH: '/nonexistent' } }), []); }); test('detectTools: eslint and tsc (repo tier) from config + local binaries', () => { const repo = makeRepo('main'); writeFileSync(join(repo, 'eslint.config.js'), 'export default [];'); writeFileSync(join(repo, 'tsconfig.json'), '{}'); fakeTool(join(repo, 'node_modules', '.bin'), 'eslint'); fakeTool(join(repo, 'node_modules', '.bin'), 'tsc'); const tools = detectTools(repo, { env: { PATH: '/nonexistent' } }); const eslint = tools.find((t) => t.name === 'eslint'); const tsc = tools.find((t) => t.name === 'tsc'); assert.equal(eslint.kind, 'lint'); assert.equal(eslint.tier, 'file'); assert.equal(tsc.kind, 'typecheck'); assert.equal(tsc.tier, 'repo'); }); test('detectTools: ruff from pyproject [tool.ruff] and a binary on PATH', () => { const repo = makeRepo('main'); writeFileSync(join(repo, 'pyproject.toml'), '[tool.ruff]\nline-length = 100\n'); const bin = join(tmp(), 'bin'); fakeTool(bin, 'ruff'); const tools = detectTools(repo, { env: { PATH: bin } }); assert.ok(tools.some((t) => t.name === 'ruff-check' && t.available)); assert.ok(tools.some((t) => t.name === 'ruff-format' && t.available)); }); test('toolsForFile: by extension, file tier only', () => { const repo = makeRepo('main'); writeFileSync(join(repo, '.prettierrc'), '{}'); writeFileSync(join(repo, 'tsconfig.json'), '{}'); fakeTool(join(repo, 'node_modules', '.bin'), 'prettier'); fakeTool(join(repo, 'node_modules', '.bin'), 'tsc'); const tools = detectTools(repo, { env: { PATH: '/nonexistent' } }); assert.deepEqual(toolsForFile(tools, 'src/a.ts').map((t) => t.name), ['prettier']); assert.deepEqual(toolsForFile(tools, 'image.png').map((t) => t.name), []); }); test('runTool: clean, finding and error are three different answers', () => { const repo = makeRepo('main'); writeFileSync(join(repo, '.prettierrc'), '{}'); fakeTool(join(repo, 'node_modules', '.bin'), 'prettier'); mkdirSync(join(repo, 'src')); writeFileSync(join(repo, 'src', 'good.js'), 'ok'); writeFileSync(join(repo, 'src', 'bad.js'), 'BAD'); const tool = detectTools(repo, { env: { PATH: '/nonexistent' } }).find((t) => t.name === 'prettier'); assert.equal(runTool(tool, ['src/good.js'], { cwd: repo }).status, 'clean'); const f = runTool(tool, ['src/bad.js'], { cwd: repo }); assert.equal(f.status, 'finding'); assert.match(f.output, /problem/); // exit 7 is neither clean nor a finding for prettier fakeTool(join(repo, 'node_modules', '.bin'), 'prettier', { findingExit: 7 }); assert.equal(runTool(tool, ['src/bad.js'], { cwd: repo }).status, 'error'); });