inspector/clients/web/src/utils/uncapturedBodyNote.test.ts
cliffhall 1a5dd6820d fix: address Copilot review round 1 on #2318
- fetchTracking: count NDJSON streams line by line instead of leaving
  them at zero forever; justify the detached watcher promise; document
  that a partial SSE block at end-of-stream is dropped, as the SDK's own
  parser (no flush) and the SSE spec drop it.
- uncapturedBodyNote: case-insensitive content-type lookup, and a
  stream label (SSE / NDJSON) for the Network badge.
- InspectorClient: `openedAt` is when the response headers arrived, not
  when the GET went out; the snapshot copies its request records; a
  request the transport failed to send is rolled back out of the
  unanswered set via a new `trackSendFailure` callback; a raw-wire
  timeout is built as the SDK's own timeout shape and annotated the
  same way, so callers see one kind of timeout.
- Remote session storage revives the nested `stream.closedAt` Date.
- Hook test models a legacy double with `Object.defineProperty`, not a
  double cast.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APKSYHGxXBUpnR7ErFiLJn
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-13 22:03:00 -04:00

79 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { FetchRequestEntry } from "@inspector/core/mcp/types.js";
import {
isLongLivedStreamEntry,
longLivedStreamLabel,
uncapturedBodyNote,
} from "./uncapturedBodyNote";
const streamEntry: FetchRequestEntry = {
id: "n-1",
timestamp: new Date("2026-03-17T10:00:00Z"),
method: "GET",
url: "https://example.com/mcp",
requestHeaders: {},
responseStatus: 200,
responseHeaders: { "content-type": "text/event-stream" },
category: "transport",
};
describe("isLongLivedStreamEntry", () => {
it("is true for a GET answered with an event stream and false otherwise", () => {
expect(isLongLivedStreamEntry(streamEntry)).toBe(true);
expect(isLongLivedStreamEntry({ ...streamEntry, method: "POST" })).toBe(
false,
);
expect(
isLongLivedStreamEntry({ ...streamEntry, responseHeaders: undefined }),
).toBe(false);
});
it("reads the content type whatever casing the entry recorded it with", () => {
expect(
isLongLivedStreamEntry({
...streamEntry,
responseHeaders: { "Content-Type": "text/event-stream" },
}),
).toBe(true);
});
});
describe("longLivedStreamLabel", () => {
it("names the framing the stream uses", () => {
expect(longLivedStreamLabel(streamEntry)).toBe("SSE");
expect(
longLivedStreamLabel({
...streamEntry,
responseHeaders: { "Content-Type": "application/x-ndjson" },
}),
).toBe("NDJSON");
});
});
describe("uncapturedBodyNote", () => {
it("says (empty) for a bounded response", () => {
expect(uncapturedBodyNote({ ...streamEntry, method: "POST" })).toBe(
"(empty)",
);
});
it("says only that the body was not captured before the stream is watched", () => {
expect(uncapturedBodyNote(streamEntry)).toBe(
"Long-lived stream — body not captured",
);
});
it("reports the event count and open state, with the singular for one event", () => {
expect(
uncapturedBodyNote({ ...streamEntry, stream: { eventCount: 3 } }),
).toBe(
"Long-lived stream — 3 events delivered, still open; body not captured",
);
expect(
uncapturedBodyNote({
...streamEntry,
stream: { eventCount: 1, closedAt: new Date() },
}),
).toBe("Long-lived stream — 1 event delivered, closed; body not captured");
});
});