fix: address review nits — refactor fallbacks, add unit tests (FAR-122)

- Merge both one-shot log fallbacks into a single conditional block using a
  cheap string-scan guard (`stdout.includes('"type":"result"')`) to avoid
  calling parseClaudeStreamJson twice and prevent double readPodLogs calls
  when the first fallback already ran.

- Extract error-message logic into `buildPartialRunError(exitCode, model, stdout)`
  (exported for tests) so the `!parsed` branch is a one-liner and the logic
  is independently testable.

- Export `isK8s404` for tests.

- Add execute.test.ts with 15 unit tests covering:
  - isK8s404: v0.x response.statusCode, v1.0+ response.status, direct
    statusCode, message-based detection, non-404 codes
  - buildPartialRunError: exitCode=0 path, empty stdout, init-only output
    (model surfaced), first non-system content line, null exitCode (-1),
    multiple consecutive system events

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Test User
2026-04-22 19:42:57 +00:00
parent b9def0964e
commit 8c8c2f2ec0
2 changed files with 169 additions and 48 deletions
+108
View File
@@ -0,0 +1,108 @@
import { describe, it, expect } from "vitest";
import { isK8s404, buildPartialRunError } from "./execute.js";
describe("isK8s404", () => {
it("returns false for non-Error values", () => {
expect(isK8s404(null)).toBe(false);
expect(isK8s404(undefined)).toBe(false);
expect(isK8s404("string error")).toBe(false);
expect(isK8s404(404)).toBe(false);
});
it("returns false for unrelated errors", () => {
expect(isK8s404(new Error("something went wrong"))).toBe(false);
expect(isK8s404(new Error("HTTP-Code: 500 Message: Internal Server Error"))).toBe(false);
});
it("detects 404 from v1.0+ message format", () => {
const err = new Error("HTTP-Code: 404 Message: Unknown API Status Code! Body: ...");
expect(isK8s404(err)).toBe(true);
});
it("detects 404 from v0.x response.statusCode", () => {
const err = Object.assign(new Error("Not Found"), {
response: { statusCode: 404 },
});
expect(isK8s404(err)).toBe(true);
});
it("detects 404 from v1.0+ response.status", () => {
const err = Object.assign(new Error("Not Found"), {
response: { status: 404 },
});
expect(isK8s404(err)).toBe(true);
});
it("detects 404 from direct statusCode property", () => {
const err = Object.assign(new Error("Not Found"), { statusCode: 404 });
expect(isK8s404(err)).toBe(true);
});
it("does not match non-404 status codes on response", () => {
const err = Object.assign(new Error("Forbidden"), {
response: { statusCode: 403 },
});
expect(isK8s404(err)).toBe(false);
});
});
describe("buildPartialRunError", () => {
const initLine = JSON.stringify({
type: "system",
subtype: "init",
model: "claude-sonnet-4-6",
session_id: "sess_abc",
});
it("returns parse-failure message when exitCode is 0", () => {
expect(buildPartialRunError(0, "", "")).toBe("Failed to parse Claude JSON output");
expect(buildPartialRunError(0, "claude-sonnet-4-6", initLine)).toBe(
"Failed to parse Claude JSON output",
);
});
it("returns generic exit message when stdout is empty", () => {
expect(buildPartialRunError(1, "", "")).toBe("Claude exited with code 1");
expect(buildPartialRunError(null, "", "")).toBe("Claude exited with code -1");
});
it("skips system/init events and returns generic message when only init captured", () => {
const msg = buildPartialRunError(1, "claude-sonnet-4-6", initLine);
expect(msg).toBe(
"Claude started but did not produce a result (model: claude-sonnet-4-6) — check API credentials, model support, and adapter config",
);
});
it("includes model from parsedStream when stdout is init-only", () => {
const msg = buildPartialRunError(null, "MiniMax-M2.7", initLine);
expect(msg).toContain("MiniMax-M2.7");
expect(msg).not.toContain("type");
expect(msg).not.toContain("system");
});
it("uses first non-system line as content when present", () => {
const stdout = [initLine, "Error: no API key configured"].join("\n");
const msg = buildPartialRunError(1, "claude-sonnet-4-6", stdout);
expect(msg).toBe("Claude exited with code 1: Error: no API key configured");
});
it("uses first non-system JSON event as content", () => {
const resultLike = JSON.stringify({ type: "result", subtype: "error", result: "rate limit" });
const stdout = [initLine, resultLike].join("\n");
const msg = buildPartialRunError(2, "claude-sonnet-4-6", stdout);
expect(msg).toContain("rate limit");
expect(msg).toContain("code 2");
});
it("null exitCode renders as -1 in message", () => {
const msg = buildPartialRunError(null, "", "Some plain error text");
expect(msg).toBe("Claude exited with code -1: Some plain error text");
});
it("skips multiple consecutive system events", () => {
const anotherSystem = JSON.stringify({ type: "system", subtype: "other" });
const stdout = [initLine, anotherSystem, "real error line"].join("\n");
const msg = buildPartialRunError(1, "model-x", stdout);
expect(msg).toBe("Claude exited with code 1: real error line");
});
});