import { describe, it, expect } from "@effect/vitest";import { Effect, Predicate } from "effect";import { HttpServerResponse } from "effect/unstable/http"; import { ConnectionId, CreateConnectionInput, createExecutor, definePlugin, ElicitationResponse, makeTestConfig, RemoveSecretInput, Scope, ScopeId, SecretId, TokenMaterial,} from "@executor-js/sdk";import { memorySecretsPlugin, serveTestHttpApp } from "@executor-js/sdk/testing"; import { graphqlPlugin } from "./plugin";import { endpointForTelemetry } from "./invoke";import { introspect } from "./introspect";import { GraphqlSourceBindingInput, graphqlHeaderSlot, graphqlQueryParamSlot } from "./types";import type { IntrospectionResult } from "./introspect";import { makeGreetingGraphqlSchema, serveGraphqlTestServer } from "../testing"; const TEST_SCOPE = "test-scope"; // ---------------------------------------------------------------------------// Mock introspection response// --------------------------------------------------------------------------- const introspectionResult: IntrospectionResult = { __schema: { queryType: { name: "Query" }, mutationType: { name: "Mutation" }, types: [ { kind: "OBJECT", name: "Query", description: null, fields: [ { name: "hello", description: "Say hello", args: [ { name: "name", description: null, type: { kind: "SCALAR", name: "String", ofType: null }, defaultValue: null, }, ], type: { kind: "SCALAR", name: "String", ofType: null }, }, ], inputFields: null, enumValues: null, }, { kind: "OBJECT", name: "Mutation", description: null, fields: [ { name: "setGreeting", description: "Set greeting message", args: [ { name: "message", description: null, type: { kind: "NON_NULL", name: null, ofType: { kind: "SCALAR", name: "String", ofType: null }, }, defaultValue: null, }, ], type: { kind: "SCALAR", name: "String", ofType: null }, }, ], inputFields: null, enumValues: null, }, { kind: "SCALAR", name: "String", description: null, fields: null, inputFields: null, enumValues: null, }, ], },}; const introspectionJson = JSON.stringify({ data: introspectionResult });const serveGreetingServer = serveGraphqlTestServer({ schema: makeGreetingGraphqlSchema() });const declineAll = () => Effect.succeed(new ElicitationResponse({ action: "decline" })); const sampleDataPlugin = definePlugin(() => ({ id: "sample-read-test" as const, storage: () => ({}), staticSources: () => [ { id: "sample", kind: "in-memory", name: "Sample", tools: [ { name: "read", description: "Read sample data", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, handler: () => Effect.succeed("sample-value"), }, ], }, ],})); describe("graphqlPlugin real protocol server", () => { it("uses query-free endpoints for invocation attributes", () => { expect(endpointForTelemetry("https://api.example.test/graphql?token=secret#section")).toBe( "https://api.example.test/graphql", ); }); it.effect("does not include upstream response bodies in introspection status errors", () => Effect.gen(function* () { const server = yield* serveTestHttpApp(() => Effect.succeed( HttpServerResponse.text("internal token value", { status: 500, contentType: "text/plain", }), ), ); const error = yield* introspect(server.url("/graphql")).pipe( Effect.provide(server.httpClientLayer), Effect.flip, ); expect(error).toHaveProperty("message", "Introspection failed with status 500"); expect(error).not.toHaveProperty("message", expect.stringContaining("internal token value")); }), ); it.effect("adds a source by introspecting the live GraphQL endpoint", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); const result = yield* executor.graphql.addSource({ endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "live_graph", }); expect(result).toEqual({ toolCount: 2, namespace: "live_graph" }); const tools = yield* executor.tools.list(); expect(tools.map((tool) => tool.id)).toEqual( expect.arrayContaining(["live_graph.query.hello", "live_graph.mutation.setGreeting"]), ); const requests = yield* server.requests; expect(requests.some((request) => request.payload.query?.includes("__schema"))).toBe(true); }), ); it.effect("invokes a live query with headers and query params", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "live_invoke", headers: { "x-static": "abc" }, queryParams: { token: "qp-token" }, }); yield* server.clearRequests; const result = yield* executor.tools.invoke("live_invoke.query.hello", { name: "Ada", }); expect(result).toEqual({ status: 200, data: { hello: "Hello Ada" }, errors: null, }); const requests = yield* server.requests; expect(requests.length).toBe(1); expect(requests[0]?.headers["x-static"]).toBe("abc"); expect(new URL(requests[0]!.url).searchParams.get("token")).toBe("qp-token"); expect(requests[0]?.payload.variables).toEqual({ name: "Ada" }); }), ); it.effect("invokes OAuth-backed sources with a bearer token", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [memorySecretsPlugin(), graphqlPlugin()] as const, }), ); const connectionId = ConnectionId.make("graphql-oauth2-test"); yield* executor.connections.create( new CreateConnectionInput({ id: connectionId, scope: ScopeId.make(TEST_SCOPE), provider: "oauth2", identityLabel: "GraphQL Test", accessToken: new TokenMaterial({ secretId: SecretId.make(`${connectionId}.access_token`), name: "GraphQL Access Token", value: "secret-token", }), refreshToken: null, expiresAt: null, oauthScope: null, providerState: null, }), ); yield* executor.graphql.addSource({ endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "oauth_graph", credentialTargetScope: TEST_SCOPE, auth: { kind: "oauth2", connectionId }, }); yield* server.clearRequests; const result = yield* executor.tools.invoke("oauth_graph.query.hello", { name: "Ada", }); expect(result).toEqual({ status: 200, data: { hello: "Hello Ada" }, errors: null, }); const requests = yield* server.requests; expect(requests[0]?.headers.authorization).toBe("Bearer secret-token"); }), );}); describe("graphqlPlugin", () => { it.effect("registers tools from introspection JSON", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); const result = yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "test_api", }); expect(result.toolCount).toBe(2); expect(result.namespace).toBe("test_api"); const tools = yield* executor.tools.list(); const ids = tools.map((t) => t.id); expect(ids).toContain("test_api.query.hello"); expect(ids).toContain("test_api.mutation.setGreeting"); // static control tool also present expect(ids).toContain("graphql.addSource"); const queryTool = tools.find((t) => t.id === "test_api.query.hello"); expect(queryTool?.description).toBe("Say hello"); const mutationTool = tools.find((t) => t.id === "test_api.mutation.setGreeting"); expect(mutationTool?.description).toBe("Set greeting message"); }), ); it.effect("removes a source and its tools", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "removable", }); let tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "removable").length).toBe(2); yield* executor.graphql.removeSource("removable", TEST_SCOPE); tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "removable").length).toBe(0); const source = yield* executor.graphql.getSource("removable", TEST_SCOPE); expect(source).toBeNull(); }), ); it.effect("lists sources with the static control source", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "my_gql", }); const sources = yield* executor.sources.list(); const dynamic = sources.find((s) => s.id === "my_gql"); expect(dynamic).toBeDefined(); expect(dynamic!.kind).toBe("graphql"); expect(dynamic!.canRemove).toBe(true); expect(dynamic!.canEdit).toBe(true); expect(dynamic!.runtime).toBe(false); const control = sources.find((s) => s.id === "graphql"); expect(control).toBeDefined(); expect(control!.runtime).toBe(true); }), ); it.effect("mutations require approval via resolveAnnotations", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "approval_test", }); const tools = yield* executor.tools.list(); const mutationTool = tools.find((t) => t.id === "approval_test.mutation.setGreeting"); expect(mutationTool).toBeDefined(); expect(mutationTool!.annotations?.requiresApproval).toBe(true); expect(mutationTool!.annotations?.approvalDescription).toBe("mutation setGreeting"); const queryTool = tools.find((t) => t.id === "approval_test.query.hello"); expect(queryTool).toBeDefined(); expect(queryTool!.annotations?.requiresApproval).toBeFalsy(); }), ); it.effect("updateSource patches endpoint/headers without re-registering", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "patched", }); yield* executor.graphql.updateSource("patched", TEST_SCOPE, { endpoint: "http://localhost:5000/graphql", headers: { "x-custom": "abc" }, }); const source = yield* executor.graphql.getSource("patched", TEST_SCOPE); expect(source?.endpoint).toBe("http://localhost:5000/graphql"); expect(source?.headers).toEqual({ "x-custom": "abc" }); // Tools still present (no re-register happened, but they were // already there from addSource and haven't been removed). const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "patched").length).toBe(2); }), ); it.effect("static graphql.addSource delegates to extension", () => Effect.gen(function* () { const userScope = ScopeId.make("static-user"); const orgScope = ScopeId.make("static-org"); const executor = yield* createExecutor( makeTestConfig({ scopes: [ new Scope({ id: userScope, name: "user", createdAt: new Date() }), new Scope({ id: orgScope, name: "org", createdAt: new Date() }), ], plugins: [graphqlPlugin()] as const, }), ); const result = yield* executor.tools.invoke( "graphql.addSource", { scope: String(orgScope), endpoint: "http://localhost:4000/graphql", introspectionJson, namespace: "via_static", }, { onElicitation: "accept-all" }, ); expect(result).toEqual({ toolCount: 2, namespace: "via_static" }); expect(yield* executor.graphql.getSource("via_static", String(userScope))).toBeNull(); expect((yield* executor.graphql.getSource("via_static", String(orgScope)))?.scope).toBe( orgScope, ); const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "via_static").length).toBe(2); }), ); it.effect("requires approval before a runtime-added query sends prior tool output", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [sampleDataPlugin(), graphqlPlugin()] as const }), ); const trusted = yield* executor.tools.invoke( "sample.read", {}, { onElicitation: declineAll }, ); expect(trusted).toBe("sample-value"); const declined = yield* executor.tools .invoke( "graphql.addSource", { endpoint: server.endpoint, scope: TEST_SCOPE, introspectionJson, namespace: "runtime_graphql", }, { onElicitation: declineAll }, ) .pipe(Effect.flip); expect(Predicate.isTagged(declined, "ElicitationDeclinedError")).toBe(true); const requests = yield* server.requests; expect(requests.some((request) => request.payload.variables?.name === "sample-value")).toBe( false, ); }), ); it.effect("applies source headers to the introspection request after approval", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.tools.invoke( "graphql.addSource", { endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "header_materialization", headers: { authorization: "Bearer sample-token", }, }, { onElicitation: "accept-all" }, ); const requests = yield* server.requests; expect( requests.some((request) => request.headers.authorization === "Bearer sample-token"), ).toBe(true); }), ); // -------------------------------------------------------------------------Lines 1–500 of 1134. Load more
import { describe, it, expect } from "@effect/vitest"; import { Effect, Predicate } from "effect"; import { HttpServerResponse } from "effect/unstable/http"; import { ConnectionId, CreateConnectionInput, createExecutor, definePlugin, ElicitationResponse, makeTestConfig, RemoveSecretInput, Scope, ScopeId, SecretId, TokenMaterial, } from "@executor-js/sdk"; import { memorySecretsPlugin, serveTestHttpApp } from "@executor-js/sdk/testing"; import { graphqlPlugin } from "./plugin"; import { endpointForTelemetry } from "./invoke"; import { introspect } from "./introspect"; import { GraphqlSourceBindingInput, graphqlHeaderSlot, graphqlQueryParamSlot } from "./types"; import type { IntrospectionResult } from "./introspect"; import { makeGreetingGraphqlSchema, serveGraphqlTestServer } from "../testing"; const TEST_SCOPE = "test-scope"; // --------------------------------------------------------------------------- // Mock introspection response // --------------------------------------------------------------------------- const introspectionResult: IntrospectionResult = { __schema: { queryType: { name: "Query" }, mutationType: { name: "Mutation" }, types: [ { kind: "OBJECT", name: "Query", description: null, fields: [ { name: "hello", description: "Say hello", args: [ { name: "name", description: null, type: { kind: "SCALAR", name: "String", ofType: null }, defaultValue: null, }, ], type: { kind: "SCALAR", name: "String", ofType: null }, }, ], inputFields: null, enumValues: null, }, { kind: "OBJECT", name: "Mutation", description: null, fields: [ { name: "setGreeting", description: "Set greeting message", args: [ { name: "message", description: null, type: { kind: "NON_NULL", name: null, ofType: { kind: "SCALAR", name: "String", ofType: null }, }, defaultValue: null, }, ], type: { kind: "SCALAR", name: "String", ofType: null }, }, ], inputFields: null, enumValues: null, }, { kind: "SCALAR", name: "String", description: null, fields: null, inputFields: null, enumValues: null, }, ], }, }; const introspectionJson = JSON.stringify({ data: introspectionResult }); const serveGreetingServer = serveGraphqlTestServer({ schema: makeGreetingGraphqlSchema() }); const declineAll = () => Effect.succeed(new ElicitationResponse({ action: "decline" })); const sampleDataPlugin = definePlugin(() => ({ id: "sample-read-test" as const, storage: () => ({}), staticSources: () => [ { id: "sample", kind: "in-memory", name: "Sample", tools: [ { name: "read", description: "Read sample data", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, handler: () => Effect.succeed("sample-value"), }, ], }, ], })); describe("graphqlPlugin real protocol server", () => { it("uses query-free endpoints for invocation attributes", () => { expect(endpointForTelemetry("https://api.example.test/graphql?token=secret#section")).toBe( "https://api.example.test/graphql", ); }); it.effect("does not include upstream response bodies in introspection status errors", () => Effect.gen(function* () { const server = yield* serveTestHttpApp(() => Effect.succeed( HttpServerResponse.text("internal token value", { status: 500, contentType: "text/plain", }), ), ); const error = yield* introspect(server.url("/graphql")).pipe( Effect.provide(server.httpClientLayer), Effect.flip, ); expect(error).toHaveProperty("message", "Introspection failed with status 500"); expect(error).not.toHaveProperty("message", expect.stringContaining("internal token value")); }), ); it.effect("adds a source by introspecting the live GraphQL endpoint", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); const result = yield* executor.graphql.addSource({ endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "live_graph", }); expect(result).toEqual({ toolCount: 2, namespace: "live_graph" }); const tools = yield* executor.tools.list(); expect(tools.map((tool) => tool.id)).toEqual( expect.arrayContaining(["live_graph.query.hello", "live_graph.mutation.setGreeting"]), ); const requests = yield* server.requests; expect(requests.some((request) => request.payload.query?.includes("__schema"))).toBe(true); }), ); it.effect("invokes a live query with headers and query params", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "live_invoke", headers: { "x-static": "abc" }, queryParams: { token: "qp-token" }, }); yield* server.clearRequests; const result = yield* executor.tools.invoke("live_invoke.query.hello", { name: "Ada", }); expect(result).toEqual({ status: 200, data: { hello: "Hello Ada" }, errors: null, }); const requests = yield* server.requests; expect(requests.length).toBe(1); expect(requests[0]?.headers["x-static"]).toBe("abc"); expect(new URL(requests[0]!.url).searchParams.get("token")).toBe("qp-token"); expect(requests[0]?.payload.variables).toEqual({ name: "Ada" }); }), ); it.effect("invokes OAuth-backed sources with a bearer token", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [memorySecretsPlugin(), graphqlPlugin()] as const, }), ); const connectionId = ConnectionId.make("graphql-oauth2-test"); yield* executor.connections.create( new CreateConnectionInput({ id: connectionId, scope: ScopeId.make(TEST_SCOPE), provider: "oauth2", identityLabel: "GraphQL Test", accessToken: new TokenMaterial({ secretId: SecretId.make(`${connectionId}.access_token`), name: "GraphQL Access Token", value: "secret-token", }), refreshToken: null, expiresAt: null, oauthScope: null, providerState: null, }), ); yield* executor.graphql.addSource({ endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "oauth_graph", credentialTargetScope: TEST_SCOPE, auth: { kind: "oauth2", connectionId }, }); yield* server.clearRequests; const result = yield* executor.tools.invoke("oauth_graph.query.hello", { name: "Ada", }); expect(result).toEqual({ status: 200, data: { hello: "Hello Ada" }, errors: null, }); const requests = yield* server.requests; expect(requests[0]?.headers.authorization).toBe("Bearer secret-token"); }), ); }); describe("graphqlPlugin", () => { it.effect("registers tools from introspection JSON", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); const result = yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "test_api", }); expect(result.toolCount).toBe(2); expect(result.namespace).toBe("test_api"); const tools = yield* executor.tools.list(); const ids = tools.map((t) => t.id); expect(ids).toContain("test_api.query.hello"); expect(ids).toContain("test_api.mutation.setGreeting"); // static control tool also present expect(ids).toContain("graphql.addSource"); const queryTool = tools.find((t) => t.id === "test_api.query.hello"); expect(queryTool?.description).toBe("Say hello"); const mutationTool = tools.find((t) => t.id === "test_api.mutation.setGreeting"); expect(mutationTool?.description).toBe("Set greeting message"); }), ); it.effect("removes a source and its tools", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "removable", }); let tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "removable").length).toBe(2); yield* executor.graphql.removeSource("removable", TEST_SCOPE); tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "removable").length).toBe(0); const source = yield* executor.graphql.getSource("removable", TEST_SCOPE); expect(source).toBeNull(); }), ); it.effect("lists sources with the static control source", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "my_gql", }); const sources = yield* executor.sources.list(); const dynamic = sources.find((s) => s.id === "my_gql"); expect(dynamic).toBeDefined(); expect(dynamic!.kind).toBe("graphql"); expect(dynamic!.canRemove).toBe(true); expect(dynamic!.canEdit).toBe(true); expect(dynamic!.runtime).toBe(false); const control = sources.find((s) => s.id === "graphql"); expect(control).toBeDefined(); expect(control!.runtime).toBe(true); }), ); it.effect("mutations require approval via resolveAnnotations", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "approval_test", }); const tools = yield* executor.tools.list(); const mutationTool = tools.find((t) => t.id === "approval_test.mutation.setGreeting"); expect(mutationTool).toBeDefined(); expect(mutationTool!.annotations?.requiresApproval).toBe(true); expect(mutationTool!.annotations?.approvalDescription).toBe("mutation setGreeting"); const queryTool = tools.find((t) => t.id === "approval_test.query.hello"); expect(queryTool).toBeDefined(); expect(queryTool!.annotations?.requiresApproval).toBeFalsy(); }), ); it.effect("updateSource patches endpoint/headers without re-registering", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.graphql.addSource({ endpoint: "http://localhost:4000/graphql", scope: "test-scope", introspectionJson, namespace: "patched", }); yield* executor.graphql.updateSource("patched", TEST_SCOPE, { endpoint: "http://localhost:5000/graphql", headers: { "x-custom": "abc" }, }); const source = yield* executor.graphql.getSource("patched", TEST_SCOPE); expect(source?.endpoint).toBe("http://localhost:5000/graphql"); expect(source?.headers).toEqual({ "x-custom": "abc" }); // Tools still present (no re-register happened, but they were // already there from addSource and haven't been removed). const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "patched").length).toBe(2); }), ); it.effect("static graphql.addSource delegates to extension", () => Effect.gen(function* () { const userScope = ScopeId.make("static-user"); const orgScope = ScopeId.make("static-org"); const executor = yield* createExecutor( makeTestConfig({ scopes: [ new Scope({ id: userScope, name: "user", createdAt: new Date() }), new Scope({ id: orgScope, name: "org", createdAt: new Date() }), ], plugins: [graphqlPlugin()] as const, }), ); const result = yield* executor.tools.invoke( "graphql.addSource", { scope: String(orgScope), endpoint: "http://localhost:4000/graphql", introspectionJson, namespace: "via_static", }, { onElicitation: "accept-all" }, ); expect(result).toEqual({ toolCount: 2, namespace: "via_static" }); expect(yield* executor.graphql.getSource("via_static", String(userScope))).toBeNull(); expect((yield* executor.graphql.getSource("via_static", String(orgScope)))?.scope).toBe( orgScope, ); const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "via_static").length).toBe(2); }), ); it.effect("requires approval before a runtime-added query sends prior tool output", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [sampleDataPlugin(), graphqlPlugin()] as const }), ); const trusted = yield* executor.tools.invoke( "sample.read", {}, { onElicitation: declineAll }, ); expect(trusted).toBe("sample-value"); const declined = yield* executor.tools .invoke( "graphql.addSource", { endpoint: server.endpoint, scope: TEST_SCOPE, introspectionJson, namespace: "runtime_graphql", }, { onElicitation: declineAll }, ) .pipe(Effect.flip); expect(Predicate.isTagged(declined, "ElicitationDeclinedError")).toBe(true); const requests = yield* server.requests; expect(requests.some((request) => request.payload.variables?.name === "sample-value")).toBe( false, ); }), ); it.effect("applies source headers to the introspection request after approval", () => Effect.gen(function* () { const server = yield* serveGreetingServer; const executor = yield* createExecutor( makeTestConfig({ plugins: [graphqlPlugin()] as const }), ); yield* executor.tools.invoke( "graphql.addSource", { endpoint: server.endpoint, scope: TEST_SCOPE, namespace: "header_materialization", headers: { authorization: "Bearer sample-token", }, }, { onElicitation: "accept-all" }, ); const requests = yield* server.requests; expect( requests.some((request) => request.headers.authorization === "Bearer sample-token"), ).toBe(true); }), ); // -------------------------------------------------------------------------