|
1 | 1 | import { afterEach, describe, expect, it } from "vitest"; |
2 | 2 | import { ClawpatchError } from "./errors.js"; |
3 | 3 | import { __testing, extractJson, providerByName } from "./provider.js"; |
| 4 | +import { safeProviderPreview } from "./provider-json.js"; |
4 | 5 | import { revalidateOutputSchema, reviewOutputSchema } from "./types.js"; |
5 | 6 |
|
6 | 7 | // eslint-disable-next-line no-underscore-dangle |
@@ -55,6 +56,10 @@ function expectMalformed(fn: () => unknown, message: RegExp): void { |
55 | 56 | throw new Error("expected malformed-output"); |
56 | 57 | } |
57 | 58 |
|
| 59 | +function escapeRegExp(value: string): string { |
| 60 | + return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); |
| 61 | +} |
| 62 | + |
58 | 63 | describe("extractJson", () => { |
59 | 64 | it("parses strict JSON directly", () => { |
60 | 65 | const input = '{"findings":[],"inspected":{"files":[],"symbols":[],"notes":[]}}'; |
@@ -484,6 +489,46 @@ describe("extractOpencodeJson", () => { |
484 | 489 | expectMalformed(() => extractOpencodeJson(stdout), /no extractable text.*step_finish/u); |
485 | 490 | }); |
486 | 491 |
|
| 492 | + it("treats whitespace-only opencode text as no extractable text", () => { |
| 493 | + const stdout = [ |
| 494 | + JSON.stringify({ type: "text", part: { text: " \n\t " } }), |
| 495 | + JSON.stringify({ type: "step_finish", part: { reason: "stop" } }), |
| 496 | + ].join("\n"); |
| 497 | + |
| 498 | + expectMalformed(() => extractOpencodeJson(stdout), /no extractable text.*text, step_finish/u); |
| 499 | + }); |
| 500 | + |
| 501 | + it("throws malformed-output with a preview when opencode text is unparsable", () => { |
| 502 | + const stdout = [ |
| 503 | + JSON.stringify({ |
| 504 | + type: "text", |
| 505 | + part: { text: '{"findings": [' }, |
| 506 | + }), |
| 507 | + JSON.stringify({ type: "step_finish", part: { reason: "stop" } }), |
| 508 | + ].join("\n"); |
| 509 | + |
| 510 | + expectMalformed( |
| 511 | + () => extractOpencodeJson(stdout), |
| 512 | + /unparsable JSON.*text chars=14.*observed event kinds: \[text, step_finish\].*output preview: \{"findings": \[/u, |
| 513 | + ); |
| 514 | + }); |
| 515 | + |
| 516 | + it("bounds the opencode unparsable text preview", () => { |
| 517 | + const text = `{"findings":["${"x".repeat(300)}`; |
| 518 | + const stdout = JSON.stringify({ |
| 519 | + type: "text", |
| 520 | + part: { text }, |
| 521 | + }); |
| 522 | + const preview = safeProviderPreview(text); |
| 523 | + |
| 524 | + expect(preview.length).toBe(200); |
| 525 | + |
| 526 | + expectMalformed( |
| 527 | + () => extractOpencodeJson(stdout), |
| 528 | + new RegExp(`output preview: ${escapeRegExp(preview)}\\)`, "u"), |
| 529 | + ); |
| 530 | + }); |
| 531 | + |
487 | 532 | it("throws provider-failure for opencode error events", () => { |
488 | 533 | const stdout = JSON.stringify({ |
489 | 534 | type: "error", |
|
0 commit comments