mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-24 21:59:53 +00:00
543 lines
21 KiB
TypeScript
543 lines
21 KiB
TypeScript
import { beforeEach, expect } from "bun:test"
|
|
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
|
|
import { Database } from "@opencode/core/database/database"
|
|
import { Failed, makeFiles, makeMemoryDriver, NotFound } from "@opencode/core/environment/index"
|
|
import { Workspace } from "@opencode/core/workspace"
|
|
import { WorkspaceDriver } from "@opencode/core/workspace/driver"
|
|
import { WorkspaceTable } from "@opencode/core/workspace/sql"
|
|
import { LayerNode } from "@opencode/util/effect/layer-node"
|
|
import { eq } from "drizzle-orm"
|
|
import { Deferred, Effect, Fiber, Sink, Stream } from "effect"
|
|
import { TestClock } from "effect/testing"
|
|
import { ChildProcess } from "effect/process"
|
|
import { ExitCode, make as makeSpawner, makeHandle, ProcessId } from "effect/process/ChildProcessSpawner"
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
const calls: Array<{ readonly operation: string; readonly binding?: WorkspaceDriver.Binding | null }> = []
|
|
const memory = makeMemoryDriver()
|
|
let failConnect = false
|
|
let create: WorkspaceDriver.Interface["create"] = ({ workspaceID }) =>
|
|
Effect.succeed({ binding: { workspaceID, generation: 0 } })
|
|
let connectDriver: WorkspaceDriver.Interface["connect"] = () => Effect.succeed(memory)
|
|
|
|
const driver = WorkspaceDriver.make({
|
|
create: (input) => {
|
|
calls.push({ operation: "create" })
|
|
return create(input)
|
|
},
|
|
connect: (input) => {
|
|
calls.push({ operation: "connect", binding: input.binding })
|
|
if (failConnect) return Effect.fail(new WorkspaceDriver.Error({ message: "wake failed" }))
|
|
return connectDriver(input)
|
|
},
|
|
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.replace(WorkspaceDriver.registryNode({ fake: driver, other: driver }))],
|
|
),
|
|
)
|
|
|
|
beforeEach(() => {
|
|
calls.splice(0)
|
|
failConnect = false
|
|
create = ({ workspaceID }) => Effect.succeed({ binding: { workspaceID, generation: 0 } })
|
|
connectDriver = () => Effect.succeed(memory)
|
|
})
|
|
|
|
const gateCreate = Effect.fnUntraced(function* () {
|
|
const started = yield* Deferred.make<void>()
|
|
const release = yield* Deferred.make<void>()
|
|
create = ({ workspaceID }) =>
|
|
Deferred.succeed(started, undefined).pipe(
|
|
Effect.andThen(Deferred.await(release)),
|
|
Effect.as({ binding: { workspaceID, generation: 0 } }),
|
|
)
|
|
return { started, release }
|
|
})
|
|
|
|
it.effect("rejects unregistered workspace providers", () =>
|
|
Effect.gen(function* () {
|
|
const registry = WorkspaceDriver.registry({ fake: driver })
|
|
|
|
for (const provider of ["missing", "constructor", "toString", "__proto__"]) {
|
|
expect(yield* registry.get(provider).pipe(Effect.flip)).toEqual(
|
|
new WorkspaceDriver.ProviderNotFound({ provider }),
|
|
)
|
|
}
|
|
}),
|
|
)
|
|
|
|
it.effect("creates and persists an ID without provisioning", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
|
|
expect(workspaceID.startsWith("wrk_")).toBe(true)
|
|
expect(calls).toEqual([])
|
|
expect(
|
|
yield* Database.Service.use(({ db }) =>
|
|
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, workspaceID)).get(),
|
|
).pipe(Effect.orDie),
|
|
).toMatchObject({ id: workspaceID, provider: "fake", binding: null })
|
|
}),
|
|
)
|
|
|
|
it.effect("creates a workspace with a caller-supplied ID", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const id = Workspace.ID.create()
|
|
|
|
expect(yield* workspace.create({ id, provider: "fake" })).toBe(id)
|
|
expect(
|
|
yield* Database.Service.use(({ db }) =>
|
|
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get(),
|
|
).pipe(Effect.orDie),
|
|
).toMatchObject({ id, provider: "fake", binding: null })
|
|
}),
|
|
)
|
|
|
|
it.effect("reuses a caller-supplied ID with the same provider", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const id = Workspace.ID.create()
|
|
|
|
expect(yield* workspace.create({ id, provider: "fake" })).toBe(id)
|
|
expect(yield* workspace.create({ id, provider: "fake" })).toBe(id)
|
|
expect(
|
|
yield* Database.Service.use(({ db }) =>
|
|
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).all(),
|
|
).pipe(Effect.orDie),
|
|
).toHaveLength(1)
|
|
expect(calls).toEqual([])
|
|
}),
|
|
)
|
|
|
|
it.effect("rejects a caller-supplied ID already assigned to another provider", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const id = Workspace.ID.create()
|
|
yield* workspace.create({ id, provider: "fake" })
|
|
|
|
expect(yield* workspace.create({ id, provider: "other" }).pipe(Effect.flip)).toEqual(
|
|
new Workspace.CreateConflict({ workspaceID: id, provider: "other", existingProvider: "fake" }),
|
|
)
|
|
}),
|
|
)
|
|
|
|
it.effect("resolves an existing caller-supplied ID before provider lookup", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const id = Workspace.ID.create()
|
|
yield* Database.Service.use(({ db }) =>
|
|
db
|
|
.insert(WorkspaceTable)
|
|
.values({ id, provider: "missing", binding: null, created_at: 0, last_used_at: 0 })
|
|
.run(),
|
|
).pipe(Effect.orDie)
|
|
|
|
expect(yield* workspace.create({ id, provider: "missing" })).toBe(id)
|
|
expect(yield* workspace.create({ id, provider: "another-missing" }).pipe(Effect.flip)).toEqual(
|
|
new Workspace.CreateConflict({
|
|
workspaceID: id,
|
|
provider: "another-missing",
|
|
existingProvider: "missing",
|
|
}),
|
|
)
|
|
}),
|
|
)
|
|
|
|
it.effect("destroys an unprovisioned workspace through the driver with a null binding", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
|
|
expect(yield* workspace.destroy(workspaceID)).toEqual({ destroyed: true })
|
|
expect(calls).toEqual([{ operation: "destroy", binding: null }])
|
|
expect(
|
|
yield* Database.Service.use(({ db }) =>
|
|
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, workspaceID)).get(),
|
|
).pipe(Effect.orDie),
|
|
).toBeUndefined()
|
|
}),
|
|
)
|
|
|
|
it.effect("succeeds without calling the driver when the workspace does not exist", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = Workspace.ID.create()
|
|
|
|
expect(yield* workspace.destroy(workspaceID)).toEqual({ destroyed: false })
|
|
expect(calls).toEqual([])
|
|
}),
|
|
)
|
|
|
|
it.effect("reports whether destroy removed an existing workspace", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
|
|
expect(yield* workspace.destroy(workspaceID)).toEqual({ destroyed: true })
|
|
expect(yield* workspace.destroy(workspaceID)).toEqual({ destroyed: false })
|
|
expect(calls).toEqual([{ operation: "destroy", binding: null }])
|
|
}),
|
|
)
|
|
|
|
it.effect("starts eager provisioning in the background and lets callers join it", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const gate = yield* gateCreate()
|
|
|
|
const eager = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Deferred.await(gate.started)
|
|
const waiter = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Effect.yieldNow
|
|
expect(calls.map((call) => call.operation)).toEqual(["create"])
|
|
|
|
yield* Deferred.succeed(gate.release, undefined)
|
|
const [eagerResult, waiterResult] = yield* Effect.all([Fiber.join(eager), Fiber.join(waiter)])
|
|
expect(eagerResult).toEqual(waiterResult)
|
|
expect(eagerResult.binding).toEqual({ workspaceID, generation: 0 })
|
|
}),
|
|
)
|
|
|
|
it.effect("starts lazy provisioning on the first spawn", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const environment = yield* workspace.connect(workspaceID)
|
|
const gate = yield* gateCreate()
|
|
|
|
expect(calls).toEqual([])
|
|
const spawned = yield* Effect.scoped(environment.spawner.spawn(ChildProcess.make("lazy"))).pipe(
|
|
Effect.forkScoped({ startImmediately: true }),
|
|
)
|
|
yield* Deferred.await(gate.started)
|
|
expect(calls.map((call) => call.operation)).toEqual(["create"])
|
|
yield* Deferred.succeed(gate.release, undefined)
|
|
yield* Fiber.await(spawned)
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
}),
|
|
)
|
|
|
|
it.effect("shares provisioning between concurrent first spawns", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const environment = yield* workspace.connect(workspaceID)
|
|
const gate = yield* gateCreate()
|
|
|
|
const spawned = yield* Effect.all(
|
|
["first", "second"].map((command) =>
|
|
Effect.scoped(environment.spawner.spawn(ChildProcess.make(command))).pipe(
|
|
Effect.forkScoped({ startImmediately: true }),
|
|
),
|
|
),
|
|
)
|
|
yield* Deferred.await(gate.started)
|
|
yield* Effect.yieldNow
|
|
expect(calls.map((call) => call.operation)).toEqual(["create"])
|
|
|
|
yield* Deferred.succeed(gate.release, undefined)
|
|
yield* Effect.forEach(spawned, Fiber.await)
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
}),
|
|
)
|
|
|
|
it.effect("keeps shared provisioning alive when a waiter is interrupted", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const gate = yield* gateCreate()
|
|
|
|
const owner = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Deferred.await(gate.started)
|
|
const waiter = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Fiber.interrupt(waiter)
|
|
expect(calls.map((call) => call.operation)).toEqual(["create"])
|
|
|
|
yield* Deferred.succeed(gate.release, undefined)
|
|
expect((yield* Fiber.join(owner)).binding).toEqual({ workspaceID, generation: 0 })
|
|
expect(calls.map((call) => call.operation)).toEqual(["create"])
|
|
}),
|
|
)
|
|
|
|
it.effect("interrupts in-flight provisioning on destroy and fails waiters with NotFound", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const gate = yield* gateCreate()
|
|
|
|
const waiter = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Deferred.await(gate.started)
|
|
yield* workspace.destroy(workspaceID)
|
|
|
|
expect(yield* Fiber.join(waiter).pipe(Effect.flip)).toEqual(new Workspace.NotFound({ workspaceID }))
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "destroy"])
|
|
expect(calls.at(-1)?.binding).toBeNull()
|
|
expect(
|
|
yield* Database.Service.use(({ db }) =>
|
|
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, workspaceID)).get(),
|
|
).pipe(Effect.orDie),
|
|
).toBeUndefined()
|
|
}),
|
|
)
|
|
|
|
it.effect("shares a failed attempt and retries the same workspace ID", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const started = yield* Deferred.make<void>()
|
|
const release = yield* Deferred.make<void>()
|
|
let fail = true
|
|
create = ({ workspaceID }) =>
|
|
Deferred.succeed(started, undefined).pipe(
|
|
Effect.andThen(Deferred.await(release)),
|
|
Effect.andThen(
|
|
Effect.suspend(() =>
|
|
fail
|
|
? Effect.fail(new WorkspaceDriver.Error({ message: "create failed" }))
|
|
: Effect.succeed({ binding: { workspaceID, generation: 0 } }),
|
|
),
|
|
),
|
|
)
|
|
|
|
const first = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Deferred.await(started)
|
|
const second = yield* workspace.provision(workspaceID).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Deferred.succeed(release, undefined)
|
|
const [firstExit, secondExit] = yield* Effect.all([Fiber.await(first), Fiber.await(second)])
|
|
expect(firstExit._tag).toBe("Failure")
|
|
expect(secondExit._tag).toBe("Failure")
|
|
expect(calls.map((call) => call.operation)).toEqual(["create"])
|
|
|
|
fail = false
|
|
expect((yield* workspace.provision(workspaceID)).binding).toEqual({ workspaceID, generation: 0 })
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "create"])
|
|
}),
|
|
)
|
|
|
|
it.effect("persists the workspace lifecycle and reconnects after idle suspension", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const created = yield* workspace.provision(workspaceID)
|
|
|
|
expect(created.id).toBe(workspaceID)
|
|
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.provision(yield* workspace.create({ provider: "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}`,
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
it.effect("routes file overrides through the workspace connection without spawning processes", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const files = makeFiles(yield* workspace.connect(workspaceID))
|
|
|
|
expect(calls).toEqual([])
|
|
const root = `/override-${workspaceID}`
|
|
const payload = new TextEncoder().encode("hello workspace")
|
|
|
|
yield* files.mkdir(root)
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
|
|
yield* files.write(`${root}/note.txt`, payload)
|
|
expect(yield* files.stat(`${root}/note.txt`)).toMatchObject({
|
|
type: "file",
|
|
size: payload.byteLength,
|
|
})
|
|
expect((yield* files.read(`${root}/note.txt`, { offset: 0, length: 5 })).bytes).toEqual(
|
|
new TextEncoder().encode("hello"),
|
|
)
|
|
expect(yield* files.list(root)).toEqual([{ name: "note.txt", type: "file" }])
|
|
|
|
yield* files.move(`${root}/note.txt`, `${root}/renamed.txt`)
|
|
expect(yield* files.list(root)).toEqual([{ name: "renamed.txt", type: "file" }])
|
|
|
|
yield* files.remove(root)
|
|
expect(yield* files.stat(root).pipe(Effect.flip)).toEqual(new NotFound({ path: root }))
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
}),
|
|
)
|
|
|
|
it.effect("falls back to execDefaults over the connection spawner for unprovided overrides", () =>
|
|
Effect.gen(function* () {
|
|
const spawned: string[] = []
|
|
const written: Array<{ readonly path: string; readonly size: number }> = []
|
|
const spawner = makeSpawner((command) =>
|
|
Effect.sync(() => {
|
|
if (command._tag === "StandardCommand") {
|
|
spawned.push(command.args.at(3) ?? command.command)
|
|
}
|
|
return makeHandle({
|
|
pid: ProcessId(1),
|
|
exitCode: Effect.succeed(ExitCode(0)),
|
|
isRunning: Effect.succeed(false),
|
|
kill: () => Effect.void,
|
|
stdin: Sink.drain,
|
|
stdout: Stream.empty,
|
|
stderr: Stream.empty,
|
|
all: Stream.empty,
|
|
getInputFd: () => Sink.drain,
|
|
getOutputFd: () => Stream.empty,
|
|
unref: Effect.succeed(Effect.void),
|
|
})
|
|
}),
|
|
)
|
|
connectDriver = () =>
|
|
Effect.succeed({
|
|
spawner,
|
|
overrides: {
|
|
write: (path, bytes) =>
|
|
Effect.sync(() => {
|
|
written.push({ path, size: bytes.byteLength })
|
|
}),
|
|
},
|
|
})
|
|
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const files = makeFiles(yield* workspace.connect(workspaceID))
|
|
|
|
yield* files.write("/workspace/direct.txt", new Uint8Array([1, 2, 3]))
|
|
expect(written).toEqual([{ path: "/workspace/direct.txt", size: 3 }])
|
|
expect(spawned).toEqual([])
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
|
|
yield* files.mkdir("/workspace/sub")
|
|
yield* files.remove("/workspace/direct.txt")
|
|
expect(spawned).toEqual(["/workspace/sub", "/workspace/direct.txt"])
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
}),
|
|
)
|
|
|
|
it.effect("tracks active operations and lastActivity across file overrides", () =>
|
|
Effect.gen(function* () {
|
|
const started = yield* Deferred.make<void>()
|
|
const release = yield* Deferred.make<void>()
|
|
connectDriver = () =>
|
|
Effect.succeed({
|
|
spawner: memory.spawner,
|
|
overrides: {
|
|
...memory.overrides,
|
|
read: (path) =>
|
|
Deferred.succeed(started, undefined).pipe(
|
|
Effect.andThen(Deferred.await(release)),
|
|
Effect.as({
|
|
info: { type: "file" as const, size: 2, mtimeMs: 0 },
|
|
bytes: new Uint8Array([1, 2]),
|
|
}),
|
|
),
|
|
},
|
|
})
|
|
|
|
const workspace = yield* Workspace.Service
|
|
const workspaceID = yield* workspace.create({ provider: "fake" })
|
|
const files = makeFiles(yield* workspace.connect(workspaceID))
|
|
|
|
const reading = yield* files.read("/workspace/in-flight.txt").pipe(Effect.forkScoped({ startImmediately: true }))
|
|
yield* Deferred.await(started)
|
|
|
|
yield* TestClock.adjust("6 minutes")
|
|
expect(calls.map((call) => call.operation)).toEqual(["create", "connect"])
|
|
|
|
yield* Deferred.succeed(release, undefined)
|
|
expect((yield* Fiber.join(reading)).bytes).toEqual(new Uint8Array([1, 2]))
|
|
|
|
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, workspaceID)).get(),
|
|
).pipe(Effect.orDie)
|
|
expect(stored?.last_used_at).toBe(6 * 60 * 1000)
|
|
}),
|
|
)
|
|
|
|
it.effect("surfaces wake failures during file overrides as Environment.Failed", () =>
|
|
Effect.gen(function* () {
|
|
const workspace = yield* Workspace.Service
|
|
const created = yield* workspace.provision(yield* workspace.create({ provider: "fake" }))
|
|
const files = makeFiles(yield* workspace.connect(created.id))
|
|
yield* files.mkdir("/warm")
|
|
|
|
yield* TestClock.adjust("6 minutes")
|
|
failConnect = true
|
|
|
|
const readError = yield* files.read("/target.txt").pipe(Effect.flip)
|
|
expect(readError).toEqual(
|
|
new Failed({
|
|
path: "/target.txt",
|
|
cause: new WorkspaceDriver.Error({ message: "wake failed" }),
|
|
}),
|
|
)
|
|
|
|
const moveError = yield* files.move("/from.txt", "/to.txt").pipe(Effect.flip)
|
|
expect(moveError).toEqual(
|
|
new Failed({
|
|
path: "/from.txt",
|
|
cause: new WorkspaceDriver.Error({ message: "wake failed" }),
|
|
}),
|
|
)
|
|
}),
|
|
)
|