import { describe, expect, it } from "@effect/vitest"; import { migrationOAuthAuthorizationUrlFor, migrationOAuthClientAuthorizationUrlResolutionSource, migrationOAuthClientNeedsAuthorizationUrlResolution, migrationOAuthClientPlanKey, resolveMigrationOAuthAuthorizationUrls, type MigrationOAuthMetadataFetch, type MigrationPlan, } from "./migration-spec"; type OAuthClient = MigrationPlan["oauthClients"][number]; const ownerKeys = { owner: "user" as const, subject: "user_1", tenant: "org_1" }; const oauthClient = (overrides: Partial = {}): OAuthClient => ({ ownerKeys, clientId: "client-id", tokenUrl: "https://oauth.example.com/token", authorizationUrl: "https://oauth.example.com/authorize", grant: "authorization_code", resource: null, clientSecretRef: null, slug: "oauth", clientSecretItemId: null, ...overrides, }); const migrationPlan = (oauthClients: readonly OAuthClient[]): MigrationPlan => ({ integrations: [], oauthClients, connections: [], secretOps: [], policies: [], report: { integrations: 0, oauthClients: oauthClients.length, connections: 0, secretOps: 0, staleConnections: 0, policies: { ok: 0, static: 0, deadInert: 0 }, warnings: [], }, }); const jsonResponse = ( body: unknown, status = 200, ): Awaited> => ({ ok: status >= 200 && status < 300, status, json: async () => body, }); const fetchFixture = ( responses: Readonly>, ): { readonly fetch: MigrationOAuthMetadataFetch; readonly seen: readonly string[]; } => { const seen: string[] = []; return { seen, fetch: async (input) => { seen.push(input); const response = responses[input]; if (!response) return jsonResponse({ error: "not_found" }, 404); return jsonResponse(response.body, response.status ?? 200); }, }; }; describe("resolveMigrationOAuthAuthorizationUrls", () => { it("uses an archived authorization-server metadata URL when present", async () => { const metadataUrl = "https://mcp.pscale.dev/.well-known/oauth-authorization-server/mcp/planetscale"; const client = oauthClient({ slug: "planetscale", authorizationUrl: "https://mcp.pscale.dev/mcp/planetscale", authorizationServerMetadataUrl: metadataUrl, resource: "https://mcp.pscale.dev/mcp/planetscale", }); const fixture = fetchFixture({ [metadataUrl]: { body: { authorization_endpoint: "https://app.planetscale.com/oauth/authorize", }, }, }); const resolved = await resolveMigrationOAuthAuthorizationUrls(migrationPlan([client]), { fetch: fixture.fetch, }); expect(fixture.seen).toEqual([metadataUrl]); expect(resolved.get(migrationOAuthClientPlanKey(client))).toBe( "https://app.planetscale.com/oauth/authorize", ); expect(migrationOAuthAuthorizationUrlFor(client, resolved)).toBe( "https://app.planetscale.com/oauth/authorize", ); }); it("discovers an MCP authorization endpoint through protected-resource metadata", async () => { const client = oauthClient({ slug: "linear", authorizationUrl: "https://mcp.linear.example.com", tokenUrl: "https://mcp.linear.example.com/token", resource: "https://mcp.linear.example.com/mcp", }); const fixture = fetchFixture({ "https://mcp.linear.example.com/.well-known/oauth-protected-resource/mcp": { body: { authorization_servers: ["https://mcp.linear.example.com"] }, }, "https://mcp.linear.example.com/.well-known/oauth-authorization-server": { body: { authorization_endpoint: "https://mcp.linear.example.com/authorize", }, }, }); const resolved = await resolveMigrationOAuthAuthorizationUrls(migrationPlan([client]), { fetch: fixture.fetch, }); expect(fixture.seen).toEqual([ "https://mcp.linear.example.com/.well-known/oauth-protected-resource/mcp", "https://mcp.linear.example.com/.well-known/oauth-authorization-server", ]); expect(resolved.get(migrationOAuthClientPlanKey(client))).toBe( "https://mcp.linear.example.com/authorize", ); }); it("discovers an authorization endpoint from an issuer-root authorization URL", async () => { const client = oauthClient({ slug: "spotify", authorizationUrl: "https://accounts.example.com", tokenUrl: "https://accounts.example.com/api/token", }); const fixture = fetchFixture({ "https://accounts.example.com/.well-known/oauth-authorization-server": { body: { authorization_endpoint: "https://accounts.example.com/authorize", }, }, }); const resolved = await resolveMigrationOAuthAuthorizationUrls(migrationPlan([client]), { fetch: fixture.fetch, }); expect(resolved.get(migrationOAuthClientPlanKey(client))).toBe( "https://accounts.example.com/authorize", ); }); it("leaves an existing authorization endpoint unchanged when discovery cannot prove a replacement", async () => { const client = oauthClient({ slug: "apollo", authorizationUrl: "https://mcp.apollo.example.com/mcp/oauth_metadata/redirect_to_authorize", tokenUrl: "https://mcp.apollo.example.com/api/v1/oauth/token", }); const fixture = fetchFixture({}); const resolved = await resolveMigrationOAuthAuthorizationUrls(migrationPlan([client]), { fetch: fixture.fetch, }); expect(resolved.has(migrationOAuthClientPlanKey(client))).toBe(false); expect(migrationOAuthAuthorizationUrlFor(client, resolved)).toBe( "https://mcp.apollo.example.com/mcp/oauth_metadata/redirect_to_authorize", ); }); it("marks metadata, resource, and issuer-backed authorization-code clients as discoverable", () => { expect( migrationOAuthClientNeedsAuthorizationUrlResolution( oauthClient({ authorizationServerMetadataUrl: "https://oauth.example.com/metadata" }), ), ).toBe(true); expect( migrationOAuthClientNeedsAuthorizationUrlResolution( oauthClient({ resource: "https://mcp.example.com/mcp" }), ), ).toBe(true); expect(migrationOAuthClientNeedsAuthorizationUrlResolution(oauthClient())).toBe(true); expect( migrationOAuthClientNeedsAuthorizationUrlResolution( oauthClient({ grant: "client_credentials", authorizationUrl: "", resource: null }), ), ).toBe(false); expect( migrationOAuthClientAuthorizationUrlResolutionSource( oauthClient({ authorizationServerMetadataUrl: "", resource: "https://mcp.example.com/mcp", }), ), ).toBe("https://mcp.example.com/mcp"); }); });