77 lines
3.4 KiB
JavaScript
77 lines
3.4 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import os from 'node:os'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { spawnSync, execFileSync } from 'node:child_process'
|
|
|
|
const script = fileURLToPath(new URL('./check-doc-links.mjs', import.meta.url))
|
|
function fixture(t, contents) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hm-doc-links-'))
|
|
t.after(() => {
|
|
const relative = path.relative(path.resolve(os.tmpdir()), path.resolve(root))
|
|
assert.ok(relative.startsWith('hm-doc-links-') && !relative.includes(path.sep), 'cleanup must stay in its own temporary fixture')
|
|
fs.rmSync(root, { recursive: true, force: true })
|
|
})
|
|
for (const [name, source] of Object.entries(contents)) {
|
|
const target = path.join(root, name)
|
|
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
fs.writeFileSync(target, source)
|
|
}
|
|
return root
|
|
}
|
|
function run(root) {
|
|
const result = spawnSync(process.execPath, [script], { cwd: root, encoding: 'utf8' })
|
|
return { status: result.status, report: JSON.parse(result.stdout || result.stderr) }
|
|
}
|
|
|
|
test('Git and filesystem modes preserve current ignore scope and link counts', t => {
|
|
const root = fixture(t, {
|
|
'.gitignore': 'node_modules/\n_local/\n**/dist-dashboard/\n*.log\n.env.*\n!.env.example\n',
|
|
'README.md': '[한글](docs/a%20b.md#한글-제목) [asset](.env.example)\n```md\n[x](missing.md)\n```\n',
|
|
'docs/a b.md': '# 한글 제목\n# 한글 제목\n[duplicate](#한글-제목-1) [id](#custom)\n<a id="custom"></a>\n',
|
|
'.env.example': '',
|
|
'node_modules/package/bad.md': '[x](missing.md)',
|
|
'_local/bad.md': '[x](missing.md)',
|
|
'app/dist-dashboard/bad.md': '[x](missing.md)',
|
|
})
|
|
const portable = run(root)
|
|
assert.equal(portable.status, 0)
|
|
assert.equal(portable.report.collectionMode, 'filesystem')
|
|
assert.equal(portable.report.files, 2)
|
|
assert.equal(portable.report.internalLinks, 4)
|
|
execFileSync('git', ['init', '--quiet', root])
|
|
execFileSync('git', ['add', '.'], { cwd: root })
|
|
const repository = run(root)
|
|
assert.equal(repository.status, 0)
|
|
assert.equal(repository.report.collectionMode, 'git')
|
|
assert.deepEqual({ ...repository.report, collectionMode: 'filesystem' }, portable.report)
|
|
})
|
|
|
|
test('missing paths, headings and excluded local evidence return exit 1', t => {
|
|
const root = fixture(t, {
|
|
'README.md': '[path](missing.md) [heading](doc.md#missing) [local](_local/proof.json)',
|
|
'doc.md': '# Present', '_local/proof.json': '{}',
|
|
})
|
|
const result = run(root)
|
|
assert.equal(result.status, 1)
|
|
assert.deepEqual(result.report.problems.map(problem => problem.reason), ['missing path', 'missing heading', 'local evidence is excluded from Git Clone'])
|
|
})
|
|
|
|
test('nested ignore rules and negation retain the intended Markdown files', t => {
|
|
const root = fixture(t, {
|
|
'.gitignore': '*.md\n!README.md\n', 'README.md': '# Readme', 'ignored.md': '[x](missing)',
|
|
'docs/.gitignore': '!keep.md\n', 'docs/keep.md': '# Kept', 'docs/drop.md': '[x](missing)',
|
|
})
|
|
assert.equal(run(root).report.files, 2)
|
|
})
|
|
|
|
test('broken Git metadata is an operational error, never a silent fallback', t => {
|
|
const root = fixture(t, { '.git': 'gitdir: missing-metadata', 'README.md': '# Readme' })
|
|
const result = run(root)
|
|
assert.equal(result.status, 2)
|
|
assert.equal(result.report.collectionMode, 'git')
|
|
assert.equal(result.report.error, 'Markdown collection failed')
|
|
})
|