test(server): migrate provider httpapi test to effect runner (#27216)

This commit is contained in:
Kit Langton
2026-05-13 02:05:52 +00:00
committed by GitHub
parent 7f9268f147
commit a26a2a95bd
@@ -1,18 +1,23 @@
import { afterEach, describe, expect } from "bun:test" import { describe, expect } from "bun:test"
import { Effect, FileSystem, Layer, Path } from "effect" import { Effect, FileSystem, Layer, Path } from "effect"
import { NodeFileSystem, NodePath } from "@effect/platform-node" import { NodeFileSystem, NodePath } from "@effect/platform-node"
import { Instance } from "../../src/project/instance"
import { WithInstance } from "../../src/project/with-instance"
import { InstanceRuntime } from "../../src/project/instance-runtime"
import { Server } from "../../src/server/server" import { Server } from "../../src/server/server"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { resetDatabase } from "../fixture/db" import { resetDatabase } from "../fixture/db"
import { disposeAllInstances, provideInstance } from "../fixture/fixture" import { TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"
void Log.init({ print: false }) void Log.init({ print: false })
const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)) const testStateLayer = Layer.effectDiscard(
Effect.acquireRelease(
Effect.promise(() => resetDatabase()),
() => Effect.promise(() => resetDatabase()),
),
)
const it = testEffect(Layer.mergeAll(testStateLayer, NodeFileSystem.layer, NodePath.layer))
const projectOptions = { config: { formatter: false, lsp: false } }
const providerID = "test-oauth-parity" const providerID = "test-oauth-parity"
const oauthURL = "https://example.com/oauth" const oauthURL = "https://example.com/oauth"
const oauthInstructions = "Finish OAuth" const oauthInstructions = "Finish OAuth"
@@ -191,91 +196,69 @@ function writeProviderModelsMutationPlugin(dir: string) {
}) })
} }
function withProviderProject<A, E, R>(self: (dir: string) => Effect.Effect<A, E, R>) { function setEnvScoped(key: string, value: string) {
return Effect.gen(function* () { return Effect.acquireRelease(
const fs = yield* FileSystem.FileSystem Effect.sync(() => {
const path = yield* Path.Path const previous = process.env[key]
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "opencode-test-" }) process.env[key] = value
return previous
yield* fs.writeFileString( }),
path.join(dir, "opencode.json"), (previous) =>
JSON.stringify({ $schema: "https://opencode.ai/config.json", formatter: false, lsp: false }), Effect.sync(() => {
) if (previous === undefined) delete process.env[key]
yield* writeProviderAuthPlugin(dir) else process.env[key] = previous
yield* Effect.addFinalizer(() => }),
Effect.promise(() => )
WithInstance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }),
).pipe(Effect.ignore),
)
return yield* self(dir).pipe(provideInstance(dir))
})
} }
afterEach(async () => {
await disposeAllInstances()
await resetDatabase()
})
describe("provider HttpApi", () => { describe("provider HttpApi", () => {
it.live( it.instance(
"serves OAuth authorize response shapes", "serves OAuth authorize response shapes",
withProviderProject((dir) => Effect.gen(function* () {
Effect.gen(function* () { const instance = yield* TestInstance
const headers = { "x-opencode-directory": dir, "content-type": "application/json" } yield* writeProviderAuthPlugin(instance.directory)
const server = app() const headers = { "x-opencode-directory": instance.directory, "content-type": "application/json" }
const server = app()
const api = yield* requestAuthorize({ const api = yield* requestAuthorize({
app: server, app: server,
providerID, providerID,
method: 0, method: 0,
headers, headers,
}) })
// method 0 (api-key style) — authorize() resolves with no further // method 0 (api-key style) — authorize() resolves with no further
// redirect; #26474 changed the wire format to JSON `null` so clients // redirect; #26474 changed the wire format to JSON `null` so clients
// can `.json()` parse uniformly instead of getting an empty body // can `.json()` parse uniformly instead of getting an empty body
// that throws. // that throws.
expect(api).toEqual({ status: 200, body: "null" }) expect(api).toEqual({ status: 200, body: "null" })
const oauth = yield* requestAuthorize({ const oauth = yield* requestAuthorize({
app: server, app: server,
providerID, providerID,
method: 1, method: 1,
headers, headers,
}) })
expect(JSON.parse(oauth.body)).toEqual({ expect(JSON.parse(oauth.body)).toEqual({
url: oauthURL, url: oauthURL,
method: "code", method: "code",
instructions: oauthInstructions, instructions: oauthInstructions,
}) })
}), }),
), projectOptions,
) )
it.live("serves provider lists when auth loaders add runtime fetch options", () => it.instance(
"serves provider lists when auth loaders add runtime fetch options",
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem const instance = yield* TestInstance
const path = yield* Path.Path yield* writeFunctionOptionsPlugin(instance.directory)
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "opencode-test-" }) yield* setEnvScoped(
const previous = process.env.OPENCODE_AUTH_CONTENT "OPENCODE_AUTH_CONTENT",
JSON.stringify({
yield* fs.writeFileString(
path.join(dir, "opencode.json"),
JSON.stringify({ $schema: "https://opencode.ai/config.json", formatter: false, lsp: false }),
)
yield* writeFunctionOptionsPlugin(dir)
yield* Effect.sync(() => {
process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({
google: { type: "oauth", refresh: "dummy", access: "dummy", expires: 9999999999999 }, google: { type: "oauth", refresh: "dummy", access: "dummy", expires: 9999999999999 },
})
})
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
if (previous === undefined) delete process.env.OPENCODE_AUTH_CONTENT
if (previous !== undefined) process.env.OPENCODE_AUTH_CONTENT = previous
}), }),
) )
const headers = { "x-opencode-directory": dir } const headers = { "x-opencode-directory": instance.directory }
const providerResponse = yield* Effect.promise(() => Promise.resolve(app().request("/provider", { headers }))) const providerResponse = yield* Effect.promise(() => Promise.resolve(app().request("/provider", { headers })))
const configResponse = yield* Effect.promise(() => const configResponse = yield* Effect.promise(() =>
Promise.resolve(app().request("/config/providers", { headers })), Promise.resolve(app().request("/config/providers", { headers })),
@@ -291,21 +274,16 @@ describe("provider HttpApi", () => {
expect(hasNonZeroModelCost(providerBody, "all", "google")).toBe(true) expect(hasNonZeroModelCost(providerBody, "all", "google")).toBe(true)
expect(hasNonZeroModelCost(configBody, "providers", "google")).toBe(true) expect(hasNonZeroModelCost(configBody, "providers", "google")).toBe(true)
}), }),
projectOptions,
) )
it.live("keeps provider.models hook input mutations out of provider state", () => it.instance(
"keeps provider.models hook input mutations out of provider state",
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem const instance = yield* TestInstance
const path = yield* Path.Path yield* writeProviderModelsMutationPlugin(instance.directory)
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "opencode-test-" })
yield* fs.writeFileString( const headers = { "x-opencode-directory": instance.directory }
path.join(dir, "opencode.json"),
JSON.stringify({ $schema: "https://opencode.ai/config.json", formatter: false, lsp: false }),
)
yield* writeProviderModelsMutationPlugin(dir)
const headers = { "x-opencode-directory": dir }
const providerResponse = yield* Effect.promise(() => Promise.resolve(app().request("/provider", { headers }))) const providerResponse = yield* Effect.promise(() => Promise.resolve(app().request("/provider", { headers })))
const configResponse = yield* Effect.promise(() => const configResponse = yield* Effect.promise(() =>
Promise.resolve(app().request("/config/providers", { headers })), Promise.resolve(app().request("/config/providers", { headers })),
@@ -320,5 +298,6 @@ describe("provider HttpApi", () => {
expect(hasProviderMutationMarker(configBody, "providers", "google")).toBe(false) expect(hasProviderMutationMarker(configBody, "providers", "google")).toBe(false)
expect(hasNonZeroModelCost(providerBody, "all", "google")).toBe(true) expect(hasNonZeroModelCost(providerBody, "all", "google")).toBe(true)
}), }),
projectOptions,
) )
}) })