evOo-local/packages/adapters/src/installed-connectors.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

572 lines
18 KiB
TypeScript

import { approvalEffectKey } from "@rakazo/core/node/approval-effect-key";
import { describe, expect, it, vi } from "vitest";
import {
approvedCatalogReplay,
approvedReplayArgs,
catalogApprovalRequest,
createApprovedEffectReplayQueue,
} from "./approval-effect.js";
import {
InstalledConnectorProvider,
importOpenApiDocument,
prepareApiInstall,
verifyMcpInstall,
} from "./installed-connectors.js";
describe("OpenAPI connector import", () => {
it("uses the bounded catalog for a real large installed OpenAPI source", async () => {
const install = {
id: "api-1",
kind: "api",
name: "Contacts API",
source: "https://api.example.test/v1",
secretId: null,
createdAt: new Date(0),
config: {
auth: { type: "none" },
operations: Array.from({ length: 21 }, (_, index) => ({
id: `operation_${String(index).padStart(2, "0")}`,
description: index === 20 ? "Read the final contact" : `Operation ${index}`,
method: "GET",
path: `/contacts/${index}`,
inputSchema: { type: "object", properties: { limit: { type: "integer" } } },
readOnly: true,
})),
},
};
const prisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue([install]),
findFirst: vi.fn().mockResolvedValue(install),
},
};
const provider = new InstalledConnectorProvider(prisma as never, {} as never);
const context = {
workspaceId: "workspace-1",
userId: "user-1",
signal: new AbortController().signal,
} as never;
const [search, load, execute] = await provider.discoverTools(context);
expect([search!.name, load!.name, execute!.name]).toEqual([
"installed_search_tools",
"installed_load_tool",
"installed_execute_tool",
]);
// Names may appear in the search description index; operation schemas must not.
expect(JSON.stringify([search, load, execute])).not.toContain("/contacts/");
expect(search!.description).toContain("operation_20");
const events: unknown[] = [];
for await (const event of provider.execute(
{
tool: search!.name,
args: { query: "final contact" },
executionId: "search",
route: search!.route,
},
context,
)) {
events.push(event);
}
expect(events).toEqual([
{
type: "result",
data: {
tools: [
{
id: "api-1:operation_20",
name: "operation_20",
description: "Read the final contact",
readOnly: true,
},
],
},
},
]);
const listed: unknown[] = [];
for await (const event of provider.execute(
{
tool: search!.name,
args: {},
executionId: "list",
route: search!.route,
},
context,
)) {
listed.push(event);
}
expect(listed).toEqual([
{
type: "result",
data: {
index: [
{
group: "Contacts API",
names: Array.from(
{ length: 21 },
(_, index) => `operation_${String(index).padStart(2, "0")}`,
),
},
],
},
},
]);
expect(JSON.stringify(listed)).not.toContain("inputSchema");
expect(search!.description).toContain("Contacts API:");
expect(search!.description).toContain("operation_00");
await expect(
provider.resolveCall(
{
tool: execute!.name,
args: { id: "api-1:operation_20", arguments: { limit: 5 } },
executionId: "execute",
route: execute!.route,
},
context,
),
).resolves.toMatchObject({
tool: { name: "operation_20", readOnly: true },
call: {
tool: "operation_20",
args: { limit: 5 },
route: { connectorId: "installed", resourceId: "api-1", toolName: "operation_20" },
},
});
});
it("executes an approved lazy call with authoritative normalized arguments", async () => {
const install = {
id: "api-approved",
kind: "api",
source: "https://93.184.216.34",
secretId: null,
createdAt: new Date(0),
config: {
auth: { type: "none" },
operations: Array.from({ length: 21 }, (_, index) => ({
id: `operation_${index}`,
method: index === 20 ? "POST" : "GET",
path: `/items/${index}`,
inputSchema:
index === 20
? {
type: "object",
properties: { count: { type: "integer", default: 3 } },
additionalProperties: false,
}
: { type: "object" },
})),
},
};
const prisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue([install]),
findFirst: vi.fn().mockResolvedValue(install),
},
};
let requestBody: unknown;
const fetch = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
const request = input instanceof Request ? input : new Request(input, init);
requestBody = JSON.parse(await request.text());
return Response.json({ ok: true });
});
const provider = new InstalledConnectorProvider(prisma as never, {} as never, { fetch });
const context = {
workspaceId: "workspace-1",
userId: "user-1",
signal: new AbortController().signal,
} as never;
const execute = (await provider.discoverTools(context))[2]!;
const marker = "__rakazoCatalogTool";
const approvedRequest = catalogApprovalRequest(
execute.name,
{ id: "api-approved:operation_20", arguments: {} },
marker,
);
const queue = createApprovedEffectReplayQueue([
{ kind: "operation_20", request: approvedRequest },
]);
const replay = approvedCatalogReplay(queue, execute.name, marker, true);
const resolved = await provider.resolveCall(
{
tool: execute.name,
args: replay.args!,
executionId: "approved",
route: execute.route,
},
context,
);
const args = approvedReplayArgs(queue.take(resolved!.tool.name)!, resolved!.call.args, marker);
const events = [];
for await (const event of provider.execute({ ...resolved!.call, args }, context)) {
events.push(event);
}
expect(resolved!.tool.name).toBe("operation_20");
expect(args).toEqual({ count: 3 });
expect(approvalEffectKey("run", resolved!.tool.name, args)).toBe(
approvalEffectKey("run", resolved!.tool.name, resolved!.call.args),
);
expect(requestBody).toEqual({ count: 3 });
expect(events).toEqual([{ type: "result", data: { status: 200, data: { ok: true } } }]);
expect(queue.assertDrained).not.toThrow();
});
it("does not emit a replacement character when a response limit splits UTF-8", async () => {
const install = {
id: "api-utf8",
kind: "api",
source: "https://93.184.216.34",
secretId: null,
createdAt: new Date(0),
config: {
auth: { type: "none" },
operations: [
{
id: "read_text",
method: "GET",
path: "/text",
inputSchema: { type: "object" },
readOnly: true,
},
],
},
};
const prisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue([install]),
findFirst: vi.fn().mockResolvedValue(install),
},
};
const prefix = "a".repeat(999_999);
const fetch = vi.fn().mockResolvedValue(new Response(`${prefix}€`));
const provider = new InstalledConnectorProvider(prisma as never, {} as never, { fetch });
const context = {
spaceId: "space-1",
userId: "user-1",
signal: new AbortController().signal,
} as never;
const [tool] = await provider.discoverTools(context);
const events = [];
for await (const event of provider.execute(
{ tool: tool!.name, args: {}, executionId: "utf8", route: tool!.route },
context,
)) {
events.push(event);
}
expect(events).toHaveLength(1);
const event = events[0] as {
type: string;
data: { status: number; data: string; truncated: boolean };
};
expect(event.type).toBe("result");
expect(event.data.status).toBe(200);
expect(event.data.truncated).toBe(true);
expect(event.data.data).toHaveLength(prefix.length);
expect(event.data.data.endsWith("a")).toBe(true);
expect(event.data.data.includes("\uFFFD")).toBe(false);
});
it("keeps colliding OpenAPI operation names unique across installs", async () => {
const installs = ["install-A", "install-B"].map((id, index) => ({
id,
kind: "api",
source: `https://api.example.test/${id}`,
secretId: null,
createdAt: new Date(index),
config: {
auth: { type: "none" },
operations: Array.from({ length: 11 }, (_, op) => ({
id: op === 0 ? "delete_item" : `other_${op}`,
name: op === 0 ? "delete_item" : `other_${op}`,
method: "DELETE",
path: `/${op}`,
inputSchema: { type: "object", properties: { target: { type: "string" } } },
})),
},
}));
const prisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue(installs),
findFirst: vi
.fn()
.mockImplementation(({ where }: { where: { id: string } }) =>
Promise.resolve(installs.find((install) => install.id === where.id) ?? null),
),
},
};
const provider = new InstalledConnectorProvider(prisma as never, {} as never);
const context = {
workspaceId: "workspace-1",
userId: "user-1",
signal: new AbortController().signal,
} as never;
const discovered = await provider.discoverTools(context);
expect(discovered).toHaveLength(3);
const [search, , execute] = discovered;
const events: unknown[] = [];
for await (const event of provider.execute(
{
tool: search!.name,
args: { query: "delete_item" },
executionId: "search",
route: search!.route,
},
context,
)) {
events.push(event);
}
const tools = (events[0] as { data: { tools: Array<{ id: string; name: string }> } }).data
.tools;
expect(tools.map((tool) => tool.name).sort()).toEqual([
"installed__install-A__delete_item",
"installed__install-B__delete_item",
]);
const resolvedA = await provider.resolveCall(
{
tool: execute!.name,
args: { id: "install-A:delete_item", arguments: { target: "a" } },
executionId: "a",
route: execute!.route,
},
context,
);
const resolvedB = await provider.resolveCall(
{
tool: execute!.name,
args: { id: "install-B:delete_item", arguments: { target: "b" } },
executionId: "b",
route: execute!.route,
},
context,
);
expect(resolvedA?.tool.name).toBe("installed__install-A__delete_item");
expect(resolvedB?.tool.name).toBe("installed__install-B__delete_item");
expect(resolvedA?.tool.name).not.toBe(resolvedB?.tool.name);
});
it("exposes 20 installed tools directly and switches at 21", async () => {
const makeInstall = (count: number) => ({
id: "api-threshold",
kind: "api",
source: "https://api.example.test/v1",
secretId: null,
createdAt: new Date(0),
config: {
auth: { type: "none" },
operations: Array.from({ length: count }, (_, index) => ({
id: `operation_${index}`,
method: "GET",
path: `/${index}`,
inputSchema: { type: "object" },
readOnly: true,
})),
},
});
const context = {
workspaceId: "workspace-1",
userId: "user-1",
signal: new AbortController().signal,
} as never;
const directPrisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue([makeInstall(20)]),
},
};
const direct = await new InstalledConnectorProvider(
directPrisma as never,
{} as never,
).discoverTools(context);
expect(direct).toHaveLength(20);
expect(direct[0]?.name).toBe("operation_0");
expect(direct.every((tool) => !tool.name.startsWith("installed__"))).toBe(true);
const lazyPrisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue([makeInstall(21)]),
},
};
const lazy = await new InstalledConnectorProvider(
lazyPrisma as never,
{} as never,
).discoverTools(context);
expect(lazy.map((tool) => tool.name)).toEqual([
"installed_search_tools",
"installed_load_tool",
"installed_execute_tool",
]);
});
it("returns no tools for an empty installed catalog", async () => {
const provider = new InstalledConnectorProvider(
{ capabilityInstall: { findMany: vi.fn().mockResolvedValue([]) } } as never,
{} as never,
);
await expect(
provider.discoverTools({
workspaceId: "workspace-1",
userId: "user-1",
signal: new AbortController().signal,
} as never),
).resolves.toEqual([]);
});
it("executes an installed API operation whose id matches a catalog control", async () => {
const install = {
id: "api-reserved",
kind: "api",
source: "https://93.184.216.34",
secretId: null,
createdAt: new Date(0),
config: {
auth: { type: "none" },
operations: [
{
id: "__catalog_execute",
method: "GET",
path: "/reserved",
inputSchema: { type: "object" },
},
],
},
};
const prisma = {
capabilityInstall: {
findMany: vi.fn().mockResolvedValue([install]),
findFirst: vi.fn().mockResolvedValue(install),
},
};
const fetch = vi.fn().mockResolvedValue(Response.json({ ok: true }));
const provider = new InstalledConnectorProvider(prisma as never, {} as never, { fetch });
const context = {
workspaceId: "workspace-1",
userId: "user-1",
signal: new AbortController().signal,
} as never;
const [tool] = await provider.discoverTools(context);
const call = { tool: tool!.name, args: {}, executionId: "reserved", route: tool!.route };
await expect(provider.resolveCall(call, context)).resolves.toBeUndefined();
const events = [];
for await (const event of provider.execute(call, context)) events.push(event);
expect(events).toEqual([{ type: "result", data: { status: 200, data: { ok: true } } }]);
expect(fetch).toHaveBeenCalledOnce();
});
it("maps operation ids, parameters, and JSON bodies to bounded agent tools", () => {
const imported = importOpenApiDocument({
openapi: "3.1.0",
servers: [{ url: "https://api.example.test/v1" }],
paths: {
"/contacts/{contactId}": {
parameters: [
{
name: "contactId",
in: "path",
required: true,
schema: { type: "string" },
},
],
patch: {
operationId: "updateContact",
summary: "Update one contact",
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: { name: { type: "string" } },
},
},
},
},
},
},
},
});
expect(imported.baseUrl).toBe("https://api.example.test/v1");
expect(imported.operations).toEqual([
expect.objectContaining({
id: "updateContact",
method: "PATCH",
path: "/contacts/{contactId}",
readOnly: false,
inputSchema: expect.objectContaining({ required: ["contactId", "body"] }),
}),
]);
});
it("refuses ambiguous specs without stable operation ids", () => {
expect(() =>
importOpenApiDocument({
servers: [{ url: "https://api.example.test" }],
paths: { "/contacts": { get: { summary: "List contacts" } } },
}),
).toThrow("operationId");
});
it("refuses credentials embedded in an imported OpenAPI server URL", () => {
expect(() =>
importOpenApiDocument({
servers: [{ url: "https://api.example.test?token=fake-secret" }],
paths: { "/contacts": { get: { operationId: "listContacts" } } },
}),
).toThrow("encrypted credential field");
});
it("refuses sensitive headers that would become model-controlled inputs", () => {
expect(() =>
importOpenApiDocument({
servers: [{ url: "https://api.example.test" }],
paths: {
"/contacts": {
get: {
operationId: "listContacts",
parameters: [{ name: "Authorization", in: "header", schema: { type: "string" } }],
},
},
},
}),
).toThrow("unsafe header Authorization");
});
it("refuses credentials embedded in a persisted MCP URL", async () => {
await expect(
verifyMcpInstall({
source: "https://connectors.example.test/mcp?access_token=fake-secret",
config: { preset: "custom", auth: { type: "none" } },
}),
).rejects.toThrow("encrypted credential field");
});
it("refuses model-controlled sensitive headers in authored API operations", async () => {
await expect(
prepareApiInstall({
source: "https://93.184.216.34",
config: {
auth: { type: "bearer" },
operations: [
{
id: "unsafe",
method: "GET",
path: "/contacts",
headerParameters: ["authorization"],
},
],
},
credential: "fake-credential",
}),
).rejects.toThrow("Sensitive headers cannot be model-controlled");
});
});