import { describe, test, beforeEach, afterEach } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { writePublished, ingestText, scanInbox, listPublished, serializePublishedRecord, parsePublishedRecord, type PublishedRecord, } from "../src/ingest.js"; import { mintContentId } from "../src/id.js"; const CAPTURED = "2026-06-23"; function publishedDir(root: string): string { return join(root, "ingest", "published"); } function inboxDir(root: string): string { return join(root, "ingest", "inbox"); } function mdFiles(dir: string): string[] { return existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith(".md")) : []; } describe("ingest IO — writePublished / ingestText / scanInbox / listPublished (SC1/SC3/SC4)", () => { let root: string; const prevEnv = process.env.LINKEDIN_STUDIO_DATA; beforeEach(() => { root = mkdtempSync(join(tmpdir(), "brain-ingest-")); process.env.LINKEDIN_STUDIO_DATA = root; }); afterEach(() => { if (prevEnv === undefined) delete process.env.LINKEDIN_STUDIO_DATA; else process.env.LINKEDIN_STUDIO_DATA = prevEnv; rmSync(root, { recursive: true, force: true }); }); test("create-on-demand: ingestText writes ingest/published/.md with no prior init (SC1)", () => { const body = "Min første publiserte post.\n\nMed avsnitt."; const res = ingestText({ body, captured_at: CAPTURED }); assert.equal(res.written, true); const path = join(publishedDir(root), `${mintContentId(body)}.md`); assert.ok(existsSync(path), "record file created at runtime data-path"); const rec = parsePublishedRecord(readFileSync(path, "utf8")); assert.equal(rec.provenance, "published"); assert.equal(rec.body, body, "verbatim body preserved"); }); test("published_date defaults to captured_at when not supplied", () => { const res = ingestText({ body: "no date given", captured_at: CAPTURED }); assert.equal(res.record.published_date, CAPTURED); }); test("published_date is kept when supplied", () => { const res = ingestText({ body: "dated", captured_at: CAPTURED, published_date: "2026-05-26" }); assert.equal(res.record.published_date, "2026-05-26"); }); test("re-ingesting an identical body is a no-op (SC3 dedup): {written:false}, no duplicate file", () => { const body = "same exact text"; ingestText({ body, captured_at: CAPTURED }); const res2 = ingestText({ body, captured_at: "2026-07-01" }); // different day, same body assert.equal(res2.written, false); assert.equal(mdFiles(publishedDir(root)).length, 1, "still exactly one record"); }); test("id-collision with a DIFFERING body disambiguates to -2.md — never silent data loss (B2)", () => { const body = "the real body"; const id = mintContentId(body); // Pre-plant a file at .md whose body is DIFFERENT (simulates a hash collision). mkdirSync(publishedDir(root), { recursive: true }); const squatter: PublishedRecord = { id, provenance: "published", published_date: CAPTURED, captured_at: CAPTURED, source: "manual", body: "a DIFFERENT body that happens to share the id", }; writeFileSync(join(publishedDir(root), `${id}.md`), serializePublishedRecord(squatter), "utf8"); const res = writePublished({ ...squatter, body }); assert.equal(res.written, true); assert.equal(res.collision, true); assert.match(res.path, /-2\.md$/); assert.equal(mdFiles(publishedDir(root)).length, 2, "both records kept — no data loss"); }); test("scanInbox processes top-level *.md only, skipping dotfiles like .DS_Store (SC4)", () => { mkdirSync(inboxDir(root), { recursive: true }); writeFileSync(join(inboxDir(root), "post-a.md"), "Post A body", "utf8"); writeFileSync(join(inboxDir(root), "post-b.md"), "Post B body", "utf8"); writeFileSync(join(inboxDir(root), ".DS_Store"), "junk", "utf8"); writeFileSync(join(inboxDir(root), "notes.txt"), "not markdown", "utf8"); const res = scanInbox({ captured_at: CAPTURED }); assert.equal(res.processed.length, 2, "only the two .md files processed"); assert.equal(mdFiles(publishedDir(root)).length, 2); }); test("scanInbox on an empty/absent inbox is a clean no-op (SC4)", () => { const res = scanInbox({ captured_at: CAPTURED }); assert.deepEqual(res, { processed: [], skipped: [] }); }); test("re-running scanInbox re-skips already-published records (idempotent)", () => { mkdirSync(inboxDir(root), { recursive: true }); writeFileSync(join(inboxDir(root), "post.md"), "once", "utf8"); scanInbox({ captured_at: CAPTURED }); const res2 = scanInbox({ captured_at: CAPTURED }); assert.equal(res2.processed.length, 0); assert.equal(res2.skipped.length, 1); assert.equal(mdFiles(publishedDir(root)).length, 1, "no duplicate"); }); test("listPublished surfaces provenance and skips a malformed file without throwing", () => { ingestText({ body: "good record", captured_at: CAPTURED }); mkdirSync(publishedDir(root), { recursive: true }); writeFileSync(join(publishedDir(root), "broken.md"), "not a valid record at all", "utf8"); const res = listPublished(); assert.equal(res.records.length, 1, "the one valid record listed"); assert.equal(res.records[0].provenance, "published"); assert.equal(res.skipped, 1, "the malformed file counted as skipped, not a crash"); }); });