Files
opencode/packages/server/test/integration-policy.test.ts

92 lines
4.2 KiB
TypeScript

import { expect } from "bun:test"
import { Context, Effect, Layer } from "effect"
import { HttpEffect, HttpRouter, HttpServer } from "effect/http"
import { tmpdirScoped } from "../../core/test/fixture/tmpdir"
import { it } from "../../core/test/lib/effect"
import { createRoutes } from "../src/routes"
it.live("integration policy filters /connect and blocks denied integrations and providers", () =>
Effect.gen(function* () {
const tmp = yield* tmpdirScoped("opencode-integration-policy-")
const context = yield* Layer.build(
createRoutes({
password: "secret",
database: { path: ":memory:" },
models: { fetch: false },
fs: { filewatcher: false },
config: {
directory: tmp.path,
project: false,
content: JSON.stringify({
providers: {
custom: {
name: "Configured Custom Provider",
package: "@opencode/ai/providers/openai-compatible",
settings: { apiKey: "secret" },
models: { chat: {} },
},
},
mcp: { servers: { wiki: { type: "remote", url: "http://127.0.0.1:9/mcp" } } },
policies: [
{ action: "integration.connect", resource: "*", effect: "deny" },
{ action: "integration.connect", resource: "opencode", effect: "allow" },
{ action: "integration.use", resource: "*", effect: "deny" },
{ action: "integration.use", resource: "opencode#org_01M3CAP8H43Z49CER6NT0Y3ZF1", effect: "allow" },
],
}),
},
}).pipe(Layer.provide(HttpServer.layerServices)),
)
const handler = Context.get(context, HttpRouter.HttpRouter)
.asHttpEffect()
.pipe(HttpEffect.toWebHandlerWith(context))
const request = (method: "GET" | "POST", route: string, body?: unknown) =>
Effect.promise(async (signal) => {
const url = new URL(route, "http://opencode.local")
url.searchParams.set("location[directory]", tmp.path)
const response = await handler(
new Request(url, {
method,
headers: {
authorization: `Basic ${btoa("opencode:secret")}`,
...(body === undefined ? {} : { "content-type": "application/json" }),
},
body: body === undefined ? undefined : JSON.stringify(body),
signal,
}),
)
return { status: response.status, body: response.status === 204 ? undefined : await response.json() }
})
const integrations = yield* request("GET", "/api/integration")
expect(integrations.status).toBe(200)
// MCP sign-ins are outside integration policy; `mcp.use` governs MCP servers.
const listed: { id: string; metadata?: { source?: string } }[] = integrations.body.data
expect(listed.filter((integration) => integration.metadata?.source !== "mcp").map((item) => item.id)).toEqual([
"opencode",
])
const mcp = listed.find((integration) => integration.metadata?.source === "mcp")
expect(mcp?.id).toStartWith("mcp_")
const signIn = yield* request("POST", `/api/integration/${encodeURIComponent(mcp?.id ?? "")}/connect/oauth`, {
methodID: mcp?.id,
})
expect(signIn.body?.kind).not.toBe("integration_policy")
const denied = yield* request("POST", "/api/integration/anthropic/connect/key", { key: "sk-test" })
expect(denied.status).toBe(400)
expect(denied.body).toMatchObject({ _tag: "InvalidRequestError", kind: "integration_policy" })
const credential = yield* request("POST", "/api/credential", {
integrationID: "anthropic",
value: { type: "key", key: "sk-test" },
})
expect(credential.status).toBe(400)
expect(credential.body).toMatchObject({ _tag: "InvalidRequestError", kind: "integration_policy" })
expect((yield* request("GET", "/api/credential")).body.data).toEqual([])
const wellknown = yield* request("POST", "/api/experimental/integration/wellknown", { url: "https://ai.acme.com" })
expect(wellknown.body).toMatchObject({ kind: "integration_policy" })
// The signed-out free tier and the authored provider are both denied.
expect((yield* request("GET", "/api/provider")).body.data).toEqual([])
}),
)