test: port instance HttpApi path/vcs read coverage to Effect

This commit is contained in:
Kit Langton 2026-04-30 11:07:00 -04:00 committed by GitHub
parent 62e1335388
commit dddfcbf0d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 712 additions and 216 deletions

View file

@ -1,7 +1,8 @@
import { NodeHttpServer, NodeServices } from "@effect/platform-node"
import { Flag } from "@opencode-ai/core/flag/flag"
import { GlobalBus } from "@/bus/global"
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { Effect, Fiber, Layer } from "effect"
import { HttpClient, HttpClientRequest, HttpRouter, HttpServerResponse } from "effect/unstable/http"
import * as Socket from "effect/unstable/socket/Socket"
import { mkdir } from "node:fs/promises"
@ -12,6 +13,7 @@ import { Workspace } from "../../src/control-plane/workspace"
import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref"
import { Instance } from "../../src/project/instance"
import { Project } from "../../src/project/project"
import { disposeMiddleware, markInstanceForDisposal } from "../../src/server/routes/instance/httpapi/lifecycle"
import { instanceRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/instance-context"
import { workspaceRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/workspace-routing"
import { resetDatabase } from "../fixture/db"
@ -84,6 +86,40 @@ const serveProbe = (probePath: HttpRouter.PathInput = "/probe") =>
Layer.build,
)
const waitDisposedEvent = Effect.promise(
() =>
new Promise<{ directory?: string; workspace?: string }>((resolve, reject) => {
const timer = setTimeout(() => {
GlobalBus.off("event", onEvent)
reject(new Error("timed out waiting for instance disposal"))
}, 10_000)
function onEvent(event: { directory?: string; workspace?: string; payload: { type?: string } }) {
if (event.payload.type !== "server.instance.disposed") return
clearTimeout(timer)
GlobalBus.off("event", onEvent)
resolve({ directory: event.directory, workspace: event.workspace })
}
GlobalBus.on("event", onEvent)
}),
)
const serveDisposeProbe = () =>
HttpRouter.serve(
HttpRouter.add(
"POST",
"/dispose-probe",
Effect.gen(function* () {
const instance = yield* InstanceRef
if (!instance) return HttpServerResponse.empty({ status: 500 })
yield* markInstanceForDisposal(instance)
return yield* HttpServerResponse.json(true)
}),
).pipe(Layer.provide(instanceContextTestLayer)),
{ middleware: disposeMiddleware, disableListenLog: true, disableLogger: true },
).pipe(Layer.build)
describe("HttpApi instance context middleware", () => {
it.live("provides instance context from the routed directory", () =>
Effect.gen(function* () {
@ -164,4 +200,25 @@ describe("HttpApi instance context middleware", () => {
})
}),
)
it.live("preserves selected workspace id on instance disposal events", () =>
Effect.gen(function* () {
const dir = yield* tmpdirScoped({ git: true })
const project = yield* Project.use.fromDirectory(dir)
const workspaceDir = path.join(dir, ".workspace-local")
const workspace = yield* createLocalWorkspace({
projectID: project.project.id,
type: "instance-context-dispose-event",
directory: workspaceDir,
})
yield* serveDisposeProbe()
const disposed = yield* waitDisposedEvent.pipe(Effect.forkScoped)
const response = yield* HttpClientRequest.post(`/dispose-probe?workspace=${workspace.id}`).pipe(HttpClient.execute)
expect(response.status).toBe(200)
expect(yield* response.json).toBe(true)
expect(yield* Fiber.join(disposed)).toEqual({ directory: workspaceDir, workspace: workspace.id })
}),
)
})