import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import { describe, expect, it, vi } from "vitest"; import { createTestDraftStream } from "./draft-stream.test-helpers.js"; import {
createLaneTextDeliverer,
type DraftLaneState,
type LaneDeliveryResult,
type LaneName,
} from "./lane-delivery.js";
it("treats 'message is not modified' preview edit errors as delivered", async () => { const harness = createHarness({ answerMessageId: 999 });
harness.editPreview.mockRejectedValue( new Error( "400: Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message",
),
);
const result = await deliverFinalAnswer(harness, HELLO_FINAL);
expect(expectPreviewFinalized(result)).toEqual({ content: HELLO_FINAL, messageId: 999 });
expect(harness.editPreview).toHaveBeenCalledTimes(1);
expect(harness.sendPayload).not.toHaveBeenCalled();
expect(harness.markDelivered).toHaveBeenCalledTimes(1);
expect(harness.log).toHaveBeenCalledWith(
expect.stringContaining('edit returned "message is not modified"; treating as delivered'),
);
});
it("retains preview when an existing preview final edit fails with ambiguous error", async () => { const harness = createHarness({ answerMessageId: 999 }); // Plain Error with no error_code → ambiguous, prefer incomplete over duplicate
harness.editPreview.mockRejectedValue(new Error("500: preview edit failed"));
it("falls back when Telegram reports the current final edit target missing", async () => { const harness = createHarness({ answerMessageId: 999 });
harness.editPreview.mockRejectedValue(new Error("400: Bad Request: message to edit not found"));
await expectFinalEditFallbackToSend({
harness,
text: "Hello final",
expectedLogSnippet: "edit target missing with no alternate preview; falling back",
});
});
it("falls back to sendPayload when the final edit fails before reaching Telegram", async () => { const harness = createHarness({ answerMessageId: 999 }); const err = Object.assign(new Error("connect ECONNREFUSED"), { code: "ECONNREFUSED" });
harness.editPreview.mockRejectedValue(err);
const result = await deliverFinalAnswer(harness, HELLO_FINAL);
it("keeps preview when the final edit times out after the request may have landed", async () => { const harness = createHarness({ answerMessageId: 999 });
harness.editPreview.mockRejectedValue(new Error("timeout: request timed out after 30000ms"));
it("falls back to normal delivery when final text exceeds preview edit limit", async () => { const harness = createHarness({ answerMessageId: 999, draftMaxChars: 20 }); const longText = "x".repeat(50);
// ── Duplicate message regression tests ────────────────────────────────── // During final delivery, only ambiguous post-connect failures keep the // preview. Definite non-delivery falls back to a real send.
it("retains preview on ambiguous API error during final", async () => { const harness = createHarness({ answerMessageId: 999 }); // Plain Error with no error_code → ambiguous, prefer incomplete over duplicate
harness.editPreview.mockRejectedValue(new Error("500: Internal Server Error"));
it("falls back when an archived preview edit target is missing and no alternate preview exists", async () => { const harness = createHarness();
seedArchivedAnswerPreview(harness);
harness.editPreview.mockRejectedValue(new Error("400: Bad Request: message to edit not found"));
const result = await deliverFinalAnswer(harness, "Complete final answer");
it("keeps the active preview when an archived final edit target is missing", async () => { const harness = createHarness({ answerMessageId: 999 });
seedArchivedAnswerPreview(harness);
harness.editPreview.mockRejectedValue(new Error("400: Bad Request: message to edit not found"));
const result = await deliverFinalAnswer(harness, "Complete final answer");
it("keeps the archived preview when the final text regresses", async () => { const harness = createHarness();
harness.archivedAnswerPreviews.push({
messageId: 5555,
textSnapshot: "Recovered final answer.",
deleteIfUnused: true,
});
const result = await deliverFinalAnswer(harness, "Recovered final answer");
it("falls back when the first preview send may have landed without a message id", async () => { const stream = createTestDraftStream();
stream.sendMayHaveLanded.mockReturnValue(true); const harness = createHarness({ answerStream: stream });
const result = await deliverFinalAnswer(harness, HELLO_FINAL);
it("retains when sendMayHaveLanded is true and a prior preview was visible", async () => { // Stream has a messageId (visible preview) but loses it after stop const stream = createTestDraftStream({ messageId: 999 });
stream.sendMayHaveLanded.mockReturnValue(true); const harness = createHarness({
answerStream: stream,
answerHasStreamedMessage: true,
}); // Simulate messageId lost after stop (e.g. forceNewMessage or timeout)
harness.stopDraftLane.mockImplementation(async (lane: DraftLaneState) => {
stream.setMessageId(undefined);
await lane.stream?.stop();
});
await expectFinalPreviewRetained({
harness,
expectedLogSnippet: "preview send may have landed despite missing message id",
});
});
const result = await harness.deliverLaneText({
laneName: "answer",
text: "Final with media",
payload: { text: "Final with media", mediaUrl: "file:///tmp/example.png" },
infoKind: "final",
});
expect(result.kind).toBe("sent");
expect(harness.sendPayload).toHaveBeenCalledWith(
expect.objectContaining({ text: "Final with media", mediaUrl: "file:///tmp/example.png" }),
);
expect(harness.deletePreviewMessage).toHaveBeenCalledWith(4444);
});
});
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet am 2026-06-13)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.