mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-30 15:31:57 +00:00
Separate ID-bound Session policy from host routing. Bind Inbox and Location preparation dependencies at construction, preserve admission and execution semantics, and cover the extracted ownership contracts directly.
86 lines
3.5 KiB
TypeScript
86 lines
3.5 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { Effect, Layer } from "effect"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
import { Database } from "@opencode-ai/core/database/database"
|
|
import { Bus } from "@opencode-ai/core/bus"
|
|
import { Location } from "@opencode-ai/core/location"
|
|
import { Project } from "@opencode-ai/core/project"
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
import { Session } from "@opencode-ai/core/session"
|
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
|
import { SessionModelTransport } from "@opencode-ai/core/session/model-transport"
|
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
|
import { SessionStore } from "@opencode-ai/core/session/store"
|
|
import { SessionEnvironment } from "@opencode-ai/core/session/environment"
|
|
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
|
import { testEffect } from "./lib/effect"
|
|
import { globalProjectNode } from "./lib/project"
|
|
import { tmpdirScoped } from "./fixture/tmpdir"
|
|
|
|
const closed: Session.ID[] = []
|
|
const transport = Layer.succeed(
|
|
SessionModelTransport.Service,
|
|
SessionModelTransport.Service.of({
|
|
bind: () => ({ execute: () => Effect.die("Unexpected WebSocket execution") }),
|
|
close: (sessionID) => Effect.sync(() => closed.push(sessionID)),
|
|
closeAll: Effect.void,
|
|
}),
|
|
)
|
|
const it = testEffect(
|
|
AppNodeBuilder.build(
|
|
LayerNode.group([
|
|
Database.node,
|
|
Bus.node,
|
|
SessionProjector.node,
|
|
SessionStore.node,
|
|
SessionEnvironment.node,
|
|
Session.node,
|
|
LocationServiceMap.node,
|
|
]),
|
|
[
|
|
[Project.node, globalProjectNode],
|
|
[SessionExecution.node, SessionExecution.noopLayer],
|
|
[SessionModelTransport.node, transport],
|
|
],
|
|
),
|
|
)
|
|
|
|
describe("Session.remove", () => {
|
|
it.effect("removes a session and its children", () =>
|
|
Effect.gen(function* () {
|
|
const temporary = yield* tmpdirScoped()
|
|
const location = Location.Ref.make({ directory: AbsolutePath.make(temporary.path) })
|
|
const session = yield* Session.Service
|
|
const parent = yield* session.create({ location })
|
|
const child = yield* session.create({ parentID: parent.id })
|
|
yield* session.environment({ sessionID: parent.id, variables: { SESSION_ENV: "parent" } })
|
|
yield* session.environment({ sessionID: child.id, variables: { SESSION_ENV: "child" } })
|
|
const locations = yield* LocationServiceMap.Service
|
|
yield* Effect.acquireRelease(locations.contextEffect(location), () => locations.invalidate(location))
|
|
closed.length = 0
|
|
|
|
yield* session.remove(parent.id)
|
|
|
|
expect((yield* session.list()).data).toEqual([])
|
|
expect(closed).toEqual([parent.id, child.id])
|
|
const environments = yield* SessionEnvironment.Service
|
|
expect(yield* environments.get(parent.id)).toBeUndefined()
|
|
expect(yield* environments.get(child.id)).toBeUndefined()
|
|
expect(yield* Effect.result(session.get(parent.id))).toMatchObject({ _tag: "Failure" })
|
|
expect(yield* Effect.result(session.get(child.id))).toMatchObject({ _tag: "Failure" })
|
|
}),
|
|
)
|
|
|
|
it.effect("fails when the session does not exist", () =>
|
|
Effect.gen(function* () {
|
|
const session = yield* Session.Service
|
|
const sessionID = Session.ID.make("ses_missing")
|
|
|
|
expect(yield* Effect.result(session.remove(sessionID))).toMatchObject({
|
|
_tag: "Failure",
|
|
failure: { _tag: "Session.NotFoundError", sessionID },
|
|
})
|
|
}),
|
|
)
|
|
})
|