mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-12 00:23:25 +00:00
106 lines
4.2 KiB
TypeScript
106 lines
4.2 KiB
TypeScript
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}`,
|
|
},
|
|
})
|
|
}),
|
|
)
|