import { describe, expect, it } from "@effect/vitest";import { Effect, Predicate, Schema } from "effect";import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";import { FetchHttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { createExecutor, definePlugin, type FumaDb, RemoveSecretInput, Scope, ScopeId, SecretId, SetSecretInput, SetSourceCredentialBindingInput, RemoveSourceCredentialBindingInput, type InvokeOptions, type SecretProvider,} from "@executor-js/sdk";import { makeTestConfig, memorySecretsPlugin } from "@executor-js/sdk/testing";import type { ConfigFileSink } from "@executor-js/config"; const TEST_SCOPE = "test-scope";import { openApiPlugin } from "./plugin";import { ConfiguredHeaderBinding, OAuth2SourceConfig } from "./types";import { addOpenApiTestSource, makeOpenApiHttpApiTestSourceConfig, serveOpenApiHttpApiTestServer, unwrapInvocation,} from "../testing"; const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; type FumaQueryCall = { readonly method: "findFirst" | "findMany"; readonly table: string;}; const recordFumaQueries = (db: FumaDb, calls: FumaQueryCall[]): FumaDb => new Proxy(db, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); if (prop !== "findFirst" && prop !== "findMany") return value; return (table: string, ...args: readonly unknown[]) => { calls.push({ method: prop, table }); return (value as (tableName: string, ...innerArgs: readonly unknown[]) => unknown).call( target, table, ...args, ); }; }, }); // ---------------------------------------------------------------------------// Define a test API with Effect HttpApi// --------------------------------------------------------------------------- const Item = Schema.Struct({ id: Schema.Number, name: Schema.String,});type Item = typeof Item.Type; const EchoHeaders = Schema.Struct({ authorization: Schema.optional(Schema.String), "x-static": Schema.optional(Schema.String),});type EchoHeaders = typeof EchoHeaders.Type; class QueryValidationError extends Schema.TaggedErrorClass<QueryValidationError>()( "QueryValidationError", { message: Schema.String, },) {} const ItemsGroup = HttpApiGroup.make("items") .add(HttpApiEndpoint.get("listItems", "/items", { success: Schema.Array(Item) })) .add( HttpApiEndpoint.get("getItem", "/items/:itemId", { params: Schema.Struct({ itemId: Schema.NumberFromString }), success: Item, }), ) .add( HttpApiEndpoint.get("echoHeaders", "/echo-headers", { success: EchoHeaders, }), ) .add( HttpApiEndpoint.get("queryRows", "/records/rows/:entryTypeId", { params: Schema.Struct({ entryTypeId: Schema.String }), success: Schema.Unknown, error: QueryValidationError, }), ); const TestApi = HttpApi.make("testApi").add(ItemsGroup); type TestApiSourceOptions = Omit< Parameters<typeof makeOpenApiHttpApiTestSourceConfig>[1], "scope"> & { readonly scope?: string;}; const testApiSourceConfig = (options: TestApiSourceOptions = {}) => makeOpenApiHttpApiTestSourceConfig(TestApi, { scope: TEST_SCOPE, ...options, }); const testApiSpec = () => { const spec = testApiSourceConfig().spec; return spec.kind === "blob" ? spec.value : spec.url;}; // ---------------------------------------------------------------------------// Implement handlers// --------------------------------------------------------------------------- const ITEMS = [ { id: 1, name: "Widget" }, { id: 2, name: "Gadget" }, { id: 3, name: "Doohickey" },]; const ItemsGroupLive = HttpApiBuilder.group(TestApi, "items", (handlers) => handlers .handle("listItems", () => Effect.succeed(ITEMS.map((item) => Item.make(item)))) .handle("getItem", (req) => Effect.succeed( Item.make( ITEMS.find((i) => i.id === req.params.itemId) ?? { id: 0, name: "Unknown", }, ), ), ) .handle("echoHeaders", () => Effect.gen(function* () { const req = yield* HttpServerRequest.HttpServerRequest; return EchoHeaders.make({ authorization: req.headers["authorization"], "x-static": req.headers["x-static"], }); }), ) .handle("queryRows", () => Effect.fail( new QueryValidationError({ message: 'Field with name "DisplayName" does not exist', }), ), ),); const servePluginTestApi = () => serveOpenApiHttpApiTestServer({ api: TestApi, handlersLayer: ItemsGroupLive, }); const serveSpecRequiringHeader = () => { const state = { requests: 0, lastToken: null as string | null }; return serveOpenApiHttpApiTestServer({ api: TestApi, handlersLayer: ItemsGroupLive, transformSpec: (spec) => { const { servers: _servers, ...rest } = spec; return rest; }, guardSpecRequest: (request) => Effect.sync(() => { state.requests++; state.lastToken = request.headers["x-spec-token"] ?? null; if (state.requests === 1) { return null; } if (state.lastToken !== "org-token") { return HttpServerResponse.jsonUnsafe({ error: "missing token" }, { status: 401 }); } return null; }), }).pipe( Effect.map((server) => ({ specUrl: server.specUrl, requestCount: () => state.requests, lastToken: () => state.lastToken, })), );}; // ---------------------------------------------------------------------------// Tests// --------------------------------------------------------------------------- describe("OpenAPI Plugin", () => { it.effect("previewSpec returns metadata and header presets", () => Effect.scoped( Effect.gen(function* () { const server = yield* servePluginTestApi(); const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: server.httpClientLayer }), memorySecretsPlugin(), ] as const, }), ); const preview = yield* executor.openapi.previewSpec(server.specJson); expect(preview.operationCount).toBeGreaterThanOrEqual(2); expect(preview.servers).toBeDefined(); }), ), ); it.effect("registers static openapi executor tools", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const tools = yield* executor.tools.list(); const ids = tools.map((t) => t.id); expect(ids).toContain("executor.openapi.previewSpec"); expect(ids).toContain("executor.openapi.getSource"); expect(ids).toContain("executor.openapi.addSource"); expect(ids).toContain("executor.openapi.configureSource"); }), ); it.effect("lists executor as the static runtime source", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const sources = yield* executor.sources.list(); expect(sources.find((s) => s.id === "openapi")).toBeUndefined(); const control = sources.find((s) => s.id === "executor"); expect(control).toBeDefined(); expect(control!.runtime).toBe(true); expect(control!.canRemove).toBe(false); }), ); it.effect("invokes static previewSpec through executor.tools.invoke", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const preview = unwrapInvocation( yield* executor.tools.invoke( "executor.openapi.previewSpec", { spec: testApiSpec() }, autoApprove, ), ).data as { operationCount: number; operations?: unknown }; expect(preview.operationCount).toBeGreaterThanOrEqual(2); expect(preview.operations).toBeUndefined(); }), ); it.effect("describes static previewSpec output from Standard Schema", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [openApiPlugin(), memorySecretsPlugin()] as const, }), ); const schema = yield* executor.tools.schema("executor.openapi.previewSpec"); expect(schema).not.toBeNull(); expect(schema!.outputTypeScript).toContain("servers:"); expect(schema!.outputTypeScript).toContain("securitySchemes:"); expect(schema!.outputTypeScript).toContain("oauth2Presets:"); expect(schema!.outputTypeScript).toContain("title: string | null"); expect(schema!.outputTypeScript).not.toContain("operations:"); expect(schema!.outputTypeScript).not.toContain("_tag"); }), ); it.effect("describes static addSource parameters from Standard Schema", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [openApiPlugin(), memorySecretsPlugin()] as const, }), ); const schema = yield* executor.tools.schema("executor.openapi.addSource"); expect(schema).not.toBeNull(); expect(schema!.inputTypeScript).not.toContain("scope: string"); expect(schema!.inputTypeScript).toContain('kind: "url"'); expect( (schema!.inputSchema as { properties?: Record<string, unknown> }).properties, ).not.toHaveProperty("credentialTargetScope"); expect(schema!.inputTypeScript).not.toBe("Record<string, unknown>"); }), ); it.effect("invokes static addSource through executor.tools.invoke", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const userScope = ScopeId.make("static-user"); const orgScope = ScopeId.make("static-org"); const executor = yield* createExecutor( makeTestConfig({ scopes: [ Scope.make({ id: userScope, name: "user", createdAt: new Date() }), Scope.make({ id: orgScope, name: "org", createdAt: new Date() }), ], plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const result = unwrapInvocation( yield* executor.tools.invoke( "executor.openapi.addSource", (({ scope: _scope, ...input }) => input)( testApiSourceConfig({ scope: "org", namespace: "runtime" }), ), autoApprove, ), ).data as { sourceId: string; source: { id: string; scope: string }; toolCount: number }; expect(result).toEqual({ sourceId: "runtime", source: { id: "runtime", scope: String(orgScope) }, toolCount: 4, }); expect(yield* executor.openapi.getSource("runtime", String(userScope))).toBeNull(); expect((yield* executor.openapi.getSource("runtime", String(orgScope)))?.scope).toBe( orgScope, ); const inspected = unwrapInvocation( yield* executor.tools.invoke( "executor.openapi.getSource", { namespace: "runtime", scope: "org" }, autoApprove, ), ).data as { source: { namespace: string; scope: string } | null }; expect(inspected.source).toMatchObject({ namespace: "runtime", scope: String(orgScope) }); expect((yield* executor.tools.list()).map((t) => t.id)).toContain("runtime.items.listItems"); }), ); it.effect("static previewSpec returns actionable tool failures", () => Effect.gen(function* () { const config = makeTestConfig({ plugins: [openApiPlugin()] as const }); const executor = yield* createExecutor(config); const result = yield* executor.tools.invoke("executor.openapi.previewSpec", { spec: "not openapi", }); expect(result).toMatchObject({ ok: false, error: { code: "openapi_parse_failed", }, }); yield* executor.close(); yield* Effect.promise(() => config.testDb.close()); }), ); it.effect("requires approval before adding a source through the runtime tool", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [openApiPlugin()] as const }), ); const declined = yield* executor.tools .invoke( "executor.openapi.addSource", (({ scope: _scope, ...input }) => input)( testApiSourceConfig({ namespace: "runtime_declined" }), ), { onElicitation: () => Effect.succeed({ action: "decline" as const }), }, ) .pipe(Effect.flip); expect(Predicate.isTagged(declined, "ElicitationDeclinedError")).toBe(true); expect(yield* executor.openapi.getSource("runtime_declined", TEST_SCOPE)).toBeNull(); expect((yield* executor.tools.list()).map((t) => t.id)).not.toContain( "runtime_declined.items.listItems", ); }), ); it.effect("adds an org source whose direct credentials are owned by the user scope", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const userScope = ScopeId.make("openapi-user"); const orgScope = ScopeId.make("openapi-org"); const executor = yield* createExecutor( makeTestConfig({ scopes: [ Scope.make({ id: userScope, name: "user", createdAt: new Date(), }), Scope.make({ id: orgScope, name: "org", createdAt: new Date() }), ], plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); yield* executor.secrets.set( SetSecretInput.make({ id: SecretId.make("user-query-token"), scope: userScope, name: "User query token", value: "user-token", }), ); const input = testApiSourceConfig({ scope: String(orgScope), namespace: "org_direct_user_credential", queryParams: { token: { kind: "secret" } }, }); yield* executor.openapi.addSpec(input); const bindings = yield* executor.sources.listBindings({ source: { id: "org_direct_user_credential", scope: orgScope }, }); expect(bindings).toEqual([]); }), ); it.effect("sources.configure removes bindings for credential slots no longer present", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); yield* executor.secrets.set( SetSecretInput.make({ id: SecretId.make("old-token"), scope: ScopeId.make(TEST_SCOPE), name: "Old token", value: "old-secret", }), ); yield* executor.openapi.addSpec(Lines 1–500 of 1607. Load more
import { describe, expect, it } from "@effect/vitest"; import { Effect, Predicate, Schema } from "effect"; import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; import { FetchHttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { createExecutor, definePlugin, type FumaDb, RemoveSecretInput, Scope, ScopeId, SecretId, SetSecretInput, SetSourceCredentialBindingInput, RemoveSourceCredentialBindingInput, type InvokeOptions, type SecretProvider, } from "@executor-js/sdk"; import { makeTestConfig, memorySecretsPlugin } from "@executor-js/sdk/testing"; import type { ConfigFileSink } from "@executor-js/config"; const TEST_SCOPE = "test-scope"; import { openApiPlugin } from "./plugin"; import { ConfiguredHeaderBinding, OAuth2SourceConfig } from "./types"; import { addOpenApiTestSource, makeOpenApiHttpApiTestSourceConfig, serveOpenApiHttpApiTestServer, unwrapInvocation, } from "../testing"; const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; type FumaQueryCall = { readonly method: "findFirst" | "findMany"; readonly table: string; }; const recordFumaQueries = (db: FumaDb, calls: FumaQueryCall[]): FumaDb => new Proxy(db, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); if (prop !== "findFirst" && prop !== "findMany") return value; return (table: string, ...args: readonly unknown[]) => { calls.push({ method: prop, table }); return (value as (tableName: string, ...innerArgs: readonly unknown[]) => unknown).call( target, table, ...args, ); }; }, }); // --------------------------------------------------------------------------- // Define a test API with Effect HttpApi // --------------------------------------------------------------------------- const Item = Schema.Struct({ id: Schema.Number, name: Schema.String, }); type Item = typeof Item.Type; const EchoHeaders = Schema.Struct({ authorization: Schema.optional(Schema.String), "x-static": Schema.optional(Schema.String), }); type EchoHeaders = typeof EchoHeaders.Type; class QueryValidationError extends Schema.TaggedErrorClass<QueryValidationError>()( "QueryValidationError", { message: Schema.String, }, ) {} const ItemsGroup = HttpApiGroup.make("items") .add(HttpApiEndpoint.get("listItems", "/items", { success: Schema.Array(Item) })) .add( HttpApiEndpoint.get("getItem", "/items/:itemId", { params: Schema.Struct({ itemId: Schema.NumberFromString }), success: Item, }), ) .add( HttpApiEndpoint.get("echoHeaders", "/echo-headers", { success: EchoHeaders, }), ) .add( HttpApiEndpoint.get("queryRows", "/records/rows/:entryTypeId", { params: Schema.Struct({ entryTypeId: Schema.String }), success: Schema.Unknown, error: QueryValidationError, }), ); const TestApi = HttpApi.make("testApi").add(ItemsGroup); type TestApiSourceOptions = Omit< Parameters<typeof makeOpenApiHttpApiTestSourceConfig>[1], "scope" > & { readonly scope?: string; }; const testApiSourceConfig = (options: TestApiSourceOptions = {}) => makeOpenApiHttpApiTestSourceConfig(TestApi, { scope: TEST_SCOPE, ...options, }); const testApiSpec = () => { const spec = testApiSourceConfig().spec; return spec.kind === "blob" ? spec.value : spec.url; }; // --------------------------------------------------------------------------- // Implement handlers // --------------------------------------------------------------------------- const ITEMS = [ { id: 1, name: "Widget" }, { id: 2, name: "Gadget" }, { id: 3, name: "Doohickey" }, ]; const ItemsGroupLive = HttpApiBuilder.group(TestApi, "items", (handlers) => handlers .handle("listItems", () => Effect.succeed(ITEMS.map((item) => Item.make(item)))) .handle("getItem", (req) => Effect.succeed( Item.make( ITEMS.find((i) => i.id === req.params.itemId) ?? { id: 0, name: "Unknown", }, ), ), ) .handle("echoHeaders", () => Effect.gen(function* () { const req = yield* HttpServerRequest.HttpServerRequest; return EchoHeaders.make({ authorization: req.headers["authorization"], "x-static": req.headers["x-static"], }); }), ) .handle("queryRows", () => Effect.fail( new QueryValidationError({ message: 'Field with name "DisplayName" does not exist', }), ), ), ); const servePluginTestApi = () => serveOpenApiHttpApiTestServer({ api: TestApi, handlersLayer: ItemsGroupLive, }); const serveSpecRequiringHeader = () => { const state = { requests: 0, lastToken: null as string | null }; return serveOpenApiHttpApiTestServer({ api: TestApi, handlersLayer: ItemsGroupLive, transformSpec: (spec) => { const { servers: _servers, ...rest } = spec; return rest; }, guardSpecRequest: (request) => Effect.sync(() => { state.requests++; state.lastToken = request.headers["x-spec-token"] ?? null; if (state.requests === 1) { return null; } if (state.lastToken !== "org-token") { return HttpServerResponse.jsonUnsafe({ error: "missing token" }, { status: 401 }); } return null; }), }).pipe( Effect.map((server) => ({ specUrl: server.specUrl, requestCount: () => state.requests, lastToken: () => state.lastToken, })), ); }; // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- describe("OpenAPI Plugin", () => { it.effect("previewSpec returns metadata and header presets", () => Effect.scoped( Effect.gen(function* () { const server = yield* servePluginTestApi(); const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: server.httpClientLayer }), memorySecretsPlugin(), ] as const, }), ); const preview = yield* executor.openapi.previewSpec(server.specJson); expect(preview.operationCount).toBeGreaterThanOrEqual(2); expect(preview.servers).toBeDefined(); }), ), ); it.effect("registers static openapi executor tools", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const tools = yield* executor.tools.list(); const ids = tools.map((t) => t.id); expect(ids).toContain("executor.openapi.previewSpec"); expect(ids).toContain("executor.openapi.getSource"); expect(ids).toContain("executor.openapi.addSource"); expect(ids).toContain("executor.openapi.configureSource"); }), ); it.effect("lists executor as the static runtime source", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const sources = yield* executor.sources.list(); expect(sources.find((s) => s.id === "openapi")).toBeUndefined(); const control = sources.find((s) => s.id === "executor"); expect(control).toBeDefined(); expect(control!.runtime).toBe(true); expect(control!.canRemove).toBe(false); }), ); it.effect("invokes static previewSpec through executor.tools.invoke", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const preview = unwrapInvocation( yield* executor.tools.invoke( "executor.openapi.previewSpec", { spec: testApiSpec() }, autoApprove, ), ).data as { operationCount: number; operations?: unknown }; expect(preview.operationCount).toBeGreaterThanOrEqual(2); expect(preview.operations).toBeUndefined(); }), ); it.effect("describes static previewSpec output from Standard Schema", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [openApiPlugin(), memorySecretsPlugin()] as const, }), ); const schema = yield* executor.tools.schema("executor.openapi.previewSpec"); expect(schema).not.toBeNull(); expect(schema!.outputTypeScript).toContain("servers:"); expect(schema!.outputTypeScript).toContain("securitySchemes:"); expect(schema!.outputTypeScript).toContain("oauth2Presets:"); expect(schema!.outputTypeScript).toContain("title: string | null"); expect(schema!.outputTypeScript).not.toContain("operations:"); expect(schema!.outputTypeScript).not.toContain("_tag"); }), ); it.effect("describes static addSource parameters from Standard Schema", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [openApiPlugin(), memorySecretsPlugin()] as const, }), ); const schema = yield* executor.tools.schema("executor.openapi.addSource"); expect(schema).not.toBeNull(); expect(schema!.inputTypeScript).not.toContain("scope: string"); expect(schema!.inputTypeScript).toContain('kind: "url"'); expect( (schema!.inputSchema as { properties?: Record<string, unknown> }).properties, ).not.toHaveProperty("credentialTargetScope"); expect(schema!.inputTypeScript).not.toBe("Record<string, unknown>"); }), ); it.effect("invokes static addSource through executor.tools.invoke", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const userScope = ScopeId.make("static-user"); const orgScope = ScopeId.make("static-org"); const executor = yield* createExecutor( makeTestConfig({ scopes: [ Scope.make({ id: userScope, name: "user", createdAt: new Date() }), Scope.make({ id: orgScope, name: "org", createdAt: new Date() }), ], plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); const result = unwrapInvocation( yield* executor.tools.invoke( "executor.openapi.addSource", (({ scope: _scope, ...input }) => input)( testApiSourceConfig({ scope: "org", namespace: "runtime" }), ), autoApprove, ), ).data as { sourceId: string; source: { id: string; scope: string }; toolCount: number }; expect(result).toEqual({ sourceId: "runtime", source: { id: "runtime", scope: String(orgScope) }, toolCount: 4, }); expect(yield* executor.openapi.getSource("runtime", String(userScope))).toBeNull(); expect((yield* executor.openapi.getSource("runtime", String(orgScope)))?.scope).toBe( orgScope, ); const inspected = unwrapInvocation( yield* executor.tools.invoke( "executor.openapi.getSource", { namespace: "runtime", scope: "org" }, autoApprove, ), ).data as { source: { namespace: string; scope: string } | null }; expect(inspected.source).toMatchObject({ namespace: "runtime", scope: String(orgScope) }); expect((yield* executor.tools.list()).map((t) => t.id)).toContain("runtime.items.listItems"); }), ); it.effect("static previewSpec returns actionable tool failures", () => Effect.gen(function* () { const config = makeTestConfig({ plugins: [openApiPlugin()] as const }); const executor = yield* createExecutor(config); const result = yield* executor.tools.invoke("executor.openapi.previewSpec", { spec: "not openapi", }); expect(result).toMatchObject({ ok: false, error: { code: "openapi_parse_failed", }, }); yield* executor.close(); yield* Effect.promise(() => config.testDb.close()); }), ); it.effect("requires approval before adding a source through the runtime tool", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [openApiPlugin()] as const }), ); const declined = yield* executor.tools .invoke( "executor.openapi.addSource", (({ scope: _scope, ...input }) => input)( testApiSourceConfig({ namespace: "runtime_declined" }), ), { onElicitation: () => Effect.succeed({ action: "decline" as const }), }, ) .pipe(Effect.flip); expect(Predicate.isTagged(declined, "ElicitationDeclinedError")).toBe(true); expect(yield* executor.openapi.getSource("runtime_declined", TEST_SCOPE)).toBeNull(); expect((yield* executor.tools.list()).map((t) => t.id)).not.toContain( "runtime_declined.items.listItems", ); }), ); it.effect("adds an org source whose direct credentials are owned by the user scope", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const userScope = ScopeId.make("openapi-user"); const orgScope = ScopeId.make("openapi-org"); const executor = yield* createExecutor( makeTestConfig({ scopes: [ Scope.make({ id: userScope, name: "user", createdAt: new Date(), }), Scope.make({ id: orgScope, name: "org", createdAt: new Date() }), ], plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); yield* executor.secrets.set( SetSecretInput.make({ id: SecretId.make("user-query-token"), scope: userScope, name: "User query token", value: "user-token", }), ); const input = testApiSourceConfig({ scope: String(orgScope), namespace: "org_direct_user_credential", queryParams: { token: { kind: "secret" } }, }); yield* executor.openapi.addSpec(input); const bindings = yield* executor.sources.listBindings({ source: { id: "org_direct_user_credential", scope: orgScope }, }); expect(bindings).toEqual([]); }), ); it.effect("sources.configure removes bindings for credential slots no longer present", () => Effect.gen(function* () { const clientLayer = FetchHttpClient.layer; const executor = yield* createExecutor( makeTestConfig({ plugins: [ openApiPlugin({ httpClientLayer: clientLayer }), memorySecretsPlugin(), ] as const, }), ); yield* executor.secrets.set( SetSecretInput.make({ id: SecretId.make("old-token"), scope: ScopeId.make(TEST_SCOPE), name: "Old token", value: "old-secret", }), ); yield* executor.openapi.addSpec(