2026-04-03 23:06:35 +09:00
|
|
|
import { afterAll, describe, it, expect, mock, beforeEach } from "bun:test"
|
2026-02-16 18:20:32 +09:00
|
|
|
import { fixEmptyMessagesWithSDK } from "./empty-content-recovery-sdk"
|
|
|
|
|
|
|
|
|
|
const mockReplaceEmptyTextParts = mock(() => Promise.resolve(false))
|
|
|
|
|
const mockInjectTextPart = mock(() => Promise.resolve(false))
|
|
|
|
|
|
|
|
|
|
mock.module("../session-recovery/storage/empty-text", () => ({
|
|
|
|
|
replaceEmptyTextPartsAsync: mockReplaceEmptyTextParts,
|
|
|
|
|
}))
|
|
|
|
|
mock.module("../session-recovery/storage/text-part-injector", () => ({
|
|
|
|
|
injectTextPartAsync: mockInjectTextPart,
|
|
|
|
|
}))
|
|
|
|
|
|
2026-04-03 23:06:35 +09:00
|
|
|
afterAll(() => {
|
|
|
|
|
mock.restore()
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-16 18:20:32 +09:00
|
|
|
function createMockClient(messages: Array<{ info?: { id?: string }; parts?: Array<{ type?: string; text?: string }> }>) {
|
|
|
|
|
return {
|
|
|
|
|
session: {
|
|
|
|
|
messages: mock(() => Promise.resolve({ data: messages })),
|
|
|
|
|
},
|
|
|
|
|
} as never
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("fixEmptyMessagesWithSDK", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mockReplaceEmptyTextParts.mockReset()
|
|
|
|
|
mockInjectTextPart.mockReset()
|
|
|
|
|
mockReplaceEmptyTextParts.mockReturnValue(Promise.resolve(false))
|
|
|
|
|
mockInjectTextPart.mockReturnValue(Promise.resolve(false))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("returns fixed=false when no empty messages exist", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ info: { id: "msg_1" }, parts: [{ type: "text", text: "Hello" }] },
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(false)
|
|
|
|
|
expect(result.fixedMessageIds).toEqual([])
|
|
|
|
|
expect(result.scannedEmptyCount).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("fixes empty message via replace when scanning all", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ info: { id: "msg_1" }, parts: [{ type: "text", text: "" }] },
|
|
|
|
|
])
|
|
|
|
|
mockReplaceEmptyTextParts.mockReturnValue(Promise.resolve(true))
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(true)
|
|
|
|
|
expect(result.fixedMessageIds).toContain("msg_1")
|
|
|
|
|
expect(result.scannedEmptyCount).toBe(1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("falls back to inject when replace fails", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ info: { id: "msg_1" }, parts: [] },
|
|
|
|
|
])
|
|
|
|
|
mockReplaceEmptyTextParts.mockReturnValue(Promise.resolve(false))
|
|
|
|
|
mockInjectTextPart.mockReturnValue(Promise.resolve(true))
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(true)
|
|
|
|
|
expect(result.fixedMessageIds).toContain("msg_1")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("fixes target message by index when provided", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ info: { id: "msg_0" }, parts: [{ type: "text", text: "ok" }] },
|
|
|
|
|
{ info: { id: "msg_1" }, parts: [] },
|
|
|
|
|
])
|
|
|
|
|
mockReplaceEmptyTextParts.mockReturnValue(Promise.resolve(true))
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
messageIndex: 1,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(true)
|
|
|
|
|
expect(result.fixedMessageIds).toContain("msg_1")
|
|
|
|
|
expect(result.scannedEmptyCount).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("skips messages without info.id", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ parts: [] },
|
|
|
|
|
{ info: {}, parts: [] },
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(false)
|
|
|
|
|
expect(result.scannedEmptyCount).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("treats thinking-only messages as empty", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ info: { id: "msg_1" }, parts: [{ type: "thinking", text: "hmm" }] },
|
|
|
|
|
])
|
|
|
|
|
mockReplaceEmptyTextParts.mockReturnValue(Promise.resolve(true))
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(true)
|
|
|
|
|
expect(result.fixedMessageIds).toContain("msg_1")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("treats tool_use messages as non-empty", async () => {
|
|
|
|
|
//#given
|
|
|
|
|
const client = createMockClient([
|
|
|
|
|
{ info: { id: "msg_1" }, parts: [{ type: "tool_use" }] },
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = await fixEmptyMessagesWithSDK({
|
|
|
|
|
sessionID: "ses_1",
|
|
|
|
|
client,
|
|
|
|
|
placeholderText: "[recovered]",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.fixed).toBe(false)
|
|
|
|
|
expect(result.scannedEmptyCount).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
})
|