test(mcp): migrate OAuth auto-connect tests (#27356)

This commit is contained in:
Kit Langton
2026-05-13 16:38:37 +00:00
committed by GitHub
parent f0635e365f
commit 533495ae20
@@ -1,5 +1,6 @@
import { test, expect, mock, beforeEach } from "bun:test" import { expect, mock, beforeEach } from "bun:test"
import { Effect } from "effect" import { Effect, Layer } from "effect"
import { testEffect } from "../lib/effect"
// Mock UnauthorizedError to match the SDK's class // Mock UnauthorizedError to match the SDK's class
class MockUnauthorizedError extends Error { class MockUnauthorizedError extends Error {
@@ -111,39 +112,44 @@ beforeEach(() => {
// Import modules after mocking // Import modules after mocking
const { MCP } = await import("../../src/mcp/index") const { MCP } = await import("../../src/mcp/index")
const { Instance } = await import("../../src/project/instance") const { Bus } = await import("../../src/bus")
const { WithInstance } = await import("../../src/project/with-instance") const { Config } = await import("../../src/config/config")
const { tmpdir } = await import("../fixture/fixture") const { McpAuth } = await import("../../src/mcp/auth")
const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider")
const { AppFileSystem } = await import("@opencode-ai/core/filesystem")
const { CrossSpawnSpawner } = await import("@opencode-ai/core/cross-spawn-spawner")
test("first connect to OAuth server shows needs_auth instead of failed", async () => { const mcpTest = testEffect(
await using tmp = await tmpdir({ Layer.mergeAll(
init: async (dir) => { MCP.layer.pipe(
await Bun.write( Layer.provide(McpAuth.defaultLayer),
`${dir}/opencode.json`, Layer.provideMerge(Bus.layer),
JSON.stringify({ Layer.provide(Config.defaultLayer),
$schema: "https://opencode.ai/config.json", Layer.provide(CrossSpawnSpawner.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),
),
McpAuth.defaultLayer,
),
)
const config = (name: string) => ({
mcp: { mcp: {
"test-oauth": { [name]: {
type: "remote", type: "remote" as const,
url: "https://example.com/mcp", url: "https://example.com/mcp",
}, },
}, },
}),
)
},
}) })
await WithInstance.provide({ mcpTest.instance(
directory: tmp.path, "first connect to OAuth server shows needs_auth instead of failed",
fn: async () => { () =>
const result = await Effect.runPromise(
MCP.Service.use((mcp) => MCP.Service.use((mcp) =>
mcp.add("test-oauth", { Effect.gen(function* () {
const result = yield* mcp.add("test-oauth", {
type: "remote", type: "remote",
url: "https://example.com/mcp", url: "https://example.com/mcp",
}), })
).pipe(Effect.provide(MCP.defaultLayer)),
)
const serverStatus = result.status as Record<string, { status: string; error?: string }> const serverStatus = result.status as Record<string, { status: string; error?: string }>
@@ -153,24 +159,14 @@ test("first connect to OAuth server shows needs_auth instead of failed", async (
// not caught as UnauthorizedError, causing status to be "failed". // not caught as UnauthorizedError, causing status to be "failed".
expect(serverStatus["test-oauth"]).toBeDefined() expect(serverStatus["test-oauth"]).toBeDefined()
expect(serverStatus["test-oauth"].status).toBe("needs_auth") expect(serverStatus["test-oauth"].status).toBe("needs_auth")
}, }),
}) ),
}) { config: config("test-oauth") },
test("state() generates a new state when none is saved", async () => {
const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider")
const { McpAuth } = await import("../../src/mcp/auth")
await using tmp = await tmpdir()
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const auth = await Effect.runPromise(
Effect.gen(function* () {
return yield* McpAuth.Service
}).pipe(Effect.provide(McpAuth.defaultLayer)),
) )
mcpTest.instance("state() generates a new state when none is saved", () =>
Effect.gen(function* () {
const auth = yield* McpAuth.Service
const provider = new McpOAuthProvider( const provider = new McpOAuthProvider(
"test-state-gen", "test-state-gen",
"https://example.com/mcp", "https://example.com/mcp",
@@ -179,39 +175,23 @@ test("state() generates a new state when none is saved", async () => {
auth, auth,
) )
const entryBefore = await Effect.runPromise( const entryBefore = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
McpAuth.Service.use((auth) => auth.get("test-state-gen")).pipe(Effect.provide(McpAuth.defaultLayer)),
)
expect(entryBefore?.oauthState).toBeUndefined() expect(entryBefore?.oauthState).toBeUndefined()
// state() should generate and return a new state, not throw // state() should generate and return a new state, not throw
const state = await provider.state() const state = yield* Effect.promise(() => provider.state())
expect(typeof state).toBe("string") expect(typeof state).toBe("string")
expect(state.length).toBe(64) // 32 bytes as hex expect(state.length).toBe(64) // 32 bytes as hex
// The generated state should be persisted // The generated state should be persisted
const entryAfter = await Effect.runPromise( const entryAfter = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
McpAuth.Service.use((auth) => auth.get("test-state-gen")).pipe(Effect.provide(McpAuth.defaultLayer)),
)
expect(entryAfter?.oauthState).toBe(state) expect(entryAfter?.oauthState).toBe(state)
}, }),
})
})
test("state() returns existing state when one is saved", async () => {
const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider")
const { McpAuth } = await import("../../src/mcp/auth")
await using tmp = await tmpdir()
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
const auth = await Effect.runPromise(
Effect.gen(function* () {
return yield* McpAuth.Service
}).pipe(Effect.provide(McpAuth.defaultLayer)),
) )
mcpTest.instance("state() returns existing state when one is saved", () =>
Effect.gen(function* () {
const auth = yield* McpAuth.Service
const provider = new McpOAuthProvider( const provider = new McpOAuthProvider(
"test-state-existing", "test-state-existing",
"https://example.com/mcp", "https://example.com/mcp",
@@ -222,41 +202,17 @@ test("state() returns existing state when one is saved", async () => {
// Pre-save a state // Pre-save a state
const existingState = "pre-saved-state-value" const existingState = "pre-saved-state-value"
await Effect.runPromise( yield* McpAuth.Service.use((auth) => auth.updateOAuthState("test-state-existing", existingState))
McpAuth.Service.use((auth) => auth.updateOAuthState("test-state-existing", existingState)).pipe(
Effect.provide(McpAuth.defaultLayer),
),
)
// state() should return the existing state // state() should return the existing state
const state = await provider.state() const state = yield* Effect.promise(() => provider.state())
expect(state).toBe(existingState) expect(state).toBe(existingState)
},
})
})
test("authenticate() stores a connected client when auth completes without redirect", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
`${dir}/opencode.json`,
JSON.stringify({
$schema: "https://opencode.ai/config.json",
mcp: {
"test-oauth-connect": {
type: "remote",
url: "https://example.com/mcp",
},
},
}), }),
) )
},
})
await WithInstance.provide({ mcpTest.instance(
directory: tmp.path, "authenticate() stores a connected client when auth completes without redirect",
fn: async () => { () =>
await Effect.runPromise(
MCP.Service.use((mcp) => MCP.Service.use((mcp) =>
Effect.gen(function* () { Effect.gen(function* () {
const added = yield* mcp.add("test-oauth-connect", { const added = yield* mcp.add("test-oauth-connect", {
@@ -275,8 +231,6 @@ test("authenticate() stores a connected client when auth completes without redir
const after = yield* mcp.status() const after = yield* mcp.status()
expect(after["test-oauth-connect"]?.status).toBe("connected") expect(after["test-oauth-connect"]?.status).toBe("connected")
}), }),
).pipe(Effect.provide(MCP.defaultLayer)), ),
{ config: config("test-oauth-connect") },
) )
},
})
})