import { describe, it, expect } from "@effect/vitest"; import { parseScope, ownerPartitionKey, vaultV1ObjectName, vaultV1LegacyObjectName, vaultV2ObjectName, oauthClientDedupKey, serializeOAuthScopes, migratePolicyPattern, migrateOpenApiAuthTemplate, API_KEY_TEMPLATE_SLUG, migrateGrant, migrateExpiresAt, SYNTHETIC_CLIENT_CREDENTIALS_TTL_MS, migrateOpenApiSourceConfig, migrateSourceAuth, classifyBindingSlot, dedupeOAuthClients, oauthClientSlugKey, planIntegrationRow, planConnectionRow, migratedItemId, migrateMcpSourceConfig, migrateGraphqlSourceConfig, OAUTH_TEMPLATE_SLUG, planMigration, buildV1RuntimeMetadataIndex, migrateV1PluginStorageRuntimeRow, migrateV1ToolAnnotations,} from "./migration-spec";import type { MigrationInput, MigratedSourceConfig } from "./migration-spec"; describe("parseScope", () => { it("maps a bare org scope to owner=org, empty subject", () => { expect(parseScope("org_01KRFBFKMP")).toEqual({ owner: "org", subject: "", tenant: "org_01KRFBFKMP", }); }); it("maps a user-org scope to owner=user with subject + tenant", () => { expect(parseScope("user-org:user_01ABC:org_01XYZ")).toEqual({ owner: "user", subject: "user_01ABC", tenant: "org_01XYZ", }); }); it("fails loud (null) on unknown shapes rather than mis-owning", () => { expect(parseScope("user_01ABC")).toBeNull(); // bare user, no org — not a v2 shape expect(parseScope("local-folder-hash")).toBeNull(); expect(parseScope("user-org:org_01XYZ")).toBeNull(); // missing user segment expect(parseScope("user-org:user_01:org_02:extra")).toBeNull(); // too many segments expect(parseScope("org_01:trailing")).toBeNull(); // org with a colon expect(parseScope("")).toBeNull(); });}); describe("ownerPartitionKey", () => { it("collapses org apps across the org, keeps user apps per-user", () => { expect(ownerPartitionKey({ owner: "org", subject: "", tenant: "org_X" })).toBe("org:org_X"); expect(ownerPartitionKey({ owner: "user", subject: "user_U", tenant: "org_X" })).toBe( "user:user_U:org_X", ); });}); describe("vault object naming", () => { it("v1 name carries the scope segment + url-encodes both parts", () => { expect(vaultV1ObjectName("executor", "user-org:user_U:org_O", "sec_a/b")).toBe( "executor/user-org%3Auser_U%3Aorg_O/secrets/sec_a%2Fb", ); }); it("v1 legacy name leaves the segments un-encoded (the 404 fallback)", () => { expect(vaultV1LegacyObjectName("executor", "org_X", "sec_a")).toBe( "executor/org_X/secrets/sec_a", ); }); it("v2 name drops the scope segment (flat namespace)", () => { expect(vaultV2ObjectName("executor", "item_a/b")).toBe("executor/secrets/item_a%2Fb"); }); it("v1 and v2 names differ → id-reuse is impossible (the whole reason to re-key)", () => { const scope = "org_X"; const id = "sec_1"; expect(vaultV1ObjectName("executor", scope, id)).not.toBe(vaultV2ObjectName("executor", id)); });}); describe("oauthClientDedupKey", () => { it("merges identical (partition, clientId, tokenEndpoint), distinguishes any difference", () => { const a = oauthClientDedupKey("org:org_X", "cid", "https://t/token"); const b = oauthClientDedupKey("org:org_X", "cid", "https://t/token"); const c = oauthClientDedupKey("user:u:org_X", "cid", "https://t/token"); expect(a).toBe(b); expect(a).not.toBe(c); });}); describe("serializeOAuthScopes", () => { it("space-joins, de-dupes order-preserving, drops empties", () => { expect(serializeOAuthScopes(["data", "api", "data", ""])).toBe("data api"); expect(serializeOAuthScopes(["vanta-api.all:read", "vanta-api.vendors:read"])).toBe( "vanta-api.all:read vanta-api.vendors:read", ); expect(serializeOAuthScopes([])).toBe(""); });}); describe("migratePolicyPattern", () => { const M = new Map([ ["github_v3_rest_api", "github"], // a rename ["microsoft_graph", "microsoft_graph"], // no-op slug ["dealcloud_api", "dealcloud_api"], ]); it("universal pattern passes through", () => { expect(migratePolicyPattern("*", M)).toEqual({ kind: "ok", pattern: "*" }); }); it("static namespaces pass through verbatim", () => { expect(migratePolicyPattern("executor.openapi.addSource", M)).toEqual({ kind: "static", pattern: "executor.openapi.addSource", }); expect(migratePolicyPattern("openapi.addSource", M)).toEqual({ kind: "static", pattern: "openapi.addSource", }); }); it("whole-integration (`slug.*`) only remaps the slug — trailing * already a subtree", () => { expect(migratePolicyPattern("dealcloud_api.*", M)).toEqual({ kind: "ok", pattern: "dealcloud_api.*", }); expect(migratePolicyPattern("github_v3_rest_api.*", M)).toEqual({ kind: "ok", pattern: "github.*", }); }); it("exact / subtree patterns insert the owner+connection wildcards", () => { expect(migratePolicyPattern("microsoft_graph.meEvent.meEventsEventCancel", M)).toEqual({ kind: "ok", pattern: "microsoft_graph.*.*.meEvent.meEventsEventCancel", }); expect(migratePolicyPattern("github_v3_rest_api.repos.deleteAccessRestrictions", M)).toEqual({ kind: "ok", pattern: "github.*.*.repos.deleteAccessRestrictions", }); expect(migratePolicyPattern("microsoft_graph.meCalendar.*", M)).toEqual({ kind: "ok", pattern: "microsoft_graph.*.*.meCalendar.*", }); }); it("an unknown first segment is flagged DEAD (source removed) — never silently dropped", () => { expect(migratePolicyPattern("api_githubcopilot_com.delete_file", M)).toEqual({ kind: "dead", slug: "api_githubcopilot_com", }); });}); describe("plugin runtime metadata migration", () => { it("stamps MCP tool annotations from the legacy binding without changing the Executor slug", () => { const index = buildV1RuntimeMetadataIndex([ { scopeId: "org_X", pluginId: "mcp", collection: "binding", key: "axiom_mcp.querydataset", data: { namespace: "axiom_mcp", toolId: "axiom_mcp.querydataset", binding: { toolId: "querydataset", toolName: "queryDataset", annotations: { title: "Query dataset", readOnlyHint: true }, }, }, }, ]); const annotations = migrateV1ToolAnnotations( { scopeId: "org_X", sourceId: "axiom_mcp", pluginId: "mcp", name: "querydataset", annotations: null, }, index, ); expect(annotations).toEqual({ requiresApproval: false, mcp: { toolName: "queryDataset", upstream: { title: "Query dataset", readOnlyHint: true }, }, }); }); it("keeps already-stamped MCP annotations unchanged for idempotent re-runs", () => { const annotations = { requiresApproval: true, mcp: { toolName: "deleteDataset", upstream: { destructiveHint: true } }, }; expect( migrateV1ToolAnnotations( { scopeId: "org_X", sourceId: "axiom_mcp", pluginId: "mcp", name: "deletedataset", annotations, }, buildV1RuntimeMetadataIndex([]), ), ).toBe(annotations); }); it("rewrites v1 OpenAPI operation storage to the v2 catalog-owned shape", () => { expect( migrateV1PluginStorageRuntimeRow({ scopeId: "user-org:user_U:org_X", pluginId: "openapi", collection: "operation", key: "vercel_api.dns.getRecords", data: { toolId: "vercel_api.dns.getRecords", sourceId: "vercel_api", binding: { method: "get", pathTemplate: "/v4/domains/{domain}/records" }, }, }), ).toEqual({ pluginId: "openapi", collection: "operation", key: "vercel_api.dns.getRecords", data: { integration: "vercel_api", toolName: "dns.getRecords", binding: { method: "get", pathTemplate: "/v4/domains/{domain}/records" }, }, owner: "catalog", }); }); it("rewrites v1 GraphQL operation storage and normalizes graphql-greenfield ids", () => { expect( migrateV1PluginStorageRuntimeRow({ scopeId: "org_X", pluginId: "graphql-greenfield", collection: "operation", key: "graphql_api.query.hello", data: { toolId: "graphql_api.query.hello", sourceId: "graphql_api", binding: { kind: "query", fieldName: "hello", operationString: "query { hello }" }, }, }), ).toEqual({ pluginId: "graphql", collection: "operation", key: "graphql_api.query.hello", data: { integration: "graphql_api", toolName: "query.hello", binding: { kind: "query", fieldName: "hello", operationString: "query { hello }" }, }, owner: "catalog", }); }); it("keeps source-owned plugin storage rows source-owned", () => { expect( migrateV1PluginStorageRuntimeRow({ scopeId: "org_X", pluginId: "onepassword", collection: "settings", key: "config", data: { vaultId: "vault_123" }, }), ).toEqual({ pluginId: "onepassword", collection: "settings", key: "config", data: { vaultId: "vault_123" }, owner: "source", }); });}); describe("migrateOpenApiAuthTemplate", () => { it("maps a single Bearer apiKey header to one apiKey method (prefix preserved)", () => { const r = migrateOpenApiAuthTemplate({ headers: { Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, }); expect(r.authenticationTemplate).toEqual([ { slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", headers: { Authorization: ["Bearer ", { type: "variable", name: "token" }] }, }, ]); expect(r.slotToTemplateSlug).toEqual({ "header:authorization": API_KEY_TEMPLATE_SLUG }); expect(r.slotToVariable).toEqual({ "header:authorization": "token" }); expect(r.staticHeaders).toEqual({}); }); it("a prefix-less apiKey header renders a bare [token]", () => { const r = migrateOpenApiAuthTemplate({ headers: { "X-Api-Key": { kind: "binding", slot: "header:x-api-key" } }, }); expect(r.authenticationTemplate[0]).toEqual({ slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", headers: { "X-Api-Key": [{ type: "variable", name: "token" }] }, }); }); it("a query-param api key lands in queryParams, not headers", () => { const r = migrateOpenApiAuthTemplate({ queryParams: { key: { kind: "binding", slot: "query_param:key" } }, }); expect(r.authenticationTemplate[0]).toEqual({ slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", queryParams: { key: [{ type: "variable", name: "token" }] }, }); expect(r.slotToTemplateSlug).toEqual({ "query_param:key": API_KEY_TEMPLATE_SLUG }); }); it("literal-string headers pass through as static, never as credentials", () => { const r = migrateOpenApiAuthTemplate({ headers: { "User-Agent": "executor/1.0", Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, }); expect(r.staticHeaders).toEqual({ "User-Agent": "executor/1.0" }); expect(r.authenticationTemplate).toHaveLength(1); // The lone credential placement stays the canonical `token`. expect(r.slotToVariable).toEqual({ "header:authorization": "token" }); }); it("converts oauth2 to an oauth method keyed on its security-scheme slug", () => { const r = migrateOpenApiAuthTemplate({ oauth2: { securitySchemeName: "googleOAuth2", flow: "authorizationCode", authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", scopes: ["https://www.googleapis.com/auth/calendar"], }, }); expect(r.authenticationTemplate).toEqual([ { slug: "googleOAuth2", type: "oauth", authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", scopes: ["https://www.googleapis.com/auth/calendar"], }, ]); expect(r.slotToTemplateSlug).toEqual({ "oauth2:googleoauth2:connection": "googleOAuth2" }); }); it("maps hyphenated legacy oauth slot names to underscored oauth slugs", () => { const r = migrateOpenApiAuthTemplate({ oauth2: { securitySchemeName: "oauth_2_0", flow: "authorizationCode", authorizationUrl: "https://accounts.spotify.com/authorize", tokenUrl: "https://accounts.spotify.com/api/token", scopes: ["user-read-email"], }, }); expect(r.slotToTemplateSlug["oauth2:oauth-2-0:connection"]).toBe("oauth_2_0"); expect(r.slotToVariable["oauth2:oauth-2-0:connection"]).toBe("token"); }); it("a source offering both apiKey and oauth declares both methods", () => { const r = migrateOpenApiAuthTemplate({ headers: { Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, oauth2: { securitySchemeName: "oauth2", tokenUrl: "https://example.com/token", scopes: [], }, }); expect(r.authenticationTemplate.map((m) => m.type)).toEqual(["apiKey", "oauth"]); }); it("gives two distinct credential placements (Datadog) their own variables", () => { const r = migrateOpenApiAuthTemplate({ headers: { "DD-API-KEY": { kind: "binding", slot: "header:dd-api-key" }, "DD-APPLICATION-KEY": { kind: "binding", slot: "header:dd-application-key" }, }, }); expect(r.authenticationTemplate).toEqual([ { slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", headers: { "DD-API-KEY": [{ type: "variable", name: "dd_api_key" }], "DD-APPLICATION-KEY": [{ type: "variable", name: "dd_application_key" }], }, }, ]); expect(r.slotToVariable).toEqual({ "header:dd-api-key": "dd_api_key", "header:dd-application-key": "dd_application_key", }); expect(r.warnings).toEqual([]); }); it("an auth-less source yields an empty template", () => { const r = migrateOpenApiAuthTemplate({}); expect(r.authenticationTemplate).toEqual([]); expect(r.slotToTemplateSlug).toEqual({}); expect(r.slotToVariable).toEqual({}); });}); describe("migrateGrant", () => { it("maps client-credentials to client_credentials, everything else to authorization_code", () => { expect(migrateGrant("client-credentials")).toBe("client_credentials"); expect(migrateGrant("authorization-code")).toBe("authorization_code"); expect(migrateGrant("dynamic-dcr")).toBe("authorization_code"); });}); describe("migrateExpiresAt (C1a)", () => { const now = 1_700_000_000_000; it("synthesizes a 1h expiry for a client_credentials connection with null v1 expiry", () => { expect(migrateExpiresAt({ grant: "client_credentials", v1ExpiresAt: null, nowMs: now })).toBe( now + SYNTHETIC_CLIENT_CREDENTIALS_TTL_MS, ); expect(SYNTHETIC_CLIENT_CREDENTIALS_TTL_MS).toBe(60 * 60 * 1000); }); it("keeps a real v1 expiry for client_credentials (only backfills the null case)", () => { expect(migrateExpiresAt({ grant: "client_credentials", v1ExpiresAt: 123, nowMs: now })).toBe( 123, ); }); it("never synthesizes for authorization_code (null stays null — re-auth on use)", () => { expect( migrateExpiresAt({ grant: "authorization_code", v1ExpiresAt: null, nowMs: now }), ).toBeNull(); expect(migrateExpiresAt({ grant: "authorization_code", v1ExpiresAt: 456, nowMs: now })).toBe( 456, ); });}); describe("migrateOpenApiSourceConfig", () => { it("copies structural fields, drops namespace, and converts auth to a template", () => { const r = migrateOpenApiSourceConfig({ spec: "{openapi}", sourceUrl: "https://api.example.com/openapi.json", baseUrl: "https://api.example.com", headers: { "User-Agent": "executor/1.0", Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, }); const config = r.config as { readonly spec?: string; readonly sourceUrl?: string; readonly baseUrl?: string; readonly headers?: Record<string, string>; readonly authenticationTemplate?: unknown; }; expect(config.spec).toBe("{openapi}"); expect(config.sourceUrl).toBe("https://api.example.com/openapi.json"); expect(config.baseUrl).toBe("https://api.example.com"); // The literal header is static config; the credential header became a template. expect(config.headers).toEqual({ "User-Agent": "executor/1.0" }); expect(config.authenticationTemplate).toEqual([ { slug: API_KEY_TEMPLATE_SLUG, type: "apiKey",Lines 1–500 of 1853. Load more
import { describe, it, expect } from "@effect/vitest"; import { parseScope, ownerPartitionKey, vaultV1ObjectName, vaultV1LegacyObjectName, vaultV2ObjectName, oauthClientDedupKey, serializeOAuthScopes, migratePolicyPattern, migrateOpenApiAuthTemplate, API_KEY_TEMPLATE_SLUG, migrateGrant, migrateExpiresAt, SYNTHETIC_CLIENT_CREDENTIALS_TTL_MS, migrateOpenApiSourceConfig, migrateSourceAuth, classifyBindingSlot, dedupeOAuthClients, oauthClientSlugKey, planIntegrationRow, planConnectionRow, migratedItemId, migrateMcpSourceConfig, migrateGraphqlSourceConfig, OAUTH_TEMPLATE_SLUG, planMigration, buildV1RuntimeMetadataIndex, migrateV1PluginStorageRuntimeRow, migrateV1ToolAnnotations, } from "./migration-spec"; import type { MigrationInput, MigratedSourceConfig } from "./migration-spec"; describe("parseScope", () => { it("maps a bare org scope to owner=org, empty subject", () => { expect(parseScope("org_01KRFBFKMP")).toEqual({ owner: "org", subject: "", tenant: "org_01KRFBFKMP", }); }); it("maps a user-org scope to owner=user with subject + tenant", () => { expect(parseScope("user-org:user_01ABC:org_01XYZ")).toEqual({ owner: "user", subject: "user_01ABC", tenant: "org_01XYZ", }); }); it("fails loud (null) on unknown shapes rather than mis-owning", () => { expect(parseScope("user_01ABC")).toBeNull(); // bare user, no org — not a v2 shape expect(parseScope("local-folder-hash")).toBeNull(); expect(parseScope("user-org:org_01XYZ")).toBeNull(); // missing user segment expect(parseScope("user-org:user_01:org_02:extra")).toBeNull(); // too many segments expect(parseScope("org_01:trailing")).toBeNull(); // org with a colon expect(parseScope("")).toBeNull(); }); }); describe("ownerPartitionKey", () => { it("collapses org apps across the org, keeps user apps per-user", () => { expect(ownerPartitionKey({ owner: "org", subject: "", tenant: "org_X" })).toBe("org:org_X"); expect(ownerPartitionKey({ owner: "user", subject: "user_U", tenant: "org_X" })).toBe( "user:user_U:org_X", ); }); }); describe("vault object naming", () => { it("v1 name carries the scope segment + url-encodes both parts", () => { expect(vaultV1ObjectName("executor", "user-org:user_U:org_O", "sec_a/b")).toBe( "executor/user-org%3Auser_U%3Aorg_O/secrets/sec_a%2Fb", ); }); it("v1 legacy name leaves the segments un-encoded (the 404 fallback)", () => { expect(vaultV1LegacyObjectName("executor", "org_X", "sec_a")).toBe( "executor/org_X/secrets/sec_a", ); }); it("v2 name drops the scope segment (flat namespace)", () => { expect(vaultV2ObjectName("executor", "item_a/b")).toBe("executor/secrets/item_a%2Fb"); }); it("v1 and v2 names differ → id-reuse is impossible (the whole reason to re-key)", () => { const scope = "org_X"; const id = "sec_1"; expect(vaultV1ObjectName("executor", scope, id)).not.toBe(vaultV2ObjectName("executor", id)); }); }); describe("oauthClientDedupKey", () => { it("merges identical (partition, clientId, tokenEndpoint), distinguishes any difference", () => { const a = oauthClientDedupKey("org:org_X", "cid", "https://t/token"); const b = oauthClientDedupKey("org:org_X", "cid", "https://t/token"); const c = oauthClientDedupKey("user:u:org_X", "cid", "https://t/token"); expect(a).toBe(b); expect(a).not.toBe(c); }); }); describe("serializeOAuthScopes", () => { it("space-joins, de-dupes order-preserving, drops empties", () => { expect(serializeOAuthScopes(["data", "api", "data", ""])).toBe("data api"); expect(serializeOAuthScopes(["vanta-api.all:read", "vanta-api.vendors:read"])).toBe( "vanta-api.all:read vanta-api.vendors:read", ); expect(serializeOAuthScopes([])).toBe(""); }); }); describe("migratePolicyPattern", () => { const M = new Map([ ["github_v3_rest_api", "github"], // a rename ["microsoft_graph", "microsoft_graph"], // no-op slug ["dealcloud_api", "dealcloud_api"], ]); it("universal pattern passes through", () => { expect(migratePolicyPattern("*", M)).toEqual({ kind: "ok", pattern: "*" }); }); it("static namespaces pass through verbatim", () => { expect(migratePolicyPattern("executor.openapi.addSource", M)).toEqual({ kind: "static", pattern: "executor.openapi.addSource", }); expect(migratePolicyPattern("openapi.addSource", M)).toEqual({ kind: "static", pattern: "openapi.addSource", }); }); it("whole-integration (`slug.*`) only remaps the slug — trailing * already a subtree", () => { expect(migratePolicyPattern("dealcloud_api.*", M)).toEqual({ kind: "ok", pattern: "dealcloud_api.*", }); expect(migratePolicyPattern("github_v3_rest_api.*", M)).toEqual({ kind: "ok", pattern: "github.*", }); }); it("exact / subtree patterns insert the owner+connection wildcards", () => { expect(migratePolicyPattern("microsoft_graph.meEvent.meEventsEventCancel", M)).toEqual({ kind: "ok", pattern: "microsoft_graph.*.*.meEvent.meEventsEventCancel", }); expect(migratePolicyPattern("github_v3_rest_api.repos.deleteAccessRestrictions", M)).toEqual({ kind: "ok", pattern: "github.*.*.repos.deleteAccessRestrictions", }); expect(migratePolicyPattern("microsoft_graph.meCalendar.*", M)).toEqual({ kind: "ok", pattern: "microsoft_graph.*.*.meCalendar.*", }); }); it("an unknown first segment is flagged DEAD (source removed) — never silently dropped", () => { expect(migratePolicyPattern("api_githubcopilot_com.delete_file", M)).toEqual({ kind: "dead", slug: "api_githubcopilot_com", }); }); }); describe("plugin runtime metadata migration", () => { it("stamps MCP tool annotations from the legacy binding without changing the Executor slug", () => { const index = buildV1RuntimeMetadataIndex([ { scopeId: "org_X", pluginId: "mcp", collection: "binding", key: "axiom_mcp.querydataset", data: { namespace: "axiom_mcp", toolId: "axiom_mcp.querydataset", binding: { toolId: "querydataset", toolName: "queryDataset", annotations: { title: "Query dataset", readOnlyHint: true }, }, }, }, ]); const annotations = migrateV1ToolAnnotations( { scopeId: "org_X", sourceId: "axiom_mcp", pluginId: "mcp", name: "querydataset", annotations: null, }, index, ); expect(annotations).toEqual({ requiresApproval: false, mcp: { toolName: "queryDataset", upstream: { title: "Query dataset", readOnlyHint: true }, }, }); }); it("keeps already-stamped MCP annotations unchanged for idempotent re-runs", () => { const annotations = { requiresApproval: true, mcp: { toolName: "deleteDataset", upstream: { destructiveHint: true } }, }; expect( migrateV1ToolAnnotations( { scopeId: "org_X", sourceId: "axiom_mcp", pluginId: "mcp", name: "deletedataset", annotations, }, buildV1RuntimeMetadataIndex([]), ), ).toBe(annotations); }); it("rewrites v1 OpenAPI operation storage to the v2 catalog-owned shape", () => { expect( migrateV1PluginStorageRuntimeRow({ scopeId: "user-org:user_U:org_X", pluginId: "openapi", collection: "operation", key: "vercel_api.dns.getRecords", data: { toolId: "vercel_api.dns.getRecords", sourceId: "vercel_api", binding: { method: "get", pathTemplate: "/v4/domains/{domain}/records" }, }, }), ).toEqual({ pluginId: "openapi", collection: "operation", key: "vercel_api.dns.getRecords", data: { integration: "vercel_api", toolName: "dns.getRecords", binding: { method: "get", pathTemplate: "/v4/domains/{domain}/records" }, }, owner: "catalog", }); }); it("rewrites v1 GraphQL operation storage and normalizes graphql-greenfield ids", () => { expect( migrateV1PluginStorageRuntimeRow({ scopeId: "org_X", pluginId: "graphql-greenfield", collection: "operation", key: "graphql_api.query.hello", data: { toolId: "graphql_api.query.hello", sourceId: "graphql_api", binding: { kind: "query", fieldName: "hello", operationString: "query { hello }" }, }, }), ).toEqual({ pluginId: "graphql", collection: "operation", key: "graphql_api.query.hello", data: { integration: "graphql_api", toolName: "query.hello", binding: { kind: "query", fieldName: "hello", operationString: "query { hello }" }, }, owner: "catalog", }); }); it("keeps source-owned plugin storage rows source-owned", () => { expect( migrateV1PluginStorageRuntimeRow({ scopeId: "org_X", pluginId: "onepassword", collection: "settings", key: "config", data: { vaultId: "vault_123" }, }), ).toEqual({ pluginId: "onepassword", collection: "settings", key: "config", data: { vaultId: "vault_123" }, owner: "source", }); }); }); describe("migrateOpenApiAuthTemplate", () => { it("maps a single Bearer apiKey header to one apiKey method (prefix preserved)", () => { const r = migrateOpenApiAuthTemplate({ headers: { Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, }); expect(r.authenticationTemplate).toEqual([ { slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", headers: { Authorization: ["Bearer ", { type: "variable", name: "token" }] }, }, ]); expect(r.slotToTemplateSlug).toEqual({ "header:authorization": API_KEY_TEMPLATE_SLUG }); expect(r.slotToVariable).toEqual({ "header:authorization": "token" }); expect(r.staticHeaders).toEqual({}); }); it("a prefix-less apiKey header renders a bare [token]", () => { const r = migrateOpenApiAuthTemplate({ headers: { "X-Api-Key": { kind: "binding", slot: "header:x-api-key" } }, }); expect(r.authenticationTemplate[0]).toEqual({ slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", headers: { "X-Api-Key": [{ type: "variable", name: "token" }] }, }); }); it("a query-param api key lands in queryParams, not headers", () => { const r = migrateOpenApiAuthTemplate({ queryParams: { key: { kind: "binding", slot: "query_param:key" } }, }); expect(r.authenticationTemplate[0]).toEqual({ slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", queryParams: { key: [{ type: "variable", name: "token" }] }, }); expect(r.slotToTemplateSlug).toEqual({ "query_param:key": API_KEY_TEMPLATE_SLUG }); }); it("literal-string headers pass through as static, never as credentials", () => { const r = migrateOpenApiAuthTemplate({ headers: { "User-Agent": "executor/1.0", Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, }); expect(r.staticHeaders).toEqual({ "User-Agent": "executor/1.0" }); expect(r.authenticationTemplate).toHaveLength(1); // The lone credential placement stays the canonical `token`. expect(r.slotToVariable).toEqual({ "header:authorization": "token" }); }); it("converts oauth2 to an oauth method keyed on its security-scheme slug", () => { const r = migrateOpenApiAuthTemplate({ oauth2: { securitySchemeName: "googleOAuth2", flow: "authorizationCode", authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", scopes: ["https://www.googleapis.com/auth/calendar"], }, }); expect(r.authenticationTemplate).toEqual([ { slug: "googleOAuth2", type: "oauth", authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", scopes: ["https://www.googleapis.com/auth/calendar"], }, ]); expect(r.slotToTemplateSlug).toEqual({ "oauth2:googleoauth2:connection": "googleOAuth2" }); }); it("maps hyphenated legacy oauth slot names to underscored oauth slugs", () => { const r = migrateOpenApiAuthTemplate({ oauth2: { securitySchemeName: "oauth_2_0", flow: "authorizationCode", authorizationUrl: "https://accounts.spotify.com/authorize", tokenUrl: "https://accounts.spotify.com/api/token", scopes: ["user-read-email"], }, }); expect(r.slotToTemplateSlug["oauth2:oauth-2-0:connection"]).toBe("oauth_2_0"); expect(r.slotToVariable["oauth2:oauth-2-0:connection"]).toBe("token"); }); it("a source offering both apiKey and oauth declares both methods", () => { const r = migrateOpenApiAuthTemplate({ headers: { Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, oauth2: { securitySchemeName: "oauth2", tokenUrl: "https://example.com/token", scopes: [], }, }); expect(r.authenticationTemplate.map((m) => m.type)).toEqual(["apiKey", "oauth"]); }); it("gives two distinct credential placements (Datadog) their own variables", () => { const r = migrateOpenApiAuthTemplate({ headers: { "DD-API-KEY": { kind: "binding", slot: "header:dd-api-key" }, "DD-APPLICATION-KEY": { kind: "binding", slot: "header:dd-application-key" }, }, }); expect(r.authenticationTemplate).toEqual([ { slug: API_KEY_TEMPLATE_SLUG, type: "apiKey", headers: { "DD-API-KEY": [{ type: "variable", name: "dd_api_key" }], "DD-APPLICATION-KEY": [{ type: "variable", name: "dd_application_key" }], }, }, ]); expect(r.slotToVariable).toEqual({ "header:dd-api-key": "dd_api_key", "header:dd-application-key": "dd_application_key", }); expect(r.warnings).toEqual([]); }); it("an auth-less source yields an empty template", () => { const r = migrateOpenApiAuthTemplate({}); expect(r.authenticationTemplate).toEqual([]); expect(r.slotToTemplateSlug).toEqual({}); expect(r.slotToVariable).toEqual({}); }); }); describe("migrateGrant", () => { it("maps client-credentials to client_credentials, everything else to authorization_code", () => { expect(migrateGrant("client-credentials")).toBe("client_credentials"); expect(migrateGrant("authorization-code")).toBe("authorization_code"); expect(migrateGrant("dynamic-dcr")).toBe("authorization_code"); }); }); describe("migrateExpiresAt (C1a)", () => { const now = 1_700_000_000_000; it("synthesizes a 1h expiry for a client_credentials connection with null v1 expiry", () => { expect(migrateExpiresAt({ grant: "client_credentials", v1ExpiresAt: null, nowMs: now })).toBe( now + SYNTHETIC_CLIENT_CREDENTIALS_TTL_MS, ); expect(SYNTHETIC_CLIENT_CREDENTIALS_TTL_MS).toBe(60 * 60 * 1000); }); it("keeps a real v1 expiry for client_credentials (only backfills the null case)", () => { expect(migrateExpiresAt({ grant: "client_credentials", v1ExpiresAt: 123, nowMs: now })).toBe( 123, ); }); it("never synthesizes for authorization_code (null stays null — re-auth on use)", () => { expect( migrateExpiresAt({ grant: "authorization_code", v1ExpiresAt: null, nowMs: now }), ).toBeNull(); expect(migrateExpiresAt({ grant: "authorization_code", v1ExpiresAt: 456, nowMs: now })).toBe( 456, ); }); }); describe("migrateOpenApiSourceConfig", () => { it("copies structural fields, drops namespace, and converts auth to a template", () => { const r = migrateOpenApiSourceConfig({ spec: "{openapi}", sourceUrl: "https://api.example.com/openapi.json", baseUrl: "https://api.example.com", headers: { "User-Agent": "executor/1.0", Authorization: { kind: "binding", slot: "header:authorization", prefix: "Bearer " }, }, }); const config = r.config as { readonly spec?: string; readonly sourceUrl?: string; readonly baseUrl?: string; readonly headers?: Record<string, string>; readonly authenticationTemplate?: unknown; }; expect(config.spec).toBe("{openapi}"); expect(config.sourceUrl).toBe("https://api.example.com/openapi.json"); expect(config.baseUrl).toBe("https://api.example.com"); // The literal header is static config; the credential header became a template. expect(config.headers).toEqual({ "User-Agent": "executor/1.0" }); expect(config.authenticationTemplate).toEqual([ { slug: API_KEY_TEMPLATE_SLUG, type: "apiKey",