import { describe, expect, onTestFinished, test } from "bun:test"
import { createHash } from "node:crypto"
import { copyFileSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { delimiter, join } from "node:path"
import { PROMOTION } from "./fact-commands-meaning.mjs"
import { writeFidelityRecord } from "./fidelity-records.mjs"
import { PAIR_KINDS } from "./fact-pairs.mjs"
import { VERDICT_SCHEMA } from "./meaning-check.mjs"
import { POLISH_MARKUP_REASON } from "./polish-apply.mjs"
import { COMMANDS, run } from "./report-tools.mjs"
import { SCRIPT, FIXTURES, sessionDir, nodeWithoutBun, defectsFile, FIDELITY, sha256, CliRow, CliJson, nodeJson, fidelitySession, verify, codesOf, Pair, pairedSession, verdicts, calibrationRows, runLines, PolishJson, polishSession, polishApply, setField, fillTemplate, textSource, ledgerRow } from "./tests/report-tools-support"
describe("polish wiring under node", () => {
test("#given the fixture edits #when fact polish-apply runs #then it exits 0, applies the wording edit, and rejects the numeral edit", () => {
const { dir } = polishSession()
const applied = polishApply(dir, join(FIDELITY, "polish-edits.json"))
const polished = readFileSync(join(dir, "report.polished.html"), "utf8")
expect(applied.code).toBe(0)
expect(applied.json.data).toMatchObject({ applied: 1, rejected: 1, unlocated: 0 })
expect(applied.json.defects.map((d) => [d.code, d.index])).toEqual([["polish_edit_rejected", 1]])
expect(polished).toContain("Collected from a single cited public page")
expect(polished).toContain("retrieved 2026-10-01")
expect(polished).not.toContain("2026-10-02")
})
test("#given an edit that adds an attribute #when polish-apply and polish-diff run #then apply refuses it as markup changed and diff exits 1", () => {
const { dir } = polishSession()
const html = readFileSync(join(dir, "report.html"), "utf8")
const edits = join(dir, "attribute.json")
writeFileSync(edits, JSON.stringify([{ before: "
Findings
", after: 'Findings
', reason: "anchor" }]))
const applied = polishApply(dir, edits)
expect(applied.code).toBe(0)
expect(applied.json.data).toMatchObject({ applied: 0, rejected: 1, unlocated: 0 })
expect(applied.json.defects.map((d) => [d.code, d.index])).toEqual([["polish_edit_rejected", 0]])
expect(applied.json.defects[0]?.reason).toContain("markup changed")
expect(readFileSync(join(dir, "report.polished.html"), "utf8")).toBe(html)
writeFileSync(join(dir, "attribute.html"), html.replace("Findings
", 'Findings
'))
const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "attribute.html"), "--ledger", join(dir, "fact-ledger.json"), "--session-dir", dir])
expect(diffed.code).toBe(1)
expect(codesOf(diffed.json)).toContain("polish_diff_fail")
expect(JSON.stringify(diffed.json)).toContain("markup changed")
})
test("#given an edit whose before holds & #when fact polish-apply runs #then it prints polish-apply's own markup reason verbatim for that edit", () => {
const { dir } = polishSession()
const before = "Acme & Bolt"
const edits = join(dir, "ampersand.json")
writeFileSync(edits, JSON.stringify([{ before, after: "Acme and Bolt", reason: "ampersand" }]))
const applied = polishApply(dir, edits)
const line = ` rejected edit 0 ("${before}"): ${POLISH_MARKUP_REASON}`
expect(applied.code).toBe(0)
expect(applied.json.data).toMatchObject({ applied: 0, rejected: 1, unlocated: 0 })
expect(applied.json.defects.map((d) => [d.code, d.index, d.reason])).toEqual([["polish_edit_rejected", 0, POLISH_MARKUP_REASON]])
expect(applied.stderr.split("\n")).toContain(line)
})
test("#given a polished copy edited by hand #when fact polish-diff runs #then it exits 1 with polish_diff_fail", () => {
const { dir } = polishSession()
const html = readFileSync(join(dir, "report.html"), "utf8")
writeFileSync(join(dir, "hand.html"), html.replace("320 m", "321 m"))
const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "hand.html"), "--ledger", join(dir, "fact-ledger.json"), "--session-dir", dir])
expect(diffed.code).toBe(1)
expect(codesOf(diffed.json)).toContain("polish_diff_fail")
})
test("#given an accepted subject and object swap #when fact pairs runs with --changed-since #then only that sentence's pair is emitted, with the new text", () => {
const { dir, s } = polishSession()
const edits = join(dir, "swap.json")
writeFileSync(edits, JSON.stringify([{ before: "Acme acquired Bolt", after: "Bolt acquired Acme", reason: "word order" }]))
const applied = polishApply(dir, edits)
const map = join(dir, "report.html.fill-map.json")
const changed = nodeJson(["fact", "pairs", join(dir, "report.polished.html"), "--changed-since", map, "--out", join(dir, "pairs-changed.json"), ...s])
const written = JSON.parse(readFileSync(join(dir, "pairs-changed.json"), "utf8"))
expect(applied.json.data).toMatchObject({ applied: 1, rejected: 0 })
expect(changed.code).toBe(0)
expect(written.pairs.map((p: Pair) => [p.kind, p.fact_ids])).toEqual([["quote", ["F1"]]])
expect(written.pairs[0].sentence).toContain("Bolt acquired Acme")
})
})
describe("markdown editions under node", () => {
const MD = ["# Bridge", "", "## Findings", "", "The span length is {{F1:span_len}} [S4].", "", "The operator wrote {{Q1:completion}} [S4].", "", "## How this report was made", "", "Collected from one cited public page [S4].", "", "## Sources", "", "1. [S4] Bridge page, retrieved 2026-10-01.", ""].join("\n")
function mdSession() {
const session = fidelitySession()
writeFileSync(join(session.dir, "report.src.md"), MD)
const filled = nodeJson(["fact", "fill", join(session.dir, "report.src.md"), "--format", "md", "--out", join(session.dir, "report.md"), ...session.s])
return { ...session, filled }
}
test("#given a markdown source #when filled and verified as md #then verify passes and records the md bytes", () => {
const { dir, filled } = mdSession()
const checked = verify(dir, "report.md", ["--format", "md"])
expect(filled.code).toBe(0)
expect(readFileSync(join(dir, "report.md"), "utf8")).toContain("The span length is 320 m [S4].")
expect(checked.code).toBe(0)
expect(checked.json.defects).toEqual([])
expect(JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8")).artifact_sha256).toBe(sha256(join(dir, "report.md")))
})
test("#given a filled markdown edition #when a value is hand-edited or a number typed #then md verify exits 1 with the code", () => {
const { dir } = mdSession()
const md = readFileSync(join(dir, "report.md"), "utf8")
writeFileSync(join(dir, "typed.md"), md.replace("## How", "Revenue grew 12% [S4].\n\n## How"))
copyFileSync(join(dir, "report.md.fill-map.json"), join(dir, "typed.md.fill-map.json"))
const typed = verify(dir, "typed.md", ["--format", "md"])
expect(typed.code).toBe(1)
expect(codesOf(typed.json)).toContain("untraceable_number")
})
test("#given a filled markdown edition with an unterminated fence #when verified or paired #then both refuse with exit 2", () => {
const { dir, s } = mdSession()
writeFileSync(join(dir, "fence.md"), `${readFileSync(join(dir, "report.md"), "utf8")}\n\`\`\`\nunclosed code\n`)
copyFileSync(join(dir, "report.md.fill-map.json"), join(dir, "fence.md.fill-map.json"))
const checked = verify(dir, "fence.md", ["--format", "md"])
expect(checked.code).toBe(2)
expect(checked.stderr).toContain("unterminated_fence")
expect(nodeJson(["fact", "pairs", join(dir, "fence.md"), "--out", join(dir, "pairs.json"), ...s]).code).toBe(2)
})
test("#given a filled markdown edition #when fact pairs runs on it #then it pairs the same quotes as the html edition", () => {
const { dir, s } = mdSession()
const pairs = nodeJson(["fact", "pairs", join(dir, "report.md"), "--out", join(dir, "pairs.json"), ...s])
expect(pairs.code).toBe(0)
expect(JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8")).pairs.map((p: Pair) => p.fact_ids)).toEqual([["F1"], ["Q1"]])
})
})
describe("repair decide with several defect files", () => {
test("#given two --defects files #when decided #then the attempt signature carries both codes", async () => {
const dir = sessionDir()
const state = join(dir, "repair-state.json")
await run(["repair", "decide", "--state", state, "--defects", defectsFile(dir, "a.json", ["heading_generic"]), "--defects", defectsFile(dir, "b.json", ["untraceable_number"])])
const signature = JSON.parse(readFileSync(state, "utf8")).attempts[0].signature
expect(signature).toContain("heading_generic")
expect(signature).toContain("untraceable_number")
})
})