|
|
@@ -1,15 +1,28 @@
|
|
|
import { afterEach, describe, expect, test } from "bun:test"
|
|
|
-import { Context } from "effect"
|
|
|
+import type { UpgradeWebSocket } from "hono/ws"
|
|
|
+import { Context, Effect, FileSystem, Layer, Path } from "effect"
|
|
|
+import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
|
|
+import { Flag } from "@opencode-ai/core/flag/flag"
|
|
|
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
|
|
import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp"
|
|
|
import { Instance } from "../../src/project/instance"
|
|
|
+import { InstanceRoutes } from "../../src/server/routes/instance"
|
|
|
import * as Log from "@opencode-ai/core/util/log"
|
|
|
import { resetDatabase } from "../fixture/db"
|
|
|
-import { tmpdir } from "../fixture/fixture"
|
|
|
+import { provideInstance, tmpdir } from "../fixture/fixture"
|
|
|
+import { testEffect } from "../lib/effect"
|
|
|
|
|
|
void Log.init({ print: false })
|
|
|
|
|
|
+const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
|
|
const context = Context.empty() as Context.Context<unknown>
|
|
|
+const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket
|
|
|
+const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))
|
|
|
+
|
|
|
+function app(experimental: boolean) {
|
|
|
+ Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
|
|
|
+ return InstanceRoutes(websocket)
|
|
|
+}
|
|
|
|
|
|
function request(route: string, directory: string, init?: RequestInit) {
|
|
|
const headers = new Headers(init?.headers)
|
|
|
@@ -23,7 +36,51 @@ function request(route: string, directory: string, init?: RequestInit) {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+function withMcpProject<A, E, R>(self: (dir: string) => Effect.Effect<A, E, R>) {
|
|
|
+ return Effect.gen(function* () {
|
|
|
+ const fs = yield* FileSystem.FileSystem
|
|
|
+ const path = yield* Path.Path
|
|
|
+ const dir = yield* fs.makeTempDirectoryScoped({ prefix: "opencode-test-" })
|
|
|
+
|
|
|
+ yield* fs.writeFileString(
|
|
|
+ path.join(dir, "opencode.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ $schema: "https://opencode.ai/config.json",
|
|
|
+ formatter: false,
|
|
|
+ lsp: false,
|
|
|
+ mcp: {
|
|
|
+ demo: {
|
|
|
+ type: "local",
|
|
|
+ command: ["echo", "demo"],
|
|
|
+ enabled: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ yield* Effect.addFinalizer(() =>
|
|
|
+ Effect.promise(() => Instance.provide({ directory: dir, fn: () => Instance.dispose() })).pipe(Effect.ignore),
|
|
|
+ )
|
|
|
+
|
|
|
+ return yield* self(dir).pipe(provideInstance(dir))
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const readResponse = Effect.fnUntraced(function* (input: {
|
|
|
+ app: ReturnType<typeof InstanceRoutes>
|
|
|
+ path: string
|
|
|
+ headers: HeadersInit
|
|
|
+}) {
|
|
|
+ const response = yield* Effect.promise(() =>
|
|
|
+ Promise.resolve(input.app.request(input.path, { method: "POST", headers: input.headers })),
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ status: response.status,
|
|
|
+ body: yield* Effect.promise(() => response.text()),
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
afterEach(async () => {
|
|
|
+ Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original
|
|
|
await Instance.disposeAll()
|
|
|
await resetDatabase()
|
|
|
})
|
|
|
@@ -107,4 +164,28 @@ describe("mcp HttpApi", () => {
|
|
|
expect(removed.status).toBe(200)
|
|
|
expect(await removed.json()).toEqual({ success: true })
|
|
|
})
|
|
|
+
|
|
|
+ it.live(
|
|
|
+ "matches legacy unsupported OAuth error responses",
|
|
|
+ withMcpProject((dir) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const headers = { "x-opencode-directory": dir }
|
|
|
+ const legacy = app(false)
|
|
|
+ const httpapi = app(true)
|
|
|
+
|
|
|
+ yield* Effect.forEach(["/mcp/demo/auth", "/mcp/demo/auth/authenticate"], (path) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const legacyResponse = yield* readResponse({ app: legacy, path, headers })
|
|
|
+ const httpapiResponse = yield* readResponse({ app: httpapi, path, headers })
|
|
|
+
|
|
|
+ expect(legacyResponse).toEqual({
|
|
|
+ status: 400,
|
|
|
+ body: JSON.stringify({ error: "MCP server demo does not support OAuth" }),
|
|
|
+ })
|
|
|
+ expect(httpapiResponse).toEqual(legacyResponse)
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
})
|