162 lines · 5.5 KB · TypeScript
RawDownload
import { randomBytes } from "node:crypto";import { createServer } from "node:http"; import { expect } from "@effect/vitest";import { Effect } from "effect";import { composePluginApi } from "@executor-js/api/server";import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api";import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared"; import { scenario } from "../src/scenario";import { Api, Target } from "../src/services"; const api = composePluginApi([openApiHttpPlugin()] as const); const serveRecordingUpstream = () =>  Effect.acquireRelease(    Effect.callback<{      readonly url: string;      readonly requests: () => readonly string[];      readonly close: () => void;    }>((resume) => {      const recorded: string[] = [];      const server = createServer((request, response) => {        recorded.push(request.url ?? "");        response.writeHead(200, { "content-type": "application/json" });        response.end(JSON.stringify({ id: "2", name: "Gadget" }));      });      server.listen(0, "127.0.0.1", () => {        const address = server.address();        const port = typeof address === "object" && address ? address.port : 0;        resume(          Effect.succeed({            url: `http://127.0.0.1:${port}`,            requests: () => [...recorded],            close: () => {              server.close();              server.closeAllConnections();            },          }),        );      });    }),    (server) => Effect.sync(server.close),  ); const itemsSpec = (baseUrl: string): string =>  JSON.stringify({    openapi: "3.0.3",    info: { title: "Items API", version: "1.0.0" },    servers: [{ url: baseUrl }],    paths: {      "/items/{itemId}": {        get: {          operationId: "getItem",          summary: "Fetch one item",          parameters: [{ name: "itemId", in: "path", required: true, schema: { type: "string" } }],          responses: { "200": { description: "the item" } },        },      },    },  }); const invokeByAddressCode = (address: string, args: unknown) => `const segments = ${JSON.stringify(address)}.split(".").slice(1);let node = tools;for (const segment of segments) node = node[segment];const result = await node(${JSON.stringify(args)});return JSON.stringify(result);`; type ToolEnvelope = {  readonly ok: boolean;  readonly error?: {    readonly code?: string;    readonly message?: string;  };}; scenario(  "OpenAPI: unknown tool arguments fail locally before any upstream request",  {},  Effect.scoped(    Effect.gen(function* () {      const target = yield* Target;      const { client: makeClient } = yield* Api;      const identity = yield* target.newIdentity();      const client = yield* makeClient(api, identity);      const upstream = yield* serveRecordingUpstream();      const slug = `openapi_unknown_args_${randomBytes(4).toString("hex")}`;       yield* Effect.ensuring(        Effect.gen(function* () {          yield* client.openapi.addSpec({            payload: {              spec: { kind: "blob", value: itemsSpec(upstream.url) },              slug,              baseUrl: upstream.url,              authenticationTemplate: [                {                  slug: "apiKey",                  type: "apiKey",                  headers: { authorization: ["Bearer ", { type: "variable", name: "token" }] },                },              ],            },          });          yield* client.connections.create({            payload: {              owner: "org",              name: ConnectionName.make("main"),              integration: IntegrationSlug.make(slug),              template: AuthTemplateSlug.make("apiKey"),              value: "tok_unknown_args",            },          });           const tools = yield* client.tools.list({ query: {} });          const address = tools            .filter((tool) => String(tool.integration) === slug)            .map((tool) => String(tool.address))            .find((candidate) => candidate.endsWith("getItem"));          expect(address, "the getItem operation is available").toBeDefined();           const executed = yield* client.executions.execute({            payload: {              code: invokeByAddressCode(address!, { itemId: "2", doesNotExist: "nope" }),              autoApprove: true,            },          });          expect(executed.status, "the sandbox returns the tool failure as a value").toBe(            "completed",          );          const outcome = JSON.parse(executed.text) as ToolEnvelope;          expect(outcome.ok, "the unknown argument fails the tool call").toBe(false);          expect(outcome.error?.code, "the failure is classified as invalid arguments").toBe(            "invalid_tool_arguments",          );          expect(            outcome.error?.message,            "the failure names the argument the caller must remove",          ).toContain("doesNotExist");          expect(            upstream.requests(),            "argument validation stops the call before upstream dispatch",          ).toEqual([]);        }),        Effect.gen(function* () {          yield* client.connections            .remove({              params: {                owner: "org",                integration: IntegrationSlug.make(slug),                name: ConnectionName.make("main"),              },            })            .pipe(Effect.ignore);          yield* client.openapi.removeSpec({ params: { slug } }).pipe(Effect.ignore);        }),      );    }),  ),);