Files
pikasTech-unidesk/scripts/src/cli-child-json-recovery.test.ts
T

181 lines
7.4 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "bun:test";
import { resolveCliChildJsonCommandResult, resolveCliChildJsonObject } from "./cli-child-json-recovery";
test("child JSON recovery reads full JSON from CLI stdout dump wrapper", async () => {
const dir = await mkdtemp(join(tmpdir(), "unidesk-cli-child-json-dump-"));
const dumpPath = join(dir, "dump.json");
await writeFile(dumpPath, JSON.stringify({ ok: true, data: { contract: "full-json" } }) + "\n");
const wrapper = {
ok: true,
command: "fixture",
data: {
outputTruncated: true,
reason: "stdout-json-bytes-exceeded-threshold",
dump: { path: dumpPath },
},
};
const resolved = resolveCliChildJsonObject({
stdout: JSON.stringify(wrapper),
requestedStdoutType: "fixture JSON",
});
assert.equal(resolved.source, "dump");
assert.equal(resolved.parsed?.ok, true);
assert.deepEqual(resolved.parsed?.data, { contract: "full-json" });
assert.equal(resolved.diagnostics.stdoutKind, "dump-wrapper");
assert.equal(resolved.diagnostics.dumpPath, dumpPath);
assert.equal(resolved.diagnostics.dumpReadOk, true);
assert.equal(resolved.diagnostics.dumpJsonOk, true);
});
test("child JSON recovery command-result helper keeps dump diagnostics", async () => {
const dir = await mkdtemp(join(tmpdir(), "unidesk-cli-child-json-result-dump-"));
const dumpPath = join(dir, "dump.json");
await writeFile(dumpPath, JSON.stringify({ ok: true, verified: true, bytes: 123 }) + "\n");
const resolved = resolveCliChildJsonCommandResult({
result: {
stdout: JSON.stringify({
ok: true,
data: {
outputTruncated: true,
reason: "stdout-json-bytes-exceeded-threshold",
dump: { path: dumpPath },
},
}),
stderr: "",
exitCode: 0,
timedOut: false,
},
requestedStdoutType: "trans download verification JSON",
acceptParsed: (value) => value.ok === true && value.verified === true,
});
assert.equal(resolved.source, "dump");
assert.equal(resolved.parsed?.verified, true);
assert.equal(resolved.diagnostics.requestedStdoutType, "trans download verification JSON");
assert.equal(resolved.diagnostics.dumpPath, dumpPath);
});
test("child JSON recovery falls back to artifact after trans truncation summary", () => {
const summary = {
code: "ssh-truncation-summary",
exitCode: 0,
timedOut: false,
stdout: {
stream: "stdout",
thresholdBytes: 10240,
observedBytesAtTruncation: 165000,
forwardedBytes: 10240,
dumpPath: null,
dumpError: null,
},
};
const resolved = resolveCliChildJsonObject({
stdout: `UNIDESK_SSH_TRUNCATION_SUMMARY ${JSON.stringify(summary)}\n`,
requestedStdoutType: "web-probe observe analyze compact JSON",
artifactFallback: {
path: "analysis/report.json",
nextCommand: "bun scripts/cli.ts web-probe observe collect webobs-fixture --view files --file analysis/report.json",
read: () => ({ ok: true, value: { ok: true, reportJsonPath: "analysis/report.json" }, reason: "analysis-artifact-contract" }),
},
});
assert.equal(resolved.source, "artifact");
assert.equal(resolved.parsed?.ok, true);
assert.equal(resolved.diagnostics.stdoutKind, "ssh-truncation-summary");
assert.equal(resolved.diagnostics.fallbackReason, "stdout-trans-truncated");
const artifact = resolved.diagnostics.artifact as Record<string, unknown>;
assert.equal(artifact.ok, true);
assert.equal(artifact.nextCommand, "bun scripts/cli.ts web-probe observe collect webobs-fixture --view files --file analysis/report.json");
});
test("child JSON recovery falls back to artifact when stdout is not JSON", () => {
const resolved = resolveCliChildJsonObject({
stdout: "remote helper wrote a bounded text summary instead of JSON",
requestedStdoutType: "web-probe observe analyze compact JSON",
artifactFallback: {
path: "analysis/report.json",
nextCommand: "collect report",
read: () => ({ ok: true, value: { ok: true, counts: { network: 9 }, reportJsonPath: "analysis/report.json" } }),
},
});
assert.equal(resolved.source, "artifact");
assert.equal(resolved.parsed?.ok, true);
assert.deepEqual(resolved.parsed?.counts, { network: 9 });
assert.equal(resolved.diagnostics.stdoutKind, "non-json");
assert.equal(resolved.diagnostics.fallbackReason, "stdout-not-json");
});
test("child JSON recovery rejects ok-only stdout contract and uses artifact", () => {
const resolved = resolveCliChildJsonObject({
stdout: JSON.stringify({ ok: true }),
requestedStdoutType: "web-probe observe analyze compact JSON",
acceptParsed: (value) => typeof value.reportJsonPath === "string" || typeof value.counts === "object",
artifactFallback: {
path: "analysis/report.json",
nextCommand: "collect report",
read: () => ({ ok: true, value: { ok: true, reportJsonPath: "analysis/report.json", counts: { network: 9 } } }),
},
});
assert.equal(resolved.source, "artifact");
assert.equal(resolved.parsed?.reportJsonPath, "analysis/report.json");
assert.deepEqual(resolved.parsed?.counts, { network: 9 });
assert.equal(resolved.diagnostics.stdoutKind, "json");
assert.equal(resolved.diagnostics.fallbackReason, "stdout-json-contract-invalid");
assert.equal(resolved.diagnostics.stdoutContractAccepted, false);
});
test("child JSON recovery drops invalid ok-only stdout when artifact fallback is missing", () => {
const resolved = resolveCliChildJsonObject({
stdout: JSON.stringify({ ok: true }),
requestedStdoutType: "web-probe observe analyze compact JSON",
acceptParsed: (value) => typeof value.reportJsonPath === "string" || typeof value.counts === "object",
artifactFallback: {
path: "analysis/report.json",
nextCommand: "collect report",
read: () => ({ ok: false, reason: "artifact-missing", path: "analysis/report.json" }),
},
});
assert.equal(resolved.parsed, null);
assert.equal(resolved.source, null);
assert.equal(resolved.diagnostics.stdoutKind, "json");
assert.equal(resolved.diagnostics.fallbackReason, "stdout-json-contract-invalid");
assert.equal(resolved.diagnostics.stdoutContractAccepted, false);
const artifact = resolved.diagnostics.artifact as Record<string, unknown>;
assert.equal(artifact.ok, false);
assert.equal(artifact.reason, "artifact-missing");
assert.equal(artifact.path, "analysis/report.json");
assert.equal(artifact.nextCommand, "collect report");
});
test("child JSON recovery reports artifact fallback failure when stdout is unusable and artifact is missing", () => {
const resolved = resolveCliChildJsonObject({
stdout: "not json",
requestedStdoutType: "web-probe observe analyze compact JSON",
artifactFallback: {
path: "analysis/report.json",
nextCommand: "collect report",
read: () => ({ ok: false, reason: "artifact-missing", path: "analysis/report.json" }),
},
});
assert.equal(resolved.parsed, null);
assert.equal(resolved.source, null);
assert.equal(resolved.diagnostics.stdoutKind, "non-json");
assert.equal(resolved.diagnostics.fallbackReason, "stdout-not-json");
const artifact = resolved.diagnostics.artifact as Record<string, unknown>;
assert.equal(artifact.ok, false);
assert.equal(artifact.reason, "artifact-missing");
assert.equal(artifact.path, "analysis/report.json");
assert.equal(artifact.nextCommand, "collect report");
});