// ---------------------------------------------------------------------------// Dispatch tests for non-JSON request bodies.//// Each case spins up an Effect HttpApi-backed test server, derives the// OpenAPI spec from that API, and asserts both the wire-level content type// and body shape the plugin actually sent.//// The scenarios mirror what real specs commonly carry — multipart uploads// (files + scalar fields), XML bodies declared as pre-serialized strings,// text/plain payloads, and raw octet-stream byte uploads.//// v2: tools are produced per-connection, so each case adds the integration via// `addSpec` AND creates a connection before executing the full tool address.// --------------------------------------------------------------------------- import { describe, expect, it } from "@effect/vitest";import { Effect, Exit, Schema } from "effect";import { FetchHttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema,} from "effect/unstable/httpapi"; import { createExecutor } from "@executor-js/sdk";import { makeTestConfig, memoryCredentialsPlugin } from "@executor-js/sdk/testing";import { addOpenApiTestConnection, serveOpenApiHttpApiTestServer,} from "@executor-js/plugin-openapi/testing"; import { openApiPlugin } from "./plugin"; const JsonNameBody = Schema.fromJsonString( Schema.Struct({ name: Schema.String, }),);const decodeJsonNameBody = Schema.decodeUnknownSync(JsonNameBody); const testPlugins = () => [openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), memoryCredentialsPlugin()] as const; type Captured = { contentType: string; body: Buffer;}; const Ok = Schema.Struct({ ok: Schema.Boolean }); const startEchoServer = (options: { readonly name?: string; readonly path?: `/${string}`; readonly payload: Schema.Top | readonly Schema.Top[]; readonly transformSpec?: (spec: Record<string, unknown>) => Record<string, unknown>;}) => Effect.gen(function* () { const captured: Captured = { contentType: "", body: Buffer.alloc(0) }; const endpointName = options.name ?? "submit"; const path = options.path ?? "/submit"; const group = HttpApiGroup.make("body").add( HttpApiEndpoint.post(endpointName, path, { payload: options.payload, success: Ok, }), ); const api = HttpApi.make(`bodyTest_${endpointName}`).add(group); const handlersLayer = HttpApiBuilder.group(api, "body", (handlers) => handlers.handleRaw(endpointName, () => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; captured.contentType = request.headers["content-type"] ?? ""; const body = yield* request.arrayBuffer.pipe( Effect.catch(() => Effect.succeed(new ArrayBuffer(0))), ); captured.body = Buffer.from(body); return HttpServerResponse.jsonUnsafe({ ok: true }); }), ), ); const server = yield* serveOpenApiHttpApiTestServer({ api, handlersLayer, transformSpec: options.transformSpec, }); return { server, captured }; }); const ObjectBody = Schema.Struct({ name: Schema.optional(Schema.String), flag: Schema.optional(Schema.Boolean), count: Schema.optional(Schema.Number),}); const JsonNameObject = Schema.Struct({ name: Schema.String }); const contentFor = (contentType: string) => ({ [contentType]: { schema: { type: "object" }, },}); const replaceResponseContent = (path: string, operation: string, content: Record<string, unknown>) => (spec: Record<string, unknown>): Record<string, unknown> => { const paths = { ...(spec.paths as Record<string, unknown>) }; const pathItem = { ...(paths[path] as Record<string, unknown>) }; const operationSpec = { ...(pathItem[operation] as Record<string, unknown>) }; const responses = { ...(operationSpec.responses as Record<string, unknown>) }; const ok = { ...(responses["200"] as Record<string, unknown>) }; responses["200"] = { ...ok, content }; pathItem[operation] = { ...operationSpec, responses }; paths[path] = pathItem; return { ...spec, paths }; }; const replaceOperationServers = (path: string, operation: string, servers: readonly Record<string, unknown>[]) => (spec: Record<string, unknown>): Record<string, unknown> => { const paths = { ...(spec.paths as Record<string, unknown>) }; const pathItem = { ...(paths[path] as Record<string, unknown>) }; const operationSpec = { ...(pathItem[operation] as Record<string, unknown>) }; pathItem[operation] = { ...operationSpec, servers }; paths[path] = pathItem; return { ...spec, paths }; }; const replaceRequestBodyContent = ( path: string, operation: string, content: Record<string, unknown>, encoding?: Record<string, unknown>, ) => (spec: Record<string, unknown>): Record<string, unknown> => { const paths = { ...(spec.paths as Record<string, unknown>) }; const pathItem = { ...(paths[path] as Record<string, unknown>) }; const operationSpec = { ...(pathItem[operation] as Record<string, unknown>) }; const requestBody = { ...(operationSpec.requestBody as Record<string, unknown>) }; pathItem[operation] = { ...operationSpec, requestBody: { ...requestBody, content: encoding ? Object.fromEntries( Object.entries(content).map(([key, value]) => [ key, { ...(value as Record<string, unknown>), encoding }, ]), ) : content, }, }; paths[path] = pathItem; return { ...spec, paths }; }; describe("OpenAPI non-JSON request body dispatch", () => { it.effect("multipart/form-data: object body is encoded as real multipart", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: ObjectBody.pipe(HttpApiSchema.asMultipart()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "mp" }); yield* executor.execute(conn.address("body.submit"), { body: { name: "Acme", flag: true, count: 7 }, }); expect(captured.contentType).toMatch(/^multipart\/form-data; boundary=/); const body = captured.body.toString("utf8"); expect(body).toContain('name="name"'); expect(body).toContain("Acme"); expect(body).toContain('name="flag"'); expect(body).toContain("true"); expect(body).toContain('name="count"'); expect(body).toContain("7"); // Regression guard: never ship [object Object] over multipart. expect(body).not.toContain("[object Object]"); }), ); it.effect("application/xml: string body passes through with xml content-type", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.String.pipe(HttpApiSchema.asText({ contentType: "application/xml" })), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "xml" }); const xml = '<?xml version="1.0"?><root><name>Acme</name></root>'; yield* executor.execute(conn.address("body.submit"), { body: xml }); expect(captured.contentType).toBe("application/xml"); expect(captured.body.toString("utf8")).toBe(xml); }), ); it.effect("text/xml: object body is JSON-stringified (never '[object Object]')", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: JsonNameObject, transformSpec: replaceRequestBodyContent("/submit", "post", contentFor("text/xml")), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "tx" }); yield* executor.execute(conn.address("body.submit"), { body: { name: "Acme" } }); expect(captured.contentType).toBe("text/xml"); const body = captured.body.toString("utf8"); expect(body).not.toBe("[object Object]"); expect(decodeJsonNameBody(body)).toEqual({ name: "Acme" }); }), ); it.effect("text/plain: string body passes through with text/plain", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.String.pipe(HttpApiSchema.asText()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "tp" }); yield* executor.execute(conn.address("body.submit"), { body: "hello, world" }); expect(captured.contentType).toBe("text/plain"); expect(captured.body.toString("utf8")).toBe("hello, world"); }), ); it.effect("application/octet-stream: Uint8Array passes through as bytes", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin" }); const payload = new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0x00, 0x01, 0x02]); yield* executor.execute(conn.address("body.submit"), { body: payload }); expect(captured.contentType).toBe("application/octet-stream"); expect(captured.body.length).toBe(payload.length); expect(Array.from(captured.body)).toEqual(Array.from(payload)); }), ); it.effect("application/octet-stream: bodyBase64 passes through as bytes", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_b64" }); yield* executor.execute(conn.address("body.submit"), { bodyBase64: "3q2+7w==", }); expect(captured.contentType).toBe("application/octet-stream"); expect(Array.from(captured.body)).toEqual([0xde, 0xad, 0xbe, 0xef]); }), ); it.effect("application/octet-stream: invalid bodyBase64 fails before dispatch", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_b64_bad" }); const exit = yield* executor .execute(conn.address("body.submit"), { bodyBase64: "@@", }) .pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: invalid nested bodyBase64 fails before dispatch", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_nested_b64_bad", }); const exit = yield* executor .execute(conn.address("body.submit"), { body: { bodyBase64: "@@" }, }) .pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: required body fails before dispatch when missing", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_b64_missing", }); const exit = yield* executor.execute(conn.address("body.submit"), {}).pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: object body fails before dispatch", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_object_body", }); const exit = yield* executor .execute(conn.address("body.submit"), { body: { name: "photo.png" }, }) .pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: string body passes through as UTF-8 bytes", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_string_body", }); // A plain string `body` is the long-standing way callers upload text // content to an octet-stream endpoint, so it must reach the wire rather // than failing. It is sent verbatim as UTF-8 bytes (not base64-decoded), // which preserves the pre-bodyBase64 behavior; binary uploads still use // `bodyBase64`. const text = "plain file contents, not base64"; yield* executor.execute(conn.address("body.submit"), { body: text }); expect(captured.contentType).toBe("application/octet-stream"); expect(captured.body.toString("utf8")).toBe(text); }), ); it.effect( "format: byte response: Gmail-style image attachment data is exposed as a file artifact", () => Effect.gen(function* () { const attachmentBytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10]); const attachmentBase64Url = "_9j_4AAQ"; const MessagePartBody = Schema.Struct({ data: Schema.String, size: Schema.Number, }); const group = HttpApiGroup.make("gmail").add( HttpApiEndpoint.get("getAttachment", "/attachments/:id", { success: MessagePartBody, }), ); const api = HttpApi.make("gmailAttachmentTest").add(group); const handlersLayer = HttpApiBuilder.group(api, "gmail", (handlers) => handlers.handleRaw("getAttachment", () => Effect.succeed( HttpServerResponse.jsonUnsafe({ data: attachmentBase64Url, size: attachmentBytes.byteLength, }), ), ), ); const server = yield* serveOpenApiHttpApiTestServer({ api, handlersLayer, transformSpec: replaceResponseContent("/attachments/{id}", "get", { "application/json": { schema: { type: "object", properties: { data: { type: "string", format: "byte", description: "The body data as a base64url encoded string.", }, size: { type: "integer", format: "int32", description: "Number of bytes for the message part data.", }, }, required: ["data", "size"], }, }, }), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "gmail" }); const schema = yield* executor.tools.schema(conn.address("gmail.getAttachment")); expect(schema?.outputSchema).toMatchObject({ properties: { _tag: { enum: ["ToolFile"] }, data: { contentEncoding: "base64" }, }, }); const result = yield* executor.execute(conn.address("gmail.getAttachment"), { id: "att_1", }); expect(result).toMatchObject({ ok: true, data: { _tag: "ToolFile", encoding: "base64", mimeType: "image/jpeg", data: "/9j/4AAQ", byteLength: attachmentBytes.byteLength, }, }); }), ); it.effect("format: byte response: UTF-8 attachment data falls back to text/plain", () => Effect.gen(function* () { const attachmentText = [ "id,name,status,amount", "1,Ada,confirmed,42.50", "2,Grace,pending,13.75", "3,Linus,cancelled,0.00", "", ].join("\n"); const attachmentBase64Url = "aWQsbmFtZSxzdGF0dXMsYW1vdW50CjEsQWRhLGNvbmZpcm1lZCw0Mi41MAoyLEdyYWNlLHBlbmRpbmcsMTMuNzUKMyxMaW51cyxjYW5jZWxsZWQsMC4wMAo"; const MessagePartBody = Schema.Struct({ data: Schema.String, size: Schema.Number, }); const group = HttpApiGroup.make("gmailText").add( HttpApiEndpoint.get("getAttachment", "/attachments/:id", { success: MessagePartBody, }), ); const api = HttpApi.make("gmailTextAttachmentTest").add(group); const handlersLayer = HttpApiBuilder.group(api, "gmailText", (handlers) => handlers.handleRaw("getAttachment", () => Effect.succeed( HttpServerResponse.jsonUnsafe({ data: attachmentBase64Url,Lines 1–500 of 1175. Load more
// --------------------------------------------------------------------------- // Dispatch tests for non-JSON request bodies. // // Each case spins up an Effect HttpApi-backed test server, derives the // OpenAPI spec from that API, and asserts both the wire-level content type // and body shape the plugin actually sent. // // The scenarios mirror what real specs commonly carry — multipart uploads // (files + scalar fields), XML bodies declared as pre-serialized strings, // text/plain payloads, and raw octet-stream byte uploads. // // v2: tools are produced per-connection, so each case adds the integration via // `addSpec` AND creates a connection before executing the full tool address. // --------------------------------------------------------------------------- import { describe, expect, it } from "@effect/vitest"; import { Effect, Exit, Schema } from "effect"; import { FetchHttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, } from "effect/unstable/httpapi"; import { createExecutor } from "@executor-js/sdk"; import { makeTestConfig, memoryCredentialsPlugin } from "@executor-js/sdk/testing"; import { addOpenApiTestConnection, serveOpenApiHttpApiTestServer, } from "@executor-js/plugin-openapi/testing"; import { openApiPlugin } from "./plugin"; const JsonNameBody = Schema.fromJsonString( Schema.Struct({ name: Schema.String, }), ); const decodeJsonNameBody = Schema.decodeUnknownSync(JsonNameBody); const testPlugins = () => [openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), memoryCredentialsPlugin()] as const; type Captured = { contentType: string; body: Buffer; }; const Ok = Schema.Struct({ ok: Schema.Boolean }); const startEchoServer = (options: { readonly name?: string; readonly path?: `/${string}`; readonly payload: Schema.Top | readonly Schema.Top[]; readonly transformSpec?: (spec: Record<string, unknown>) => Record<string, unknown>; }) => Effect.gen(function* () { const captured: Captured = { contentType: "", body: Buffer.alloc(0) }; const endpointName = options.name ?? "submit"; const path = options.path ?? "/submit"; const group = HttpApiGroup.make("body").add( HttpApiEndpoint.post(endpointName, path, { payload: options.payload, success: Ok, }), ); const api = HttpApi.make(`bodyTest_${endpointName}`).add(group); const handlersLayer = HttpApiBuilder.group(api, "body", (handlers) => handlers.handleRaw(endpointName, () => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; captured.contentType = request.headers["content-type"] ?? ""; const body = yield* request.arrayBuffer.pipe( Effect.catch(() => Effect.succeed(new ArrayBuffer(0))), ); captured.body = Buffer.from(body); return HttpServerResponse.jsonUnsafe({ ok: true }); }), ), ); const server = yield* serveOpenApiHttpApiTestServer({ api, handlersLayer, transformSpec: options.transformSpec, }); return { server, captured }; }); const ObjectBody = Schema.Struct({ name: Schema.optional(Schema.String), flag: Schema.optional(Schema.Boolean), count: Schema.optional(Schema.Number), }); const JsonNameObject = Schema.Struct({ name: Schema.String }); const contentFor = (contentType: string) => ({ [contentType]: { schema: { type: "object" }, }, }); const replaceResponseContent = (path: string, operation: string, content: Record<string, unknown>) => (spec: Record<string, unknown>): Record<string, unknown> => { const paths = { ...(spec.paths as Record<string, unknown>) }; const pathItem = { ...(paths[path] as Record<string, unknown>) }; const operationSpec = { ...(pathItem[operation] as Record<string, unknown>) }; const responses = { ...(operationSpec.responses as Record<string, unknown>) }; const ok = { ...(responses["200"] as Record<string, unknown>) }; responses["200"] = { ...ok, content }; pathItem[operation] = { ...operationSpec, responses }; paths[path] = pathItem; return { ...spec, paths }; }; const replaceOperationServers = (path: string, operation: string, servers: readonly Record<string, unknown>[]) => (spec: Record<string, unknown>): Record<string, unknown> => { const paths = { ...(spec.paths as Record<string, unknown>) }; const pathItem = { ...(paths[path] as Record<string, unknown>) }; const operationSpec = { ...(pathItem[operation] as Record<string, unknown>) }; pathItem[operation] = { ...operationSpec, servers }; paths[path] = pathItem; return { ...spec, paths }; }; const replaceRequestBodyContent = ( path: string, operation: string, content: Record<string, unknown>, encoding?: Record<string, unknown>, ) => (spec: Record<string, unknown>): Record<string, unknown> => { const paths = { ...(spec.paths as Record<string, unknown>) }; const pathItem = { ...(paths[path] as Record<string, unknown>) }; const operationSpec = { ...(pathItem[operation] as Record<string, unknown>) }; const requestBody = { ...(operationSpec.requestBody as Record<string, unknown>) }; pathItem[operation] = { ...operationSpec, requestBody: { ...requestBody, content: encoding ? Object.fromEntries( Object.entries(content).map(([key, value]) => [ key, { ...(value as Record<string, unknown>), encoding }, ]), ) : content, }, }; paths[path] = pathItem; return { ...spec, paths }; }; describe("OpenAPI non-JSON request body dispatch", () => { it.effect("multipart/form-data: object body is encoded as real multipart", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: ObjectBody.pipe(HttpApiSchema.asMultipart()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "mp" }); yield* executor.execute(conn.address("body.submit"), { body: { name: "Acme", flag: true, count: 7 }, }); expect(captured.contentType).toMatch(/^multipart\/form-data; boundary=/); const body = captured.body.toString("utf8"); expect(body).toContain('name="name"'); expect(body).toContain("Acme"); expect(body).toContain('name="flag"'); expect(body).toContain("true"); expect(body).toContain('name="count"'); expect(body).toContain("7"); // Regression guard: never ship [object Object] over multipart. expect(body).not.toContain("[object Object]"); }), ); it.effect("application/xml: string body passes through with xml content-type", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.String.pipe(HttpApiSchema.asText({ contentType: "application/xml" })), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "xml" }); const xml = '<?xml version="1.0"?><root><name>Acme</name></root>'; yield* executor.execute(conn.address("body.submit"), { body: xml }); expect(captured.contentType).toBe("application/xml"); expect(captured.body.toString("utf8")).toBe(xml); }), ); it.effect("text/xml: object body is JSON-stringified (never '[object Object]')", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: JsonNameObject, transformSpec: replaceRequestBodyContent("/submit", "post", contentFor("text/xml")), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "tx" }); yield* executor.execute(conn.address("body.submit"), { body: { name: "Acme" } }); expect(captured.contentType).toBe("text/xml"); const body = captured.body.toString("utf8"); expect(body).not.toBe("[object Object]"); expect(decodeJsonNameBody(body)).toEqual({ name: "Acme" }); }), ); it.effect("text/plain: string body passes through with text/plain", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.String.pipe(HttpApiSchema.asText()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "tp" }); yield* executor.execute(conn.address("body.submit"), { body: "hello, world" }); expect(captured.contentType).toBe("text/plain"); expect(captured.body.toString("utf8")).toBe("hello, world"); }), ); it.effect("application/octet-stream: Uint8Array passes through as bytes", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin" }); const payload = new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0x00, 0x01, 0x02]); yield* executor.execute(conn.address("body.submit"), { body: payload }); expect(captured.contentType).toBe("application/octet-stream"); expect(captured.body.length).toBe(payload.length); expect(Array.from(captured.body)).toEqual(Array.from(payload)); }), ); it.effect("application/octet-stream: bodyBase64 passes through as bytes", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_b64" }); yield* executor.execute(conn.address("body.submit"), { bodyBase64: "3q2+7w==", }); expect(captured.contentType).toBe("application/octet-stream"); expect(Array.from(captured.body)).toEqual([0xde, 0xad, 0xbe, 0xef]); }), ); it.effect("application/octet-stream: invalid bodyBase64 fails before dispatch", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_b64_bad" }); const exit = yield* executor .execute(conn.address("body.submit"), { bodyBase64: "@@", }) .pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: invalid nested bodyBase64 fails before dispatch", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_nested_b64_bad", }); const exit = yield* executor .execute(conn.address("body.submit"), { body: { bodyBase64: "@@" }, }) .pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: required body fails before dispatch when missing", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_b64_missing", }); const exit = yield* executor.execute(conn.address("body.submit"), {}).pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: object body fails before dispatch", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_object_body", }); const exit = yield* executor .execute(conn.address("body.submit"), { body: { name: "photo.png" }, }) .pipe(Effect.exit); expect(Exit.isFailure(exit)).toBe(true); expect(captured.contentType).toBe(""); expect(captured.body.length).toBe(0); }), ); it.effect("application/octet-stream: string body passes through as UTF-8 bytes", () => Effect.gen(function* () { const { server, captured } = yield* startEchoServer({ payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "bin_string_body", }); // A plain string `body` is the long-standing way callers upload text // content to an octet-stream endpoint, so it must reach the wire rather // than failing. It is sent verbatim as UTF-8 bytes (not base64-decoded), // which preserves the pre-bodyBase64 behavior; binary uploads still use // `bodyBase64`. const text = "plain file contents, not base64"; yield* executor.execute(conn.address("body.submit"), { body: text }); expect(captured.contentType).toBe("application/octet-stream"); expect(captured.body.toString("utf8")).toBe(text); }), ); it.effect( "format: byte response: Gmail-style image attachment data is exposed as a file artifact", () => Effect.gen(function* () { const attachmentBytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10]); const attachmentBase64Url = "_9j_4AAQ"; const MessagePartBody = Schema.Struct({ data: Schema.String, size: Schema.Number, }); const group = HttpApiGroup.make("gmail").add( HttpApiEndpoint.get("getAttachment", "/attachments/:id", { success: MessagePartBody, }), ); const api = HttpApi.make("gmailAttachmentTest").add(group); const handlersLayer = HttpApiBuilder.group(api, "gmail", (handlers) => handlers.handleRaw("getAttachment", () => Effect.succeed( HttpServerResponse.jsonUnsafe({ data: attachmentBase64Url, size: attachmentBytes.byteLength, }), ), ), ); const server = yield* serveOpenApiHttpApiTestServer({ api, handlersLayer, transformSpec: replaceResponseContent("/attachments/{id}", "get", { "application/json": { schema: { type: "object", properties: { data: { type: "string", format: "byte", description: "The body data as a base64url encoded string.", }, size: { type: "integer", format: "int32", description: "Number of bytes for the message part data.", }, }, required: ["data", "size"], }, }, }), }); const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() })); const conn = yield* addOpenApiTestConnection(executor, server, { slug: "gmail" }); const schema = yield* executor.tools.schema(conn.address("gmail.getAttachment")); expect(schema?.outputSchema).toMatchObject({ properties: { _tag: { enum: ["ToolFile"] }, data: { contentEncoding: "base64" }, }, }); const result = yield* executor.execute(conn.address("gmail.getAttachment"), { id: "att_1", }); expect(result).toMatchObject({ ok: true, data: { _tag: "ToolFile", encoding: "base64", mimeType: "image/jpeg", data: "/9j/4AAQ", byteLength: attachmentBytes.byteLength, }, }); }), ); it.effect("format: byte response: UTF-8 attachment data falls back to text/plain", () => Effect.gen(function* () { const attachmentText = [ "id,name,status,amount", "1,Ada,confirmed,42.50", "2,Grace,pending,13.75", "3,Linus,cancelled,0.00", "", ].join("\n"); const attachmentBase64Url = "aWQsbmFtZSxzdGF0dXMsYW1vdW50CjEsQWRhLGNvbmZpcm1lZCw0Mi41MAoyLEdyYWNlLHBlbmRpbmcsMTMuNzUKMyxMaW51cyxjYW5jZWxsZWQsMC4wMAo"; const MessagePartBody = Schema.Struct({ data: Schema.String, size: Schema.Number, }); const group = HttpApiGroup.make("gmailText").add( HttpApiEndpoint.get("getAttachment", "/attachments/:id", { success: MessagePartBody, }), ); const api = HttpApi.make("gmailTextAttachmentTest").add(group); const handlersLayer = HttpApiBuilder.group(api, "gmailText", (handlers) => handlers.handleRaw("getAttachment", () => Effect.succeed( HttpServerResponse.jsonUnsafe({ data: attachmentBase64Url,