import { describe, expect, it } from "@suaveplan/testing/runner"; import { SandboxVmBridgeError } from "../error-codes/error-codes.js"; import { SerializedResponseSchema, unmarshalResponse, } from "./marshal-response.js"; function encode(bytes: ArrayLike): string { return Buffer.from(Uint8Array.from(bytes)).toString("base64url"); } describe("unmarshalResponse", () => { it("base64url body produces a Response with matching bytes", { timeout: 5000, }, async () => { const r = unmarshalResponse({ status: 200, headers: [["content-type", "text/plain"]], bodyBase64: encode( new TextEncoder().encode( "hello" ) as unknown as ArrayLike ), }); expect(r.status).toBe(200); expect(r.headers.get("content-type")).toBe("text/plain"); expect(await r.text()).toBe("hello"); }); it("binary body crosses unchanged through base64url", { timeout: 5000, }, async () => { const bytes = [1, 2, 3, 200, 250, 255]; const r = unmarshalResponse({ status: 201, headers: [], bodyBase64: encode(bytes), }); expect(r.status).toBe(201); const buf = new Uint8Array(await r.arrayBuffer()); expect(Array.from(buf)).toEqual(bytes); }); it("empty bodyBase64 produces an empty body response", { timeout: 5000, }, async () => { const r = unmarshalResponse({ status: 200, headers: [], bodyBase64: "", }); expect(r.status).toBe(200); const buf = new Uint8Array(await r.arrayBuffer()); expect(buf.byteLength).toBe(0); }); it("status 204 drops the body even when bodyBase64 is non-empty", { timeout: 5000, }, async () => { const r = unmarshalResponse({ status: 204, headers: [], bodyBase64: encode([1, 2, 3]), }); expect(r.status).toBe(204); }); it("non-object value raises UNMARSHAL_FAILED", { timeout: 5000, }, () => { expect(() => unmarshalResponse(null)).toThrow(SandboxVmBridgeError); }); it("missing status raises UNMARSHAL_FAILED", { timeout: 5000, }, () => { try { unmarshalResponse({ headers: [], bodyBase64: "" }); throw new Error("did not throw"); } catch (err) { expect(err).toBeInstanceOf(SandboxVmBridgeError); expect((err as SandboxVmBridgeError).code).toBe( "SAND_VM_BRIDGE.UNMARSHAL_FAILED" ); } }); it("status out of range raises UNMARSHAL_FAILED", { timeout: 5000, }, () => { expect(() => unmarshalResponse({ status: 99, headers: [], bodyBase64: "" }) ).toThrow(SandboxVmBridgeError); }); it("non-string bodyBase64 raises UNMARSHAL_FAILED", { timeout: 5000, }, () => { expect(() => unmarshalResponse({ status: 200, headers: [], bodyBase64: 42 }) ).toThrow(SandboxVmBridgeError); }); it("header pairs reach the Response with duplicates preserved", { timeout: 5000, }, async () => { const r = unmarshalResponse({ status: 200, headers: [ ["set-cookie", "a=1"], ["set-cookie", "b=2"], ["content-type", "text/plain"], ], bodyBase64: encode([0x68, 0x69]), }); expect(r.headers.get("content-type")).toBe("text/plain"); // `Response.headers.get('set-cookie')` returns the // comma-joined value on platforms that combine; both values // should be present in the joined output. const cookie = r.headers.get("set-cookie") ?? ""; expect(cookie).toContain("a=1"); expect(cookie).toContain("b=2"); }); it("errorCode + errorMessage parse cleanly", { timeout: 5000, }, () => { const result = SerializedResponseSchema.safeParse({ status: 500, headers: [], bodyBase64: "", errorCode: "DEMO.FAILED", errorMessage: "boom", }); expect(result.success).toBe(true); }); it("schema accepts a valid shape", { timeout: 5000 }, () => { const r = SerializedResponseSchema.safeParse({ status: 200, headers: [], bodyBase64: "", }); expect(r.success).toBe(true); }); });