test(ralph-loop): cover background task idle guard

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-27 17:52:45 +09:00
parent 5e4aa8b827
commit f429e539fa
+36 -15
View File
@@ -17,7 +17,7 @@ describe("ralph-loop", () => {
let mockSessionMessages: Array<{ info?: { role?: string }; parts?: Array<{ type: string; text?: string }> }> let mockSessionMessages: Array<{ info?: { role?: string }; parts?: Array<{ type: string; text?: string }> }>
let mockMessagesApiResponseShape: "data" | "array" let mockMessagesApiResponseShape: "data" | "array"
function createMockPluginInput() { function createMockPluginInput(): Parameters<typeof createRalphLoopHook>[0] {
return { return {
client: { client: {
session: { session: {
@@ -63,7 +63,7 @@ describe("ralph-loop", () => {
}, },
}, },
directory: TEST_DIR, directory: TEST_DIR,
} as unknown as Parameters<typeof createRalphLoopHook>[0] } as Parameters<typeof createRalphLoopHook>[0]
} }
beforeEach(() => { beforeEach(() => {
@@ -304,6 +304,33 @@ describe("ralph-loop", () => {
expect(state?.iteration).toBe(2) expect(state?.iteration).toBe(2)
}) })
test("should skip continuation when background task is running", async () => {
// given - active loop state with a running background task
const hook = createRalphLoopHook(createMockPluginInput(), {
backgroundManager: {
getTasksByParentSession: (sessionID: string) => sessionID === "session-123"
? [{ status: "running" }]
: [],
},
})
hook.startLoop("session-123", "Build a feature", { maxIterations: 10 })
// when - session goes idle
await hook.event({
event: {
type: "session.idle",
properties: { sessionID: "session-123" },
},
})
// then - no continuation should be injected
expect(promptCalls.length).toBe(0)
// then - iteration should not be incremented
const state = hook.getState()
expect(state?.iteration).toBe(1)
})
test("should stop loop when max iterations reached", async () => { test("should stop loop when max iterations reached", async () => {
// given - loop at max iteration // given - loop at max iteration
const hook = createRalphLoopHook(createMockPluginInput()) const hook = createRalphLoopHook(createMockPluginInput())
@@ -1144,20 +1171,14 @@ Original task: Build something`
test("should not hang when session.messages() throws", async () => { test("should not hang when session.messages() throws", async () => {
// given - API that throws (simulates timeout error) // given - API that throws (simulates timeout error)
let apiCallCount = 0 let apiCallCount = 0
const errorMock = { const errorMock = createMockPluginInput()
...createMockPluginInput(), Object.defineProperty(errorMock.client.session, "messages", {
client: { value: async () => {
...createMockPluginInput().client, apiCallCount++
session: { throw new Error("API timeout")
...createMockPluginInput().client.session,
messages: async () => {
apiCallCount++
throw new Error("API timeout")
},
},
}, },
} })
const hook = createRalphLoopHook(errorMock as any, { const hook = createRalphLoopHook(errorMock, {
getTranscriptPath: () => join(TEST_DIR, "nonexistent.jsonl"), getTranscriptPath: () => join(TEST_DIR, "nonexistent.jsonl"),
apiTimeout: 100, apiTimeout: 100,
}) })