test(ai): record responses websocket flows (#43660)

This commit is contained in:
Shoubhit Dash
2026-08-20 21:14:37 +05:30
committed by GitHub
parent 6f629c2a9d
commit a71884dfdf
7 changed files with 612 additions and 5 deletions
@@ -31,8 +31,11 @@ interface PendingRecordings {
}
type Frame = string | Uint8Array
const normalizeProtocols = (protocols?: string | Array<string>): Array<string> =>
protocols === undefined ? [] : typeof protocols === "string" ? [protocols] : [...protocols]
const normalizeProtocols = (protocols: unknown): Array<string> => {
if (typeof protocols === "string") return [protocols]
if (Array.isArray(protocols)) return protocols.filter((protocol): protocol is string => typeof protocol === "string")
return []
}
const frameFromWebSocketData = async (data: unknown): Promise<Frame> => {
if (typeof data === "string") return data
if (data instanceof Blob) return new Uint8Array(await data.arrayBuffer())
@@ -371,7 +374,7 @@ const makeRecordingWebSocketConstructor = (
return (url, protocols) => {
const sequence = nextSequence++
const requestedProtocols = normalizeProtocols(protocols)
const native = upstream(url, requestedProtocols)
const native = Reflect.apply(upstream, undefined, [url, protocols])
const events: WebSocketEvent[] = []
let opened = false
let failed = false
@@ -80,6 +80,38 @@ describe("WebSocket", () => {
])
})
test("constructor recording forwards handshake options", async () => {
using directory = tempDirectory("http-recorder-websocket-constructor-")
let received: unknown
const recorder = HttpRecorder.layerWebSocketConstructor("websocket/constructor-options", {
directory: directory.path,
}).pipe(
Layer.provide(
Layer.succeed(Socket.WebSocketConstructor, (url, options) => {
received = options
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- the fixture implements the WebSocket surface used by the recorder.
return new EchoWebSocket(url) as unknown as globalThis.WebSocket
}),
),
)
await Effect.runPromise(
Effect.gen(function* () {
const constructor = yield* Socket.WebSocketConstructor
const options = { headers: { authorization: "Bearer fixture" } }
const socket = Reflect.apply(constructor, undefined, ["wss://echo.example.test/options", options])
yield* Effect.callback<void>((resume) => {
socket.addEventListener("open", () => {
socket.close()
resume(Effect.void)
})
})
}).pipe(Effect.scoped, Effect.provide(recorder)),
)
expect(received).toEqual({ headers: { authorization: "Bearer fixture" } })
})
test("constructor replay validates dynamic URLs and protocols without opening a live socket", async () => {
using directory = tempDirectory("http-recorder-websocket-constructor-")
await seedCassetteDirectory(directory.path, "websocket/constructor", [