paperclip/packages/adapter-utils/src/duplex-observability.test.ts

71 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
DUPLEX_LOSS_REASONS,
HTTP2_TELEMETRY_EVENT_NAMES,
mapHttp2EventToDuplexLossReason,
normalizeDuplexLossReason,
normalizeDuplexProvider,
type DuplexFallbackReason,
type DuplexTransportValue,
} from "./duplex-observability.js";
describe("duplex observability: HTTP/2 event mapping (accepted security fix 7)", () => {
it("test_http2_events_map_to_the_loss_taxonomy", () => {
// Every closed HTTP/2 event name maps to a value from the existing,
// closed DuplexLossReason set. The map reuses one taxonomy across every
// transport, so no separate HTTP/2-only reason list exists.
for (const event of HTTP2_TELEMETRY_EVENT_NAMES) {
const reason = mapHttp2EventToDuplexLossReason(event);
expect(DUPLEX_LOSS_REASONS).toContain(reason);
}
expect(mapHttp2EventToDuplexLossReason("session_error")).toBe("rpc_failure");
expect(mapHttp2EventToDuplexLossReason("session_goaway")).toBe("transport_closed");
expect(mapHttp2EventToDuplexLossReason("session_stall")).toBe("heartbeat_timeout");
expect(mapHttp2EventToDuplexLossReason("write_error")).toBe("write_error");
expect(mapHttp2EventToDuplexLossReason("transport_closed")).toBe("transport_closed");
expect(mapHttp2EventToDuplexLossReason("channel_exit")).toBe("provider_exit");
});
it("test_an_unknown_telemetry_input_maps_to_other", () => {
// An unknown event name, a raw provider error string, and a missing value
// all map to `other`. No raw text ever reaches a sink through this map.
expect(mapHttp2EventToDuplexLossReason("ECONNRESET: read failed at socket.js:42")).toBe("other");
expect(mapHttp2EventToDuplexLossReason("some_future_event")).toBe("other");
expect(mapHttp2EventToDuplexLossReason(null)).toBe("other");
expect(mapHttp2EventToDuplexLossReason(undefined)).toBe("other");
expect(mapHttp2EventToDuplexLossReason("")).toBe("other");
});
it("keeps the closed HTTP/2 event-name set to the seven named signals", () => {
expect([...HTTP2_TELEMETRY_EVENT_NAMES].sort()).toEqual(
[
"session_error",
"session_goaway",
"session_stall",
"write_error",
"transport_closed",
"channel_exit",
].sort(),
);
});
});
describe("duplex observability: closed transport and fallback-reason values", () => {
it("accepts the http2 transport value and the preface_missing fallback reason", () => {
// A type-level check: these string literals must widen to the exported
// union types with no cast, so a drift in either union breaks the build.
const transport: DuplexTransportValue = "http2";
const fallbackReason: DuplexFallbackReason = "preface_missing";
expect(transport).toBe("http2");
expect(fallbackReason).toBe("preface_missing");
});
});
describe("duplex observability: existing normalization stays intact", () => {
it("still maps an unknown loss cause and an unknown provider key to their closed defaults", () => {
expect(normalizeDuplexLossReason("not_a_real_reason")).toBe("other");
expect(normalizeDuplexProvider("some-unlisted-plugin")).toBe("other");
});
});