Files
rea/tests/acceptance/jsonLinesCli.test.ts
T
kiran 5d2c8c2b78 feat(cli)!: export row-oriented results as JSON Lines (#5629)
* feat(cli): add JSON Lines output for row-oriented results (#5487)

- Support opt-in JSON Lines format (--format jsonl) for row-oriented results and collection views with clear record boundaries
- Preserve stable source identities and stream records deterministically line-by-line
- Emit final coverage and limitation metadata as a distinct line with https://rea.dev/schemas/jsonl-coverage-v1.json to prevent confusion with data rows
- Distinguish error outputs with https://rea.dev/schemas/jsonl-error-v1.json
- Reject non-row-oriented results that lack record boundaries with an unreadable_output error
- Document JSON Lines streaming conventions in docs/cli.md
- Add boundary tests covering empty results, multiline strings, partial coverage, large fields, and non-row rejection

* fix(cli): preserve diagnostics, provenance, and nested collections in JSONL

* fix(cli): frame classified failures and reject unrepresented JSONL facets

---------

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-24 02:13:28 +08:00

93 lines
3.2 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { parseEvidence } from "../../src/domain/evidence.js";
import { analysisViewJavaScriptEvidence } from "../fixtures/analysisView.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
cliTest(
"exports authenticated analysis-view rows, preserving every Evidence fact and empty/partial coverage",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-json-lines-view-");
const parent = analysisViewJavaScriptEvidence();
const evidencePath = join(root, "parent.json");
const requestPath = join(root, "request.json");
await writeFile(evidencePath, JSON.stringify(parent));
const request = {
source: { kind: "evidence-file", path: evidencePath },
view: { kind: "javascript-facts", property_keys: [], limit: 1 },
};
await writeFile(requestPath, JSON.stringify(request));
const invalid = await cli.run({
arguments: ["inspect-analysis-view", "--format", "jsonl"],
});
expect(invalid.exitCode).toBe(1);
expect(JSON.parse(invalid.stdout)).toMatchObject({
$rea: "jsonl/v1",
type: "error",
result: { code: "VALIDATION_ERROR" },
});
const badOutputValue = await cli.run({
arguments: ["--format", "jsonl", "--token-limit", "invalid"],
});
expect(badOutputValue.exitCode).toBe(1);
expect(JSON.parse(badOutputValue.stdout)).toMatchObject({
$rea: "jsonl/v1",
type: "error",
});
for (const where of [undefined, { paths: [] }]) {
await writeFile(
requestPath,
JSON.stringify({
...request,
view: { ...request.view, ...(where === undefined ? {} : { where }) },
}),
);
const json = await cli.run({
arguments: ["inspect-analysis-view", requestPath, "--json"],
});
expect(json.exitCode, json.stderr).toBe(0);
const expected = parseEvidence(json.json);
const result = await cli.run({
arguments: ["inspect-analysis-view", requestPath, "--format", "jsonl"],
});
expect(result.exitCode, result.stderr).toBe(0);
const records = result.stdout
.trimEnd()
.split("\n")
.map((line) => JSON.parse(line));
const final = records.pop();
expect(final).toMatchObject({
$rea: "jsonl/v1",
type: "metadata",
records: {
path: ["normalized_result", "rows"],
emitted: records.length,
source: records.length,
},
context: {
evidence_links: [parent.evidence_id],
normalized_result: { parent_evidence_id: parent.evidence_id },
},
});
const reconstructed = {
...final.context,
normalized_result: {
...final.context.normalized_result,
rows: records,
},
};
// Authenticate the reconstructed Evidence, rather than checking only a few
// convenient fields from a shortened view-shaped object.
expect(parseEvidence(reconstructed)).toEqual(expected);
expect(records.length).toBe(where === undefined ? 1 : 0);
}
},
);