87 lines
3.4 KiB
TypeScript
87 lines
3.4 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
import { tmpdir } from "node:os"
|
|
import path from "node:path"
|
|
import { test } from "node:test"
|
|
import { diffSnapshots, hashDir, listSkillDirs, recordNewSkills } from "../scripts/sync-upstream.ts"
|
|
|
|
function fixture() {
|
|
const dir = mkdtempSync(path.join(tmpdir(), "sync-test-"))
|
|
const skill = (root: string, bucket: string, name: string, body: string, extra?: string) => {
|
|
mkdirSync(path.join(root, bucket, name), { recursive: true })
|
|
writeFileSync(path.join(root, bucket, name, "SKILL.md"), body)
|
|
if (extra) writeFileSync(path.join(root, bucket, name, "EXTRA.md"), extra)
|
|
}
|
|
return { dir, skill }
|
|
}
|
|
|
|
test("listSkillDirs finds only dirs containing SKILL.md", () => {
|
|
const { dir, skill } = fixture()
|
|
try {
|
|
skill(dir, "engineering", "tdd", "---\nname: tdd\n---\n")
|
|
skill(dir, "misc", "wizard", "---\nname: wizard\n---\n")
|
|
mkdirSync(path.join(dir, "deprecated"), { recursive: true })
|
|
writeFileSync(path.join(dir, "deprecated", "README.md"), "empty bucket")
|
|
mkdirSync(path.join(dir, "engineering", "not-a-skill"), { recursive: true })
|
|
assert.deepEqual(listSkillDirs(dir), ["engineering/tdd", "misc/wizard"])
|
|
assert.deepEqual(listSkillDirs(path.join(dir, "missing")), [])
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test("diffSnapshots reports added, removed and changed skill dirs", () => {
|
|
const { dir, skill } = fixture()
|
|
const before = path.join(dir, "before")
|
|
const after = path.join(dir, "after")
|
|
try {
|
|
skill(before, "engineering", "tdd", "v1")
|
|
skill(before, "engineering", "gone", "v1")
|
|
skill(after, "engineering", "tdd", "v2")
|
|
skill(after, "productivity", "fresh", "v1")
|
|
assert.deepEqual(diffSnapshots(before, after), {
|
|
added: ["productivity/fresh"],
|
|
removed: ["engineering/gone"],
|
|
changed: ["engineering/tdd"],
|
|
})
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test("hashDir ignores file mtime and is order-independent", () => {
|
|
const { dir, skill } = fixture()
|
|
const a = path.join(dir, "a")
|
|
const b = path.join(dir, "b")
|
|
try {
|
|
skill(a, "x", "s", "same", "extra")
|
|
skill(b, "x", "s", "same", "extra")
|
|
assert.equal(hashDir(path.join(a, "x", "s")), hashDir(path.join(b, "x", "s")))
|
|
writeFileSync(path.join(b, "x", "s", "EXTRA.md"), "different")
|
|
assert.notEqual(hashDir(path.join(a, "x", "s")), hashDir(path.join(b, "x", "s")))
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test("recordNewSkills appends needs-review entries once and leaves known skills alone", () => {
|
|
const { dir } = fixture()
|
|
const file = path.join(dir, "classification.json")
|
|
try {
|
|
writeFileSync(
|
|
file,
|
|
JSON.stringify({ skills: [{ name: "tdd", bucket: "engineering", status: "analyzed" }] }),
|
|
)
|
|
const fresh = recordNewSkills(file, ["engineering/tdd", "misc/newcomer"])
|
|
assert.deepEqual(fresh, ["newcomer"])
|
|
const doc = JSON.parse(readFileSync(file, "utf8"))
|
|
assert.equal(doc.skills.length, 2)
|
|
assert.equal(doc.skills[1].status, "needs-review")
|
|
assert.equal(doc.skills[1].classification, "skill")
|
|
// idempotent: second run adds nothing
|
|
assert.deepEqual(recordNewSkills(file, ["misc/newcomer"]), [])
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|