Files

89 lines
3.5 KiB
TypeScript

import { expect } from "bun:test"
import { SdkPlugins } from "@opencode/core/plugin/sdk"
import { Plugin } from "@opencode/plugin/effect"
import { Context, Deferred, 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(
"catalog reads answer while plugins are still activating",
() =>
Effect.gen(function* () {
const tmp = yield* tmpdirScoped("opencode-provider-endpoints-")
const started = yield* Deferred.make<void>()
const release = yield* Deferred.make<void>()
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: {} },
},
},
agents: { reviewer: { description: "Reviews changes" } },
}),
},
}).pipe(Layer.provide(HttpServer.layerServices)),
)
const sdk = Context.get(context, SdkPlugins.Service)
yield* sdk.register(
Plugin.define({
id: "slow-plugin",
effect: () =>
Effect.gen(function* () {
yield* Deferred.succeed(started, undefined)
yield* Deferred.await(release)
}),
}),
)
const handler = Context.get(context, HttpRouter.HttpRouter)
.asHttpEffect()
.pipe(HttpEffect.toWebHandlerWith(context))
const request = (method: "GET" | "POST", route: string) =>
Effect.promise((signal) => {
const url = new URL(route, "http://opencode.local")
url.searchParams.set("location[directory]", tmp.path)
return handler(
new Request(url, {
method,
headers: { authorization: `Basic ${btoa("opencode:secret")}` },
signal,
}),
)
})
// Config providers and agents activate after SDK plugins. Reads return the current snapshot without waiting,
// and clients refresh on the matching *.updated events.
yield* request("GET", "/api/provider").pipe(Effect.timeout("2 seconds"))
yield* Deferred.await(started)
for (const route of ["/api/provider", "/api/agent", "/api/model", "/api/skill", "/api/integration"]) {
const response = yield* request("GET", route).pipe(Effect.timeout("2 seconds"))
expect(response.status).toBe(200)
}
const list = yield* request("GET", "/api/provider").pipe(Effect.timeout("2 seconds"))
expect(yield* Effect.promise(() => list.json())).toMatchObject({
location: { directory: tmp.path },
data: expect.not.arrayContaining([expect.objectContaining({ id: "custom" })]),
})
const get = yield* request("GET", "/api/provider/custom").pipe(Effect.timeout("2 seconds"))
expect(get.status).toBe(404)
expect(yield* Effect.promise(() => get.json())).toMatchObject({
_tag: "ProviderNotFoundError",
providerID: "custom",
})
yield* Deferred.succeed(release, undefined)
}),
15_000,
)