/** * formatter.test.ts - Unit tests verifying context is shown in all output formats * * Run with: bun test formatter.test.ts */ import { describe, test, expect } from "vitest"; import { // Search result formatters searchResultsToJson, searchResultsToCsv, searchResultsToFiles, searchResultsToMarkdown, searchResultsToXml, searchResultsToMcpCsv, formatSearchResults, // Document (multi-get) formatters documentsToJson, documentsToCsv, documentsToFiles, documentsToMarkdown, documentsToXml, formatDocuments, // Single document formatters documentToJson, documentToMarkdown, documentToXml, formatDocument, type MultiGetFile, } from "../src/cli/formatter.js"; import type { SearchResult, DocumentResult } from "../src/store.js"; // ============================================================================= // Test Fixtures // ============================================================================= const TEST_CONTEXT = "Internal engineering keynotes from company summit events"; function makeSearchResult(overrides: Partial = {}): SearchResult { return { filepath: "qmd://archive/summit/keynote.md", displayPath: "qmd://archive/summit/keynote.md", title: "Summit Keynote", context: TEST_CONTEXT, hash: "dc5590abcdef", docid: "dc5590", collectionName: "archive", modifiedAt: "2024-01-01T00:00:00Z", bodyLength: 100, body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.", score: 0.84, source: "fts", ...overrides, }; } function makeDocumentResult(overrides: Partial = {}): DocumentResult { return { filepath: "qmd://archive/summit/keynote.md", displayPath: "qmd://archive/summit/keynote.md", title: "Summit Keynote", context: TEST_CONTEXT, hash: "dc5590abcdef", docid: "dc5590", collectionName: "archive", modifiedAt: "2024-01-01T00:00:00Z", bodyLength: 100, body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.", ...overrides, }; } function makeMultiGetFile(overrides: Partial = {}): MultiGetFile { return { filepath: "qmd://archive/summit/keynote.md", displayPath: "qmd://archive/summit/keynote.md", title: "Summit Keynote", context: TEST_CONTEXT, body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.", skipped: false, ...overrides, }; } // ============================================================================= // Search Results: Context in Every Format // ============================================================================= describe("search results include context in all formats", () => { const results = [makeSearchResult()]; test("JSON format includes context", () => { const output = searchResultsToJson(results, { query: "keynote" }); const parsed = JSON.parse(output); expect(parsed[0].context).toBe(TEST_CONTEXT); }); test("CSV format includes context", () => { const output = searchResultsToCsv(results, { query: "keynote" }); // Header should have context column const lines = output.split("\n"); expect(lines[0]).toContain("context"); // Data row should contain the context text expect(output).toContain(TEST_CONTEXT); }); test("files format includes context", () => { const output = searchResultsToFiles(results); expect(output).toContain(TEST_CONTEXT); }); test("Markdown format includes context", () => { const output = searchResultsToMarkdown(results, { query: "keynote" }); expect(output).toContain(TEST_CONTEXT); }); test("XML format includes context", () => { const output = searchResultsToXml(results, { query: "keynote" }); expect(output).toContain(TEST_CONTEXT); }); test("MCP CSV format includes context", () => { const mcpResults = [{ docid: "dc5590", file: "qmd://archive/summit/keynote.md", title: "Summit Keynote", score: 0.84, context: TEST_CONTEXT, snippet: "This is the keynote content.", }]; const output = searchResultsToMcpCsv(mcpResults); expect(output).toContain(TEST_CONTEXT); }); test("formatSearchResults (JSON) includes context", () => { const output = formatSearchResults(results, "json", { query: "keynote" }); const parsed = JSON.parse(output); expect(parsed[0].context).toBe(TEST_CONTEXT); }); test("formatSearchResults (CSV) includes context", () => { const output = formatSearchResults(results, "csv", { query: "keynote" }); expect(output).toContain(TEST_CONTEXT); }); test("formatSearchResults (files) includes context", () => { const output = formatSearchResults(results, "files"); expect(output).toContain(TEST_CONTEXT); }); test("formatSearchResults (md) includes context", () => { const output = formatSearchResults(results, "md", { query: "keynote" }); expect(output).toContain(TEST_CONTEXT); }); test("formatSearchResults (xml) includes context", () => { const output = formatSearchResults(results, "xml", { query: "keynote" }); expect(output).toContain(TEST_CONTEXT); }); }); // ============================================================================= // Search Results: No Context When Absent // ============================================================================= describe("search results omit context when null", () => { const results = [makeSearchResult({ context: null })]; test("JSON format omits context field when null", () => { const output = searchResultsToJson(results, { query: "keynote" }); const parsed = JSON.parse(output); expect(parsed[0].context).toBeUndefined(); }); test("files format does not include trailing context when null", () => { const output = searchResultsToFiles(results); // Should just be docid,score,path - no trailing comma/context expect(output).not.toContain(",\""); }); }); // ============================================================================= // Multi-Get Documents: Context in Every Format // ============================================================================= describe("multi-get documents include context in all formats", () => { const docs = [makeMultiGetFile()]; test("JSON format includes context", () => { const output = documentsToJson(docs); const parsed = JSON.parse(output); expect(parsed[0].context).toBe(TEST_CONTEXT); }); test("CSV format includes context", () => { const output = documentsToCsv(docs); const lines = output.split("\n"); expect(lines[0]).toContain("context"); expect(output).toContain(TEST_CONTEXT); }); test("files format includes context", () => { const output = documentsToFiles(docs); expect(output).toContain(TEST_CONTEXT); }); test("Markdown format includes context", () => { const output = documentsToMarkdown(docs); expect(output).toContain(TEST_CONTEXT); }); test("XML format includes context", () => { const output = documentsToXml(docs); expect(output).toContain(TEST_CONTEXT); }); test("formatDocuments (JSON) includes context", () => { const output = formatDocuments(docs, "json"); const parsed = JSON.parse(output); expect(parsed[0].context).toBe(TEST_CONTEXT); }); test("formatDocuments (md) includes context", () => { const output = formatDocuments(docs, "md"); expect(output).toContain(TEST_CONTEXT); }); test("formatDocuments (xml) includes context", () => { const output = formatDocuments(docs, "xml"); expect(output).toContain(TEST_CONTEXT); }); }); // ============================================================================= // Single Document: Context in Every Format // ============================================================================= describe("single document includes context in all formats", () => { const doc = makeDocumentResult(); test("JSON format includes context", () => { const output = documentToJson(doc); const parsed = JSON.parse(output); expect(parsed.context).toBe(TEST_CONTEXT); }); test("Markdown format includes context", () => { const output = documentToMarkdown(doc); expect(output).toContain(TEST_CONTEXT); }); test("XML format includes context", () => { const output = documentToXml(doc); expect(output).toContain(TEST_CONTEXT); }); test("formatDocument (JSON) includes context", () => { const output = formatDocument(doc, "json"); const parsed = JSON.parse(output); expect(parsed.context).toBe(TEST_CONTEXT); }); test("formatDocument (md) includes context", () => { const output = formatDocument(doc, "md"); expect(output).toContain(TEST_CONTEXT); }); test("formatDocument (xml) includes context", () => { const output = formatDocument(doc, "xml"); expect(output).toContain(TEST_CONTEXT); }); }); // ============================================================================= // Single Document: No Context When Absent // ============================================================================= describe("single document omits context when null", () => { const doc = makeDocumentResult({ context: null }); test("JSON format omits context field when null", () => { const output = documentToJson(doc); const parsed = JSON.parse(output); expect(parsed.context).toBeUndefined(); }); test("Markdown format does not show Context line when null", () => { const output = documentToMarkdown(doc); expect(output).not.toContain("Context:"); }); test("XML format does not show context element when null", () => { const output = documentToXml(doc); expect(output).not.toContain(""); }); });