test(media-dedup): add missing coverage for Discord media dedup wiring

Cover three integration points where media dedup could silently regress:
- trimMessagingToolSent FIFO cap at 200 entries
- buildReplyPayloads media filter wiring (new test file)
- followup-runner messagingToolSentMediaUrls filtering
This commit is contained in:
Yaroslav Boiko
2026-02-16 20:08:27 +01:00
committed by Peter Steinberger
parent 4640999e77
commit c7681c3cff
4 changed files with 251 additions and 24 deletions

View File

@@ -0,0 +1,46 @@
import { describe, expect, it } from "vitest";
import { buildReplyPayloads } from "./agent-runner-payloads.js";
const baseParams = {
isHeartbeat: false,
didLogHeartbeatStrip: false,
blockStreamingEnabled: false,
blockReplyPipeline: null,
replyToMode: "off" as const,
};
describe("buildReplyPayloads media filter integration", () => {
it("strips media URL from payload when in messagingToolSentMediaUrls", () => {
const { replyPayloads } = buildReplyPayloads({
...baseParams,
payloads: [{ text: "hello", mediaUrl: "file:///tmp/photo.jpg" }],
messagingToolSentMediaUrls: ["file:///tmp/photo.jpg"],
});
expect(replyPayloads).toHaveLength(1);
expect(replyPayloads[0].mediaUrl).toBeUndefined();
});
it("preserves media URL when not in messagingToolSentMediaUrls", () => {
const { replyPayloads } = buildReplyPayloads({
...baseParams,
payloads: [{ text: "hello", mediaUrl: "file:///tmp/photo.jpg" }],
messagingToolSentMediaUrls: ["file:///tmp/other.jpg"],
});
expect(replyPayloads).toHaveLength(1);
expect(replyPayloads[0].mediaUrl).toBe("file:///tmp/photo.jpg");
});
it("applies media filter after text filter", () => {
const { replyPayloads } = buildReplyPayloads({
...baseParams,
payloads: [{ text: "hello world!", mediaUrl: "file:///tmp/photo.jpg" }],
messagingToolSentTexts: ["hello world!"],
messagingToolSentMediaUrls: ["file:///tmp/photo.jpg"],
});
// Text filter removes the payload entirely (text matched), so nothing remains.
expect(replyPayloads).toHaveLength(0);
});
});

View File

@@ -257,6 +257,47 @@ describe("createFollowupRunner messaging tool dedupe", () => {
expect(onBlockReply).not.toHaveBeenCalled();
});
it("drops media URL from payload when messaging tool already sent it", async () => {
const onBlockReply = vi.fn(async () => {});
runEmbeddedPiAgentMock.mockResolvedValueOnce({
payloads: [{ mediaUrl: "/tmp/img.png" }],
messagingToolSentMediaUrls: ["/tmp/img.png"],
meta: {},
});
const runner = createFollowupRunner({
opts: { onBlockReply },
typing: createMockTypingController(),
typingMode: "instant",
defaultModel: "anthropic/claude-opus-4-5",
});
await runner(baseQueuedRun());
// Media stripped → payload becomes non-renderable → not delivered.
expect(onBlockReply).not.toHaveBeenCalled();
});
it("delivers media payload when not a duplicate", async () => {
const onBlockReply = vi.fn(async () => {});
runEmbeddedPiAgentMock.mockResolvedValueOnce({
payloads: [{ mediaUrl: "/tmp/img.png" }],
messagingToolSentMediaUrls: ["/tmp/other.png"],
meta: {},
});
const runner = createFollowupRunner({
opts: { onBlockReply },
typing: createMockTypingController(),
typingMode: "instant",
defaultModel: "anthropic/claude-opus-4-5",
});
await runner(baseQueuedRun());
expect(onBlockReply).toHaveBeenCalledTimes(1);
});
it("persists usage even when replies are suppressed", async () => {
const storePath = path.join(
await fs.mkdtemp(path.join(tmpdir(), "openclaw-followup-usage-")),