Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion scripts/lib/orchestrator-gateway.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,14 @@ export async function verifyOrchestratorHealthz(healthzUrl, options = {}) {
}

let raw;
const reader = response.body?.getReader?.();
let reader;
try {
reader = response.body?.getReader?.();
} catch {
throw new Error(
"contextual-orchestrator health response body is not stream-readable",
);
}
if (reader) {
const body = Buffer.allocUnsafe(HEALTH_BODY_LIMIT_BYTES);
let totalBytes = 0;
Expand Down
20 changes: 20 additions & 0 deletions test/orchestrator-gateway-stream-bound.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ describe("contextual-orchestrator streamed health response", () => {
expect(released).toBe(true);
});

it("normalizes a locked health response body instead of surfacing stream implementation errors", async () => {
const response = new Response(
JSON.stringify({ status: "ok", service: "contextual-orchestrator" }),
{ headers: { "content-type": "application/json" } },
);
const heldReader = response.body!.getReader();

try {
await expect(
verifyOrchestratorHealthz("https://orchestrator.example/healthz", {
fetchImpl: (async () => response) as typeof fetch,
}),
).rejects.toThrow(
"contextual-orchestrator health response body is not stream-readable",
);
} finally {
heldReader.releaseLock();
}
});

it("does not let stalled response-body cancellation delay content-length rejection", async () => {
let cancellationStarted = false;
const response = {
Expand Down
Loading