From c13699e2c69ab7dc5a5bf8687aaed958196939ae Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 2 May 2026 18:01:41 -0400 Subject: [PATCH] Convert question lifecycle tests to Effect --- .../opencode/test/question/question.test.ts | 121 +++++++----------- 1 file changed, 43 insertions(+), 78 deletions(-) diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index b5041fd114..a2d4f4acbc 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -1,12 +1,11 @@ -import { afterEach, test, expect } from "bun:test" -import { Effect, Fiber, Layer } from "effect" +import { afterEach, expect } from "bun:test" +import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Question } from "../../src/question" import { Instance } from "../../src/project/instance" import { QuestionID } from "../../src/question/schema" -import { disposeAllInstances, provideInstance, tmpdir, tmpdirScoped } from "../fixture/fixture" +import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture" import { SessionID } from "../../src/session/schema" import { testEffect } from "../lib/effect" -import { AppRuntime } from "../../src/effect/app-runtime" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" const it = testEffect(Layer.mergeAll(Question.defaultLayer, CrossSpawnSpawner.defaultLayer)) @@ -35,16 +34,6 @@ const rejectEffect = Effect.fn("QuestionTest.reject")(function* (id: QuestionID) yield* question.reject(id) }) -const ask = (input: { sessionID: SessionID; questions: ReadonlyArray; tool?: Question.Tool }) => - AppRuntime.runPromise(Question.Service.use((svc) => svc.ask(input))) - -const list = () => AppRuntime.runPromise(Question.Service.use((svc) => svc.list())) - -const reply = (input: { requestID: QuestionID; answers: ReadonlyArray }) => - AppRuntime.runPromise(Question.Service.use((svc) => svc.reply(input))) - -const reject = (id: QuestionID) => AppRuntime.runPromise(Question.Service.use((svc) => svc.reject(id))) - afterEach(async () => { await disposeAllInstances() }) @@ -370,72 +359,48 @@ it.live("questions stay isolated by directory", () => }), ) -test("pending question rejects on instance dispose", async () => { - await using tmp = await tmpdir({ git: true }) +it.live("pending question rejects on instance dispose", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + const fiber = yield* askEffect({ + sessionID: SessionID.make("ses_dispose"), + questions: [ + { + question: "Dispose me?", + header: "Dispose", + options: [{ label: "Yes", description: "Yes" }], + }, + ], + }).pipe(provideInstance(dir), Effect.forkScoped) - const pending = Instance.provide({ - directory: tmp.path, - fn: () => { - return ask({ - sessionID: SessionID.make("ses_dispose"), - questions: [ - { - question: "Dispose me?", - header: "Dispose", - options: [{ label: "Yes", description: "Yes" }], - }, - ], - }) - }, - }) - const result = pending.then( - () => "resolved" as const, - (err) => err, - ) + expect(yield* waitForPending(1).pipe(provideInstance(dir))).toHaveLength(1) + yield* Effect.promise(() => Instance.provide({ directory: dir, fn: () => void Instance.dispose() })) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const items = await list() - expect(items).toHaveLength(1) - await Instance.dispose() - }, - }) + const exit = yield* Fiber.await(fiber) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Question.RejectedError) + }), +) - expect(await result).toBeInstanceOf(Question.RejectedError) -}) +it.live("pending question rejects on instance reload", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + const fiber = yield* askEffect({ + sessionID: SessionID.make("ses_reload"), + questions: [ + { + question: "Reload me?", + header: "Reload", + options: [{ label: "Yes", description: "Yes" }], + }, + ], + }).pipe(provideInstance(dir), Effect.forkScoped) -test("pending question rejects on instance reload", async () => { - await using tmp = await tmpdir({ git: true }) + expect(yield* waitForPending(1).pipe(provideInstance(dir))).toHaveLength(1) + yield* Effect.promise(() => Instance.reload({ directory: dir })) - const pending = Instance.provide({ - directory: tmp.path, - fn: () => { - return ask({ - sessionID: SessionID.make("ses_reload"), - questions: [ - { - question: "Reload me?", - header: "Reload", - options: [{ label: "Yes", description: "Yes" }], - }, - ], - }) - }, - }) - const result = pending.then( - () => "resolved" as const, - (err) => err, - ) - - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const items = await list() - expect(items).toHaveLength(1) - await Instance.reload({ directory: tmp.path }) - }, - }) - - expect(await result).toBeInstanceOf(Question.RejectedError) -}) + const exit = yield* Fiber.await(fiber) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Question.RejectedError) + }), +)