import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { beforeAll, describe, expect, it, TestContext } from 'vitest'; import { AppConfig } from '../config/app-config.service'; import { markdownForDocument } from './export-markdown'; import { PandocServerConverter } from './pandoc.converter'; /** * Export fidelity regression (issue #69, ADR 0009): exports the committed * Markdown corpus to `.docx`/`.odt` through the real pinned pandoc and reads * each document back, asserting the round-trip Markdown matches its snapshot. * This gates the export writer's structural fidelity — a pandoc version bump * that shifts the office output surfaces as a snapshot diff to review, not a * silent regression. Needs a reachable pandoc sidecar (`pandoc/core:3.6`, so * output matches the snapshots); each test skips itself when none is * configured, and CI starts one and points `PANDOC_URL` at it. */ const PANDOC_URL = process.env.PANDOC_URL ?? 'http://localhost:3030'; // The runner's cwd is `apps/api`; the corpus lives at the repo root. const FIXTURES = join(process.cwd(), '../../fixtures/export'); // Same reader options as the import corpus, so both directions normalise alike. const MARKDOWN_FORMAT = 'gfm-implicit_figures-raw_html'; const converter = new PandocServerConverter({ env: { PANDOC_URL } } as unknown as AppConfig); let reachable = false; const CORPUS = ['article', 'formatting']; const FORMATS = ['docx', 'odt'] as const; /** Export `markdown` to an office document, then read it back to Markdown — * exactly the ExportService write path (gfm → ext, standalone) followed by a * re-read (ext → gfm). */ async function roundTrip(markdown: string, ext: 'docx' | 'odt'): Promise { const document = await converter.convert({ from: 'gfm', to: ext, input: Buffer.from(markdown, 'utf8'), standalone: true, }); const back = await converter.convert({ from: ext, to: MARKDOWN_FORMAT, input: document.output, standalone: false, wrap: 'none', }); return back.output.toString('utf8'); } describe('export fidelity corpus (real pandoc, issue #69)', () => { beforeAll(async () => { reachable = await converter.reachable().catch(() => false); }); for (const name of CORPUS) { for (const ext of FORMATS) { it(`round-trips ${name}.md through ${ext} to its expected Markdown`, async (ctx: TestContext) => { if (!reachable) ctx.skip(); const source = readFileSync(join(FIXTURES, `${name}.md`), 'utf8'); // The export transform (flatten wikilinks, inline images) runs first; for // this text corpus it is an identity, so the snapshot isolates pandoc's // office-writer fidelity — the part that drifts on a version bump. const document = markdownForDocument(source, new Map()); const expected = readFileSync(join(FIXTURES, `${name}.${ext}.expected.md`), 'utf8'); expect(await roundTrip(document, ext)).toBe(expected); }); } } });