fix: resolve 25 pre-publish blockers
- postinstall.mjs: fix alias package detection - migrate-legacy-plugin-entry: dedupe + regression tests - task_system: default consistency across runtime paths - task() contract: consistent tool behavior - runtime model selection, tool cap, stale-task cancellation - recovery sanitization, context-limit gating - Ralph semantic DONE hardening, Atlas fallback persistence - native-skill description/content, skill path traversal guard - publish workflow: platform awaited via reusable workflow job - release: version edits reapplied before commit/tag - JSONC plugin migration: top-level plugin key safety - cold-cache: user fallback models skip disconnected providers - docs/version/release framing updates Verified: bun test (4599 pass), tsc --noEmit clean, bun run build clean
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"
|
||||
|
||||
const replaceEmptyTextPartsAsync = mock(() => Promise.resolve(false))
|
||||
const injectTextPartAsync = mock(() => Promise.resolve(false))
|
||||
const findMessagesWithEmptyTextPartsFromSDK = mock(() => Promise.resolve([] as string[]))
|
||||
|
||||
mock.module("../../shared", () => ({
|
||||
normalizeSDKResponse: (response: { data?: unknown[] }) => response.data ?? [],
|
||||
}))
|
||||
|
||||
mock.module("../../shared/logger", () => ({
|
||||
log: () => {},
|
||||
}))
|
||||
|
||||
mock.module("../../shared/opencode-storage-detection", () => ({
|
||||
isSqliteBackend: () => true,
|
||||
}))
|
||||
|
||||
mock.module("../session-recovery/storage", () => ({
|
||||
findEmptyMessages: () => [],
|
||||
findMessagesWithEmptyTextParts: () => [],
|
||||
injectTextPart: () => false,
|
||||
replaceEmptyTextParts: () => false,
|
||||
}))
|
||||
|
||||
mock.module("../session-recovery/storage/empty-text", () => ({
|
||||
replaceEmptyTextPartsAsync,
|
||||
findMessagesWithEmptyTextPartsFromSDK,
|
||||
}))
|
||||
|
||||
mock.module("../session-recovery/storage/text-part-injector", () => ({
|
||||
injectTextPartAsync,
|
||||
}))
|
||||
|
||||
async function importFreshMessageBuilder(): Promise<typeof import("./message-builder")> {
|
||||
return import(`./message-builder?test=${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
describe("sanitizeEmptyMessagesBeforeSummarize", () => {
|
||||
beforeEach(() => {
|
||||
replaceEmptyTextPartsAsync.mockReset()
|
||||
replaceEmptyTextPartsAsync.mockResolvedValue(false)
|
||||
injectTextPartAsync.mockReset()
|
||||
injectTextPartAsync.mockResolvedValue(false)
|
||||
findMessagesWithEmptyTextPartsFromSDK.mockReset()
|
||||
findMessagesWithEmptyTextPartsFromSDK.mockResolvedValue([])
|
||||
})
|
||||
|
||||
test("#given sqlite message with tool content and empty text part #when sanitizing #then it fixes the mixed-content message", async () => {
|
||||
const { sanitizeEmptyMessagesBeforeSummarize, PLACEHOLDER_TEXT } = await importFreshMessageBuilder()
|
||||
const client = {
|
||||
session: {
|
||||
messages: mock(() => Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
info: { id: "msg-1" },
|
||||
parts: [
|
||||
{ type: "tool_result", text: "done" },
|
||||
{ type: "text", text: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
},
|
||||
} as never
|
||||
findMessagesWithEmptyTextPartsFromSDK.mockResolvedValue(["msg-1"])
|
||||
replaceEmptyTextPartsAsync.mockResolvedValue(true)
|
||||
|
||||
const fixedCount = await sanitizeEmptyMessagesBeforeSummarize("ses-1", client)
|
||||
|
||||
expect(fixedCount).toBe(1)
|
||||
expect(replaceEmptyTextPartsAsync).toHaveBeenCalledWith(client, "ses-1", "msg-1", PLACEHOLDER_TEXT)
|
||||
expect(injectTextPartAsync).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("#given sqlite message with mixed content and failed replacement #when sanitizing #then it injects the placeholder text part", async () => {
|
||||
const { sanitizeEmptyMessagesBeforeSummarize, PLACEHOLDER_TEXT } = await importFreshMessageBuilder()
|
||||
const client = {
|
||||
session: {
|
||||
messages: mock(() => Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
info: { id: "msg-2" },
|
||||
parts: [
|
||||
{ type: "tool_use", text: "call" },
|
||||
{ type: "text", text: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
},
|
||||
} as never
|
||||
findMessagesWithEmptyTextPartsFromSDK.mockResolvedValue(["msg-2"])
|
||||
injectTextPartAsync.mockResolvedValue(true)
|
||||
|
||||
const fixedCount = await sanitizeEmptyMessagesBeforeSummarize("ses-2", client)
|
||||
|
||||
expect(fixedCount).toBe(1)
|
||||
expect(injectTextPartAsync).toHaveBeenCalledWith(client, "ses-2", "msg-2", PLACEHOLDER_TEXT)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user