mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-25 07:15:19 +00:00
All service facades now use @/effect/run (lazy runtime import) instead of directly importing @/effect/runtime. This breaks the circular dependency chain that caused "undefined is not an object" crashes in bun's bundled binary. - Add src/effect/run.ts with run() and runInstance() lazy wrappers - Strip all facades to runtime-only functions (no schema re-exports) - Consumers that need schemas import from service modules directly - Update specs/effect-migration.md with facade rules and full list
324 lines
8.3 KiB
TypeScript
324 lines
8.3 KiB
TypeScript
import { afterEach, test, expect } from "bun:test"
|
|
import { Question } from "../../src/question"
|
|
import { Question as QuestionService } from "../../src/question/service"
|
|
import { Instance } from "../../src/project/instance"
|
|
import { QuestionID } from "../../src/question/schema"
|
|
import { tmpdir } from "../fixture/fixture"
|
|
import { SessionID } from "../../src/session/schema"
|
|
|
|
afterEach(async () => {
|
|
await Instance.disposeAll()
|
|
})
|
|
|
|
/** Reject all pending questions so dangling Deferred fibers don't hang the test. */
|
|
async function rejectAll() {
|
|
const pending = await Question.list()
|
|
for (const req of pending) {
|
|
await Question.reject(req.id)
|
|
}
|
|
}
|
|
|
|
test("ask - returns pending promise", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const promise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions: [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Option 1", description: "First option" },
|
|
{ label: "Option 2", description: "Second option" },
|
|
],
|
|
},
|
|
],
|
|
})
|
|
expect(promise).toBeInstanceOf(Promise)
|
|
await rejectAll()
|
|
await promise.catch(() => {})
|
|
},
|
|
})
|
|
})
|
|
|
|
test("ask - adds to pending list", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const questions = [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Option 1", description: "First option" },
|
|
{ label: "Option 2", description: "Second option" },
|
|
],
|
|
},
|
|
]
|
|
|
|
const askPromise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions,
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
expect(pending.length).toBe(1)
|
|
expect(pending[0].questions).toEqual(questions)
|
|
await rejectAll()
|
|
await askPromise.catch(() => {})
|
|
},
|
|
})
|
|
})
|
|
|
|
// reply tests
|
|
|
|
test("reply - resolves the pending ask with answers", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const questions = [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Option 1", description: "First option" },
|
|
{ label: "Option 2", description: "Second option" },
|
|
],
|
|
},
|
|
]
|
|
|
|
const askPromise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions,
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
const requestID = pending[0].id
|
|
|
|
await Question.reply({
|
|
requestID,
|
|
answers: [["Option 1"]],
|
|
})
|
|
|
|
const answers = await askPromise
|
|
expect(answers).toEqual([["Option 1"]])
|
|
},
|
|
})
|
|
})
|
|
|
|
test("reply - removes from pending list", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const askPromise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions: [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Option 1", description: "First option" },
|
|
{ label: "Option 2", description: "Second option" },
|
|
],
|
|
},
|
|
],
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
expect(pending.length).toBe(1)
|
|
|
|
await Question.reply({
|
|
requestID: pending[0].id,
|
|
answers: [["Option 1"]],
|
|
})
|
|
await askPromise
|
|
|
|
const pendingAfter = await Question.list()
|
|
expect(pendingAfter.length).toBe(0)
|
|
},
|
|
})
|
|
})
|
|
|
|
test("reply - does nothing for unknown requestID", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
await Question.reply({
|
|
requestID: QuestionID.make("que_unknown"),
|
|
answers: [["Option 1"]],
|
|
})
|
|
// Should not throw
|
|
},
|
|
})
|
|
})
|
|
|
|
// reject tests
|
|
|
|
test("reject - throws RejectedError", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const askPromise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions: [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Option 1", description: "First option" },
|
|
{ label: "Option 2", description: "Second option" },
|
|
],
|
|
},
|
|
],
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
await Question.reject(pending[0].id)
|
|
|
|
await expect(askPromise).rejects.toBeInstanceOf(QuestionService.RejectedError)
|
|
},
|
|
})
|
|
})
|
|
|
|
test("reject - removes from pending list", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const askPromise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions: [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Option 1", description: "First option" },
|
|
{ label: "Option 2", description: "Second option" },
|
|
],
|
|
},
|
|
],
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
expect(pending.length).toBe(1)
|
|
|
|
await Question.reject(pending[0].id)
|
|
askPromise.catch(() => {}) // Ignore rejection
|
|
|
|
const pendingAfter = await Question.list()
|
|
expect(pendingAfter.length).toBe(0)
|
|
},
|
|
})
|
|
})
|
|
|
|
test("reject - does nothing for unknown requestID", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
await Question.reject(QuestionID.make("que_unknown"))
|
|
// Should not throw
|
|
},
|
|
})
|
|
})
|
|
|
|
// multiple questions tests
|
|
|
|
test("ask - handles multiple questions", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const questions = [
|
|
{
|
|
question: "What would you like to do?",
|
|
header: "Action",
|
|
options: [
|
|
{ label: "Build", description: "Build the project" },
|
|
{ label: "Test", description: "Run tests" },
|
|
],
|
|
},
|
|
{
|
|
question: "Which environment?",
|
|
header: "Env",
|
|
options: [
|
|
{ label: "Dev", description: "Development" },
|
|
{ label: "Prod", description: "Production" },
|
|
],
|
|
},
|
|
]
|
|
|
|
const askPromise = Question.ask({
|
|
sessionID: SessionID.make("ses_test"),
|
|
questions,
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
|
|
await Question.reply({
|
|
requestID: pending[0].id,
|
|
answers: [["Build"], ["Dev"]],
|
|
})
|
|
|
|
const answers = await askPromise
|
|
expect(answers).toEqual([["Build"], ["Dev"]])
|
|
},
|
|
})
|
|
})
|
|
|
|
// list tests
|
|
|
|
test("list - returns all pending requests", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const p1 = Question.ask({
|
|
sessionID: SessionID.make("ses_test1"),
|
|
questions: [
|
|
{
|
|
question: "Question 1?",
|
|
header: "Q1",
|
|
options: [{ label: "A", description: "A" }],
|
|
},
|
|
],
|
|
})
|
|
|
|
const p2 = Question.ask({
|
|
sessionID: SessionID.make("ses_test2"),
|
|
questions: [
|
|
{
|
|
question: "Question 2?",
|
|
header: "Q2",
|
|
options: [{ label: "B", description: "B" }],
|
|
},
|
|
],
|
|
})
|
|
|
|
const pending = await Question.list()
|
|
expect(pending.length).toBe(2)
|
|
await rejectAll()
|
|
p1.catch(() => {})
|
|
p2.catch(() => {})
|
|
},
|
|
})
|
|
})
|
|
|
|
test("list - returns empty when no pending", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const pending = await Question.list()
|
|
expect(pending.length).toBe(0)
|
|
},
|
|
})
|
|
})
|