Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/opencode/test/fixture/flag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { WorkspaceID } from "@/control-plane/schema"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Effect, Scope } from "effect"

/**
* Scoped override for `Flag.OPENCODE_WORKSPACE_ID`. Saves the previous value
* on entry and restores it via finalizer when the surrounding scope closes —
* preserves the original try/finally semantics regardless of test outcome.
*/
export function withFixedWorkspaceID(id: WorkspaceID): Effect.Effect<void, never, Scope.Scope> {
return Effect.gen(function* () {
const previous = Flag.OPENCODE_WORKSPACE_ID
Flag.OPENCODE_WORKSPACE_ID = id
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
Flag.OPENCODE_WORKSPACE_ID = previous
}),
)
})
}
71 changes: 63 additions & 8 deletions packages/opencode/test/server/httpapi-instance-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { instanceRouterMiddleware } from "../../src/server/routes/instance/httpa
import { workspaceRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/workspace-routing"
import { resetDatabase } from "../fixture/db"
import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
import { withFixedWorkspaceID } from "../fixture/flag"
import { waitGlobalBusEvent } from "./global-bus"
import { testEffect } from "../lib/effect"

Expand Down Expand Up @@ -204,16 +205,10 @@ describe("HttpApi instance context middleware", () => {
}),
)

it.live("uses configured workspace id instead of routing to requested workspaces", () =>
it.live("uses configured workspace id instead of routing to the requested workspace", () =>
Effect.gen(function* () {
const originalWorkspaceID = Flag.OPENCODE_WORKSPACE_ID
const fixedWorkspaceID = WorkspaceID.ascending()
Flag.OPENCODE_WORKSPACE_ID = fixedWorkspaceID
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
Flag.OPENCODE_WORKSPACE_ID = originalWorkspaceID
}),
)
yield* withFixedWorkspaceID(fixedWorkspaceID)

const dir = yield* tmpdirScoped({ git: true })
const project = yield* Project.use.fromDirectory(dir)
Expand All @@ -238,6 +233,66 @@ describe("HttpApi instance context middleware", () => {
}),
)

it.live("falls through to local instead of MissingWorkspace when configured workspace id is set", () =>
Effect.gen(function* () {
const fixedWorkspaceID = WorkspaceID.ascending()
yield* withFixedWorkspaceID(fixedWorkspaceID)

const dir = yield* tmpdirScoped({ git: true })
yield* Project.use.fromDirectory(dir)
yield* serveProbe()

// Reference a workspace id that is not registered locally. Without the
// configured env override, this would short-circuit to a 500
// MissingWorkspace response. With the env set, planRequest must skip the
// MissingWorkspace branch and fall through to Local with the configured
// workspace id.
const unknownWorkspaceID = WorkspaceID.ascending()
const response = yield* HttpClientRequest.get(`/probe?workspace=${unknownWorkspaceID}`).pipe(
HttpClientRequest.setHeader("x-opencode-directory", dir),
HttpClient.execute,
)

expect(response.status).toBe(200)
expect(yield* response.json).toMatchObject({
directory: dir,
workspaceID: fixedWorkspaceID,
})
}),
)

it.live("keeps configured workspace id on control-plane routes without remote routing", () =>
Effect.gen(function* () {
const fixedWorkspaceID = WorkspaceID.ascending()
yield* withFixedWorkspaceID(fixedWorkspaceID)

const dir = yield* tmpdirScoped({ git: true })
const project = yield* Project.use.fromDirectory(dir)
const workspaceDir = path.join(dir, ".workspace-local")
const workspace = yield* createLocalWorkspace({
projectID: project.project.id,
type: "instance-context-fixed-workspace-control-plane",
directory: workspaceDir,
})
// /session is matched by isLocalWorkspaceRoute, so shouldStayOnControlPlane
// is true. Combined with the env override, the route must stay Local with
// the configured workspace id (not divert to the requested workspace's
// local directory).
yield* serveProbe("/session")

const response = yield* HttpClientRequest.get(`/session?workspace=${workspace.id}`).pipe(
HttpClientRequest.setHeader("x-opencode-directory", dir),
HttpClient.execute,
)

expect(response.status).toBe(200)
expect(yield* response.json).toMatchObject({
directory: dir,
workspaceID: fixedWorkspaceID,
})
}),
)

it.live("preserves selected workspace id on instance disposal events", () =>
Effect.gen(function* () {
const dir = yield* tmpdirScoped({ git: true })
Expand Down
Loading