diff --git a/packages/opencode/test/server/httpapi-provider.test.ts b/packages/opencode/test/server/httpapi-provider.test.ts index 68db6663d2..6ea664d636 100644 --- a/packages/opencode/test/server/httpapi-provider.test.ts +++ b/packages/opencode/test/server/httpapi-provider.test.ts @@ -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 { 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 * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, provideInstance } from "../fixture/fixture" +import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" 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 oauthURL = "https://example.com/oauth" const oauthInstructions = "Finish OAuth" @@ -191,91 +196,69 @@ function writeProviderModelsMutationPlugin(dir: string) { }) } -function withProviderProject(self: (dir: string) => Effect.Effect) { - 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 }), - ) - yield* writeProviderAuthPlugin(dir) - yield* Effect.addFinalizer(() => - Effect.promise(() => - WithInstance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }), - ).pipe(Effect.ignore), - ) - - return yield* self(dir).pipe(provideInstance(dir)) - }) +function setEnvScoped(key: string, value: string) { + return Effect.acquireRelease( + Effect.sync(() => { + const previous = process.env[key] + process.env[key] = value + return previous + }), + (previous) => + Effect.sync(() => { + if (previous === undefined) delete process.env[key] + else process.env[key] = previous + }), + ) } -afterEach(async () => { - await disposeAllInstances() - await resetDatabase() -}) - describe("provider HttpApi", () => { - it.live( + it.instance( "serves OAuth authorize response shapes", - withProviderProject((dir) => - Effect.gen(function* () { - const headers = { "x-opencode-directory": dir, "content-type": "application/json" } - const server = app() + Effect.gen(function* () { + const instance = yield* TestInstance + yield* writeProviderAuthPlugin(instance.directory) + const headers = { "x-opencode-directory": instance.directory, "content-type": "application/json" } + const server = app() - const api = yield* requestAuthorize({ - app: server, - providerID, - method: 0, - headers, - }) - // method 0 (api-key style) — authorize() resolves with no further - // redirect; #26474 changed the wire format to JSON `null` so clients - // can `.json()` parse uniformly instead of getting an empty body - // that throws. - expect(api).toEqual({ status: 200, body: "null" }) + const api = yield* requestAuthorize({ + app: server, + providerID, + method: 0, + headers, + }) + // method 0 (api-key style) — authorize() resolves with no further + // redirect; #26474 changed the wire format to JSON `null` so clients + // can `.json()` parse uniformly instead of getting an empty body + // that throws. + expect(api).toEqual({ status: 200, body: "null" }) - const oauth = yield* requestAuthorize({ - app: server, - providerID, - method: 1, - headers, - }) - expect(JSON.parse(oauth.body)).toEqual({ - url: oauthURL, - method: "code", - instructions: oauthInstructions, - }) - }), - ), + const oauth = yield* requestAuthorize({ + app: server, + providerID, + method: 1, + headers, + }) + expect(JSON.parse(oauth.body)).toEqual({ + url: oauthURL, + method: "code", + 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* () { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path - const dir = yield* fs.makeTempDirectoryScoped({ prefix: "opencode-test-" }) - const previous = process.env.OPENCODE_AUTH_CONTENT - - 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({ + const instance = yield* TestInstance + yield* writeFunctionOptionsPlugin(instance.directory) + yield* setEnvScoped( + "OPENCODE_AUTH_CONTENT", + JSON.stringify({ 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 configResponse = yield* Effect.promise(() => Promise.resolve(app().request("/config/providers", { headers })), @@ -291,21 +274,16 @@ describe("provider HttpApi", () => { expect(hasNonZeroModelCost(providerBody, "all", "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* () { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path - const dir = yield* fs.makeTempDirectoryScoped({ prefix: "opencode-test-" }) + const instance = yield* TestInstance + yield* writeProviderModelsMutationPlugin(instance.directory) - yield* fs.writeFileString( - 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 headers = { "x-opencode-directory": instance.directory } const providerResponse = yield* Effect.promise(() => Promise.resolve(app().request("/provider", { headers }))) const configResponse = yield* Effect.promise(() => Promise.resolve(app().request("/config/providers", { headers })), @@ -320,5 +298,6 @@ describe("provider HttpApi", () => { expect(hasProviderMutationMarker(configBody, "providers", "google")).toBe(false) expect(hasNonZeroModelCost(providerBody, "all", "google")).toBe(true) }), + projectOptions, ) })