mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 16:07:12 +02:00
* fix(mattermost): prevent duplicate messages when block streaming + threading are active Remove replyToId from createBlockReplyPayloadKey so identical content is deduplicated regardless of threading target. Add explicit threading dock to the Mattermost plugin with resolveReplyToMode reading from config (default "all"), and add replyToMode to the Mattermost config schema. Fixes #41219 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(mattermost): address PR review — per-account replyToMode and test clarity Read replyToMode from the merged per-account config via resolveMattermostAccount so account-level overrides are honored in multi-account setups. Add replyToMode to MattermostAccountConfig type. Rename misleading test to clarify it exercises shouldDropFinalPayloads short-circuit, not payload key dedup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replies: keep block-pipeline reply targets distinct * Tests: cover block reply target-aware dedupe * Update CHANGELOG.md --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
createBlockReplyContentKey,
|
|
createBlockReplyPayloadKey,
|
|
createBlockReplyPipeline,
|
|
} from "./block-reply-pipeline.js";
|
|
|
|
describe("createBlockReplyPayloadKey", () => {
|
|
it("produces different keys for payloads differing only by replyToId", () => {
|
|
const a = createBlockReplyPayloadKey({ text: "hello world", replyToId: "post-1" });
|
|
const b = createBlockReplyPayloadKey({ text: "hello world", replyToId: "post-2" });
|
|
const c = createBlockReplyPayloadKey({ text: "hello world" });
|
|
expect(a).not.toBe(b);
|
|
expect(a).not.toBe(c);
|
|
});
|
|
|
|
it("produces different keys for payloads with different text", () => {
|
|
const a = createBlockReplyPayloadKey({ text: "hello" });
|
|
const b = createBlockReplyPayloadKey({ text: "world" });
|
|
expect(a).not.toBe(b);
|
|
});
|
|
|
|
it("produces different keys for payloads with different media", () => {
|
|
const a = createBlockReplyPayloadKey({ text: "hello", mediaUrl: "file:///a.png" });
|
|
const b = createBlockReplyPayloadKey({ text: "hello", mediaUrl: "file:///b.png" });
|
|
expect(a).not.toBe(b);
|
|
});
|
|
|
|
it("trims whitespace from text for key comparison", () => {
|
|
const a = createBlockReplyPayloadKey({ text: " hello " });
|
|
const b = createBlockReplyPayloadKey({ text: "hello" });
|
|
expect(a).toBe(b);
|
|
});
|
|
});
|
|
|
|
describe("createBlockReplyContentKey", () => {
|
|
it("produces the same key for payloads differing only by replyToId", () => {
|
|
const a = createBlockReplyContentKey({ text: "hello world", replyToId: "post-1" });
|
|
const b = createBlockReplyContentKey({ text: "hello world", replyToId: "post-2" });
|
|
const c = createBlockReplyContentKey({ text: "hello world" });
|
|
expect(a).toBe(b);
|
|
expect(a).toBe(c);
|
|
});
|
|
});
|
|
|
|
describe("createBlockReplyPipeline dedup with threading", () => {
|
|
it("keeps separate deliveries for same text with different replyToId", async () => {
|
|
const sent: Array<{ text?: string; replyToId?: string }> = [];
|
|
const pipeline = createBlockReplyPipeline({
|
|
onBlockReply: async (payload) => {
|
|
sent.push({ text: payload.text, replyToId: payload.replyToId });
|
|
},
|
|
timeoutMs: 5000,
|
|
});
|
|
|
|
pipeline.enqueue({ text: "response text", replyToId: "thread-root-1" });
|
|
pipeline.enqueue({ text: "response text", replyToId: undefined });
|
|
await pipeline.flush();
|
|
|
|
expect(sent).toEqual([
|
|
{ text: "response text", replyToId: "thread-root-1" },
|
|
{ text: "response text", replyToId: undefined },
|
|
]);
|
|
});
|
|
|
|
it("hasSentPayload matches regardless of replyToId", async () => {
|
|
const pipeline = createBlockReplyPipeline({
|
|
onBlockReply: async () => {},
|
|
timeoutMs: 5000,
|
|
});
|
|
|
|
pipeline.enqueue({ text: "response text", replyToId: "thread-root-1" });
|
|
await pipeline.flush();
|
|
|
|
// Final payload with no replyToId should be recognized as already sent
|
|
expect(pipeline.hasSentPayload({ text: "response text" })).toBe(true);
|
|
expect(pipeline.hasSentPayload({ text: "response text", replyToId: "other-id" })).toBe(true);
|
|
});
|
|
});
|