Files
opencode/packages/server/test/session-admit-location.test.ts

93 lines
4.0 KiB
TypeScript

import fs from "fs/promises"
import { expect } from "bun:test"
import { App } from "@opencode/core/app"
import { Bus } from "@opencode/core/bus"
import { Database } from "@opencode/core/database/database"
import { Watcher } from "@opencode/core/filesystem/watcher"
import { LocationServiceMap } from "@opencode/core/location-service-map"
import { ModelsDev } from "@opencode/core/models-dev"
import { Session } from "@opencode/core/session"
import { SessionExecution } from "@opencode/core/session/execution"
import { Location } from "@opencode/schema/location"
import { AbsolutePath } from "@opencode/schema/schema"
import { LayerNode } from "@opencode/util/effect/layer-node"
import { Global } from "@opencode/util/global"
import { Context, Effect, Layer, RcMap } from "effect"
import { HttpEffect, HttpRouter, HttpServer } from "effect/http"
import { tempGlobalLayer } from "../../core/test/fixture/global"
import { tmpdirScoped } from "../../core/test/fixture/tmpdir"
import { it } from "../../core/test/lib/effect"
import { createEmbeddedRoutes } from "../src/routes"
it.live("admits synthetic messages and compaction requests without booting or requiring the session Location", () =>
Effect.gen(function* () {
const activeDir = yield* tmpdirScoped()
const deletedDir = yield* tmpdirScoped()
const activeLocation = LocationServiceMap.canonical(
Location.Ref.make({ directory: AbsolutePath.make(activeDir.path) }),
)
const deletedLocation = LocationServiceMap.canonical(
Location.Ref.make({ directory: AbsolutePath.make(deletedDir.path) }),
)
const replacements: LayerNode.Replacements = [
Global.node.replace(tempGlobalLayer),
Database.node.replace(Database.node),
Bus.node.replace(Bus.node),
App.node.replace(App.node),
ModelsDev.node.replace(ModelsDev.configured({ fetch: false })),
Watcher.node.replace(Watcher.configured({ enabled: false })),
SessionExecution.node.replace(SessionExecution.noopLayer),
]
const context = yield* Layer.build(
createEmbeddedRoutes({}, replacements).pipe(Layer.provide(HttpServer.layerServices)),
)
const sessions = Context.get(context, Session.Service)
const locations = Context.get(context, LocationServiceMap.Service)
const handler = Context.get(context, HttpRouter.HttpRouter)
.asHttpEffect()
.pipe(HttpEffect.toWebHandlerWith(context))
const post = (route: string, body: unknown) =>
Effect.promise(() =>
handler(
new Request(`http://opencode.local${route}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
}),
),
)
const coldSession = yield* sessions.create({ location: activeLocation, title: "cold" })
const strandedSession = yield* sessions.create({ location: deletedLocation, title: "stranded" })
// Remove the worktree directory after session creation to model a pruned worktree.
yield* Effect.promise(() => fs.rm(deletedDir.path, { recursive: true, force: true }))
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([])
const coldSynthetic = yield* post(`/api/session/${coldSession.id}/synthetic`, {
text: "background notice",
resume: false,
})
expect(coldSynthetic.status).toBe(200)
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([])
const coldCompact = yield* post(`/api/session/${coldSession.id}/compact`, {})
expect(coldCompact.status).toBe(200)
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([])
const strandedSynthetic = yield* post(`/api/session/${strandedSession.id}/synthetic`, {
text: "note on deleted worktree",
resume: false,
})
expect(strandedSynthetic.status).toBe(200)
const strandedCompact = yield* post(`/api/session/${strandedSession.id}/compact`, {})
expect(strandedCompact.status).toBe(200)
expect((yield* sessions.inbox(strandedSession.id)).map((item) => item.type)).toEqual(["synthetic", "compaction"])
}),
)