| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- import { beforeEach, expect } from "bun:test"
- import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
- import { Database } from "@opencode-ai/core/database/database"
- import { makeMemoryDriver } from "@opencode-ai/core/environment"
- import { Workspace } from "@opencode-ai/core/workspace"
- import { WorkspaceDriver } from "@opencode-ai/core/workspace/driver"
- import { WorkspaceTable } from "@opencode-ai/core/workspace/sql"
- import { LayerNode } from "@opencode-ai/util/effect/layer-node"
- import { eq } from "drizzle-orm"
- import { Effect } from "effect"
- import { TestClock } from "effect/testing"
- import { ChildProcess } from "effect/unstable/process"
- import { testEffect } from "./lib/effect"
- const calls: Array<{ readonly operation: string; readonly binding?: WorkspaceDriver.Binding }> = []
- const memory = makeMemoryDriver()
- let failConnect = false
- const driver = WorkspaceDriver.make({
- create: ({ workspaceID }) => {
- calls.push({ operation: "create" })
- return Effect.succeed({ binding: { workspaceID, generation: 0 } })
- },
- connect: ({ binding }) => {
- calls.push({ operation: "connect", binding })
- if (failConnect) return Effect.fail(new WorkspaceDriver.Error({ message: "wake failed" }))
- return Effect.succeed(memory)
- },
- suspendForIdle: ({ binding, saveBinding }) => {
- calls.push({ operation: "suspendForIdle", binding })
- return saveBinding({ ...binding, generation: Number(binding.generation) + 1, suspended: true })
- },
- destroy: ({ binding }) => {
- calls.push({ operation: "destroy", binding })
- return Effect.void
- },
- })
- const it = testEffect(
- AppNodeBuilder.build(
- LayerNode.group([Database.node, Workspace.configured({ idleThreshold: "5 minutes", pollInterval: "1 minute" })]),
- [[WorkspaceDriver.node, WorkspaceDriver.registryNode({ fake: driver })]],
- ),
- )
- beforeEach(() => {
- calls.splice(0)
- failConnect = false
- })
- it.effect("persists the workspace lifecycle and reconnects after idle suspension", () =>
- Effect.gen(function* () {
- const workspace = yield* Workspace.Service
- const created = yield* workspace.create("fake")
- expect(created.id.startsWith("wrk_")).toBe(true)
- expect(created.binding).toEqual({ workspaceID: created.id, generation: 0 })
- const environment = yield* workspace.connect(created.id)
- expect(calls.map((call) => call.operation)).toEqual(["create"])
- yield* TestClock.adjust("4 minutes")
- yield* Effect.scoped(environment.spawner.spawn(ChildProcess.make("activity"))).pipe(Effect.exit)
- yield* TestClock.adjust("4 minutes")
- expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
- yield* TestClock.adjust("2 minutes")
- expect(calls.map((call) => call.operation)).toEqual(["create", "connect", "suspendForIdle"])
- const stored = yield* Database.Service.use(({ db }) =>
- db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, created.id)).get(),
- ).pipe(Effect.orDie)
- expect(stored?.binding).toEqual({ workspaceID: created.id, generation: 1, suspended: true })
- expect(stored?.last_used_at).toBe(4 * 60 * 1000)
- yield* Effect.scoped(environment.spawner.spawn(ChildProcess.make("wake"))).pipe(Effect.exit)
- expect(calls.map((call) => call.operation)).toEqual(["create", "connect", "suspendForIdle", "connect"])
- expect(calls.at(-1)?.binding).toEqual({ workspaceID: created.id, generation: 1, suspended: true })
- yield* workspace.destroy(created.id)
- expect(calls.at(-1)?.operation).toBe("destroy")
- }),
- )
- it.effect("surfaces wake failures through the spawn error channel", () =>
- Effect.gen(function* () {
- const workspace = yield* Workspace.Service
- const created = yield* workspace.create("fake")
- const environment = yield* workspace.connect(created.id)
- yield* Effect.scoped(environment.spawner.spawn(ChildProcess.make("connect"))).pipe(Effect.exit)
- yield* TestClock.adjust("6 minutes")
- failConnect = true
- const error = yield* Effect.scoped(environment.spawner.spawn(ChildProcess.make("wake"))).pipe(Effect.flip)
- expect(error).toMatchObject({
- _tag: "PlatformError",
- reason: {
- _tag: "Unknown",
- module: "Workspace",
- method: "spawn",
- description: `Failed to wake workspace ${created.id}`,
- },
- })
- }),
- )
|