fix(web-probe): reject empty analyze stdout contracts
This commit is contained in:
@@ -85,6 +85,50 @@ test("child JSON recovery falls back to artifact when stdout is not 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",
|
||||
|
||||
@@ -35,8 +35,8 @@ export function resolveCliChildJsonObject(options: {
|
||||
?? contractFallbackReason
|
||||
?? (parsedStdout.parsed === null ? fallbackReasonForStdout(parsedStdout.stdoutKind) : null);
|
||||
|
||||
let parsed = parsedStdout.parsed;
|
||||
let source: CliChildJsonSource = parsedStdout.source;
|
||||
let parsed = contractFallbackReason === null ? parsedStdout.parsed : null;
|
||||
let source: CliChildJsonSource = contractFallbackReason === null ? parsedStdout.source : null;
|
||||
let artifactDiagnostics: Record<string, unknown> | null = null;
|
||||
if (fallbackReason !== null && options.artifactFallback) {
|
||||
const artifact = options.artifactFallback.read();
|
||||
@@ -65,6 +65,7 @@ export function resolveCliChildJsonObject(options: {
|
||||
fallbackReason,
|
||||
parsedFromStdout: parsedStdout.source === "stdout",
|
||||
parsedFromDump: parsedStdout.source === "dump",
|
||||
stdoutContractAccepted: accepted,
|
||||
dumpPath: parsedStdout.dumpPath,
|
||||
dumpReason: parsedStdout.dumpReason,
|
||||
dumpReadOk: parsedStdout.dumpReadOk,
|
||||
|
||||
@@ -2607,13 +2607,24 @@ function webObserveAnalyzeCollectCommand(options: NodeWebProbeObserveOptions, fi
|
||||
}
|
||||
|
||||
function isWebObserveAnalyzeJsonContract(value: Record<string, unknown>): boolean {
|
||||
return value.ok === true
|
||||
|| value.ok === false
|
||||
|| typeof value.reportJsonPath === "string"
|
||||
|| typeof value.reportMdPath === "string"
|
||||
|| typeof value.error === "string"
|
||||
|| Object.keys(recordValue(value.analyzer)).length > 0
|
||||
|| Object.keys(recordValue(value.counts)).length > 0;
|
||||
const hasReportPath = typeof value.reportJsonPath === "string" || typeof value.reportMdPath === "string";
|
||||
const hasAnalyzer = Object.keys(recordValue(value.analyzer)).length > 0;
|
||||
const hasFindings = arrayRecordsValue(value.findings).length > 0 || arrayRecordsValue(value.archiveRedFindings).length > 0;
|
||||
const hasAnalyzeData = hasReportPath
|
||||
|| hasAnalyzer
|
||||
|| hasFindings
|
||||
|| Object.keys(recordValue(value.counts)).length > 0
|
||||
|| Object.keys(recordValue(value.sampleMetrics)).length > 0
|
||||
|| Object.keys(recordValue(value.requestRate)).length > 0
|
||||
|| Object.keys(recordValue(value.requestRateCurve)).length > 0
|
||||
|| Object.keys(recordValue(value.frontendPerformance)).length > 0
|
||||
|| Object.keys(recordValue(value.webPerformanceRuntimeDiagnostics)).length > 0
|
||||
|| Object.keys(recordValue(value.runtimeAlerts)).length > 0
|
||||
|| Object.keys(recordValue(value.archiveSummary)).length > 0
|
||||
|| Object.keys(recordValue(value.analysisWindow)).length > 0;
|
||||
const hasFailureData = typeof value.error === "string" || hasAnalyzer || hasFindings || hasReportPath;
|
||||
if (value.ok === false) return hasFailureData;
|
||||
return hasAnalyzeData;
|
||||
}
|
||||
|
||||
function compactWebObserveAnalyzePayloadForRaw(payload: Record<string, unknown>, compactRaw: boolean): Record<string, unknown> {
|
||||
|
||||
Reference in New Issue
Block a user