fix: rewrite dedup recovery test to mock module instead of filesystem
This commit is contained in:
parent
1df025ad44
commit
403457f9e4
@ -1,9 +1,12 @@
|
|||||||
import { describe, test, expect, mock } from "bun:test"
|
import { describe, test, expect, mock, beforeEach } from "bun:test"
|
||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import type { ExperimentalConfig } from "../../config"
|
import type { ExperimentalConfig } from "../../config"
|
||||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs"
|
|
||||||
import { join } from "node:path"
|
const attemptDeduplicationRecoveryMock = mock(async () => {})
|
||||||
import { tmpdir } from "node:os"
|
|
||||||
|
mock.module("./deduplication-recovery", () => ({
|
||||||
|
attemptDeduplicationRecovery: attemptDeduplicationRecoveryMock,
|
||||||
|
}))
|
||||||
|
|
||||||
function createImmediateTimeouts(): () => void {
|
function createImmediateTimeouts(): () => void {
|
||||||
const originalSetTimeout = globalThis.setTimeout
|
const originalSetTimeout = globalThis.setTimeout
|
||||||
@ -22,75 +25,14 @@ function createImmediateTimeouts(): () => void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeJson(filePath: string, data: unknown): void {
|
|
||||||
writeFileSync(filePath, JSON.stringify(data, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
||||||
test("attempts deduplication recovery when compaction hits prompt too long errors", async () => {
|
beforeEach(() => {
|
||||||
|
attemptDeduplicationRecoveryMock.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("calls deduplication recovery when compaction is already in progress", async () => {
|
||||||
|
//#given
|
||||||
const restoreTimeouts = createImmediateTimeouts()
|
const restoreTimeouts = createImmediateTimeouts()
|
||||||
const originalDataHome = process.env.XDG_DATA_HOME
|
|
||||||
const tempHome = mkdtempSync(join(tmpdir(), "omo-context-"))
|
|
||||||
process.env.XDG_DATA_HOME = tempHome
|
|
||||||
|
|
||||||
const storageRoot = join(tempHome, "opencode", "storage")
|
|
||||||
const messageDir = join(storageRoot, "message", "session-96")
|
|
||||||
const partDir = join(storageRoot, "part", "message-1")
|
|
||||||
const partDirTwo = join(storageRoot, "part", "message-2")
|
|
||||||
|
|
||||||
mkdirSync(messageDir, { recursive: true })
|
|
||||||
mkdirSync(partDir, { recursive: true })
|
|
||||||
mkdirSync(partDirTwo, { recursive: true })
|
|
||||||
|
|
||||||
writeJson(join(messageDir, "message-1.json"), {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "tool",
|
|
||||||
callID: "call-1",
|
|
||||||
tool: "read",
|
|
||||||
state: { input: { filePath: "/tmp/a.txt" } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
writeJson(join(messageDir, "message-2.json"), {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "tool",
|
|
||||||
callID: "call-2",
|
|
||||||
tool: "read",
|
|
||||||
state: { input: { filePath: "/tmp/a.txt" } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
writeJson(join(partDir, "part-1.json"), {
|
|
||||||
id: "part-1",
|
|
||||||
sessionID: "session-96",
|
|
||||||
messageID: "message-1",
|
|
||||||
type: "tool",
|
|
||||||
callID: "call-1",
|
|
||||||
tool: "read",
|
|
||||||
state: {
|
|
||||||
status: "completed",
|
|
||||||
input: { filePath: "/tmp/a.txt" },
|
|
||||||
output: "duplicate output",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
writeJson(join(partDirTwo, "part-2.json"), {
|
|
||||||
id: "part-2",
|
|
||||||
sessionID: "session-96",
|
|
||||||
messageID: "message-2",
|
|
||||||
type: "tool",
|
|
||||||
callID: "call-2",
|
|
||||||
tool: "read",
|
|
||||||
state: {
|
|
||||||
status: "completed",
|
|
||||||
input: { filePath: "/tmp/a.txt" },
|
|
||||||
output: "latest output",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const experimental = {
|
const experimental = {
|
||||||
dynamic_context_pruning: {
|
dynamic_context_pruning: {
|
||||||
@ -123,7 +65,7 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
|||||||
const ctx = { client: mockClient, directory: "/tmp" } as PluginInput
|
const ctx = { client: mockClient, directory: "/tmp" } as PluginInput
|
||||||
const hook = createAnthropicContextWindowLimitRecoveryHook(ctx, { experimental })
|
const hook = createAnthropicContextWindowLimitRecoveryHook(ctx, { experimental })
|
||||||
|
|
||||||
// given - initial token limit error schedules compaction
|
// first error triggers compaction (setTimeout runs immediately due to mock)
|
||||||
await hook.event({
|
await hook.event({
|
||||||
event: {
|
event: {
|
||||||
type: "session.error",
|
type: "session.error",
|
||||||
@ -131,7 +73,7 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// when - compaction hits another prompt-too-long error
|
//#when - second error while compaction is in progress
|
||||||
await hook.event({
|
await hook.event({
|
||||||
event: {
|
event: {
|
||||||
type: "session.error",
|
type: "session.error",
|
||||||
@ -139,17 +81,42 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// then - duplicate tool output is truncated
|
//#then - deduplication recovery was called for the second error
|
||||||
const prunedPart = JSON.parse(
|
expect(attemptDeduplicationRecoveryMock).toHaveBeenCalledTimes(1)
|
||||||
readFileSync(join(partDir, "part-1.json"), "utf-8"),
|
expect(attemptDeduplicationRecoveryMock.mock.calls[0]![0]).toBe("session-96")
|
||||||
) as { truncated?: boolean }
|
|
||||||
|
|
||||||
expect(prunedPart.truncated).toBe(true)
|
|
||||||
} finally {
|
} finally {
|
||||||
if (resolveSummarize) resolveSummarize()
|
if (resolveSummarize) resolveSummarize()
|
||||||
restoreTimeouts()
|
restoreTimeouts()
|
||||||
process.env.XDG_DATA_HOME = originalDataHome
|
|
||||||
rmSync(tempHome, { recursive: true, force: true })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("does not call deduplication when compaction is not in progress", async () => {
|
||||||
|
//#given
|
||||||
|
const mockClient = {
|
||||||
|
session: {
|
||||||
|
messages: mock(() => Promise.resolve({ data: [] })),
|
||||||
|
summarize: mock(() => Promise.resolve()),
|
||||||
|
revert: mock(() => Promise.resolve()),
|
||||||
|
prompt_async: mock(() => Promise.resolve()),
|
||||||
|
},
|
||||||
|
tui: {
|
||||||
|
showToast: mock(() => Promise.resolve()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { createAnthropicContextWindowLimitRecoveryHook } = await import("./recovery-hook")
|
||||||
|
const ctx = { client: mockClient, directory: "/tmp" } as PluginInput
|
||||||
|
const hook = createAnthropicContextWindowLimitRecoveryHook(ctx)
|
||||||
|
|
||||||
|
//#when - single error (no compaction in progress)
|
||||||
|
await hook.event({
|
||||||
|
event: {
|
||||||
|
type: "session.error",
|
||||||
|
properties: { sessionID: "session-no-dedup", error: "some other error" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(attemptDeduplicationRecoveryMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user