mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 08:11:42 +00:00
test: merge signal typing-read-receipt coverage into inbound contract suite
This commit is contained in:
@@ -1,115 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import {
|
|
||||||
createBaseSignalEventHandlerDeps,
|
|
||||||
createSignalReceiveEvent,
|
|
||||||
} from "./monitor/event-handler.test-harness.js";
|
|
||||||
|
|
||||||
const sendTypingMock = vi.fn();
|
|
||||||
const sendReadReceiptMock = vi.fn();
|
|
||||||
const dispatchInboundMessageMock = vi.fn(
|
|
||||||
async (params: {
|
|
||||||
replyOptions?: { onReplyStart?: () => void };
|
|
||||||
dispatcher?: { sendFinalReply?: (payload: { text: string }) => void };
|
|
||||||
}) => {
|
|
||||||
await Promise.resolve(params.replyOptions?.onReplyStart?.());
|
|
||||||
return { queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } };
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
vi.mock("./send.js", () => ({
|
|
||||||
sendMessageSignal: vi.fn(),
|
|
||||||
sendTypingSignal: sendTypingMock,
|
|
||||||
sendReadReceiptSignal: sendReadReceiptMock,
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../auto-reply/dispatch.js", () => ({
|
|
||||||
dispatchInboundMessage: dispatchInboundMessageMock,
|
|
||||||
dispatchInboundMessageWithDispatcher: dispatchInboundMessageMock,
|
|
||||||
dispatchInboundMessageWithBufferedDispatcher: dispatchInboundMessageMock,
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../pairing/pairing-store.js", () => ({
|
|
||||||
readChannelAllowFromStore: vi.fn().mockResolvedValue([]),
|
|
||||||
upsertChannelPairingRequest: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("signal event handler typing + read receipts", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.useRealTimers();
|
|
||||||
sendTypingMock.mockClear().mockResolvedValue(true);
|
|
||||||
sendReadReceiptMock.mockClear().mockResolvedValue(true);
|
|
||||||
dispatchInboundMessageMock.mockClear();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sends typing + read receipt for allowed DMs", async () => {
|
|
||||||
const { createSignalEventHandler } = await import("./monitor/event-handler.js");
|
|
||||||
const handler = createSignalEventHandler(
|
|
||||||
createBaseSignalEventHandlerDeps({
|
|
||||||
cfg: {
|
|
||||||
messages: { inbound: { debounceMs: 0 } },
|
|
||||||
channels: { signal: { dmPolicy: "open", allowFrom: ["*"] } },
|
|
||||||
},
|
|
||||||
account: "+15550009999",
|
|
||||||
blockStreaming: false,
|
|
||||||
historyLimit: 0,
|
|
||||||
groupHistories: new Map(),
|
|
||||||
sendReadReceipts: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await handler(
|
|
||||||
createSignalReceiveEvent({
|
|
||||||
dataMessage: {
|
|
||||||
message: "hi",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(sendTypingMock).toHaveBeenCalledWith("+15550001111", expect.any(Object));
|
|
||||||
expect(sendReadReceiptMock).toHaveBeenCalledWith(
|
|
||||||
"signal:+15550001111",
|
|
||||||
1700000000000,
|
|
||||||
expect.any(Object),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("prefixes group bodies with sender label", async () => {
|
|
||||||
let capturedBody = "";
|
|
||||||
dispatchInboundMessageMock.mockImplementationOnce(
|
|
||||||
async (params: { dispatcher?: { sendFinalReply?: (payload: { text: string }) => void } }) => {
|
|
||||||
const ctx = params as { ctx?: { Body?: string } };
|
|
||||||
capturedBody = ctx.ctx?.Body ?? "";
|
|
||||||
params.dispatcher?.sendFinalReply?.({ text: "ok" });
|
|
||||||
return { queuedFinal: true, counts: { tool: 0, block: 0, final: 1 } };
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { createSignalEventHandler } = await import("./monitor/event-handler.js");
|
|
||||||
const handler = createSignalEventHandler(
|
|
||||||
createBaseSignalEventHandlerDeps({
|
|
||||||
cfg: {
|
|
||||||
channels: { signal: {} },
|
|
||||||
} as never,
|
|
||||||
account: "+15550009999",
|
|
||||||
blockStreaming: false,
|
|
||||||
historyLimit: 0,
|
|
||||||
groupHistories: new Map(),
|
|
||||||
allowFrom: [],
|
|
||||||
groupAllowFrom: [],
|
|
||||||
sendReadReceipts: false,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await handler(
|
|
||||||
createSignalReceiveEvent({
|
|
||||||
dataMessage: {
|
|
||||||
message: "hello",
|
|
||||||
groupInfo: { groupId: "group-1", groupName: "Test Group" },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(dispatchInboundMessageMock).toHaveBeenCalled();
|
|
||||||
expect(capturedBody).toContain("Alice (+15550001111): hello");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,16 +1,63 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { inboundCtxCapture as capture } from "../../../test/helpers/inbound-contract-dispatch-mock.js";
|
|
||||||
import { expectInboundContextContract } from "../../../test/helpers/inbound-contract.js";
|
import { expectInboundContextContract } from "../../../test/helpers/inbound-contract.js";
|
||||||
|
import type { MsgContext } from "../../auto-reply/templating.js";
|
||||||
import { createSignalEventHandler } from "./event-handler.js";
|
import { createSignalEventHandler } from "./event-handler.js";
|
||||||
import {
|
import {
|
||||||
createBaseSignalEventHandlerDeps,
|
createBaseSignalEventHandlerDeps,
|
||||||
createSignalReceiveEvent,
|
createSignalReceiveEvent,
|
||||||
} from "./event-handler.test-harness.js";
|
} from "./event-handler.test-harness.js";
|
||||||
|
|
||||||
describe("signal createSignalEventHandler inbound contract", () => {
|
const { sendTypingMock, sendReadReceiptMock, dispatchInboundMessageMock, capture } = vi.hoisted(
|
||||||
it("passes a finalized MsgContext to dispatchInboundMessage", async () => {
|
() => {
|
||||||
capture.ctx = undefined;
|
const captureState: { ctx: MsgContext | undefined } = { ctx: undefined };
|
||||||
|
return {
|
||||||
|
sendTypingMock: vi.fn(),
|
||||||
|
sendReadReceiptMock: vi.fn(),
|
||||||
|
dispatchInboundMessageMock: vi.fn(
|
||||||
|
async (params: {
|
||||||
|
ctx: MsgContext;
|
||||||
|
replyOptions?: { onReplyStart?: () => void | Promise<void> };
|
||||||
|
}) => {
|
||||||
|
captureState.ctx = params.ctx;
|
||||||
|
await Promise.resolve(params.replyOptions?.onReplyStart?.());
|
||||||
|
return { queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
capture: captureState,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.mock("../send.js", () => ({
|
||||||
|
sendMessageSignal: vi.fn(),
|
||||||
|
sendTypingSignal: sendTypingMock,
|
||||||
|
sendReadReceiptSignal: sendReadReceiptMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../auto-reply/dispatch.js", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("../../auto-reply/dispatch.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
dispatchInboundMessage: dispatchInboundMessageMock,
|
||||||
|
dispatchInboundMessageWithDispatcher: dispatchInboundMessageMock,
|
||||||
|
dispatchInboundMessageWithBufferedDispatcher: dispatchInboundMessageMock,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("../../pairing/pairing-store.js", () => ({
|
||||||
|
readChannelAllowFromStore: vi.fn().mockResolvedValue([]),
|
||||||
|
upsertChannelPairingRequest: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("signal createSignalEventHandler inbound contract", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
capture.ctx = undefined;
|
||||||
|
sendTypingMock.mockReset().mockResolvedValue(true);
|
||||||
|
sendReadReceiptMock.mockReset().mockResolvedValue(true);
|
||||||
|
dispatchInboundMessageMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes a finalized MsgContext to dispatchInboundMessage", async () => {
|
||||||
const handler = createSignalEventHandler(
|
const handler = createSignalEventHandler(
|
||||||
createBaseSignalEventHandlerDeps({
|
createBaseSignalEventHandlerDeps({
|
||||||
// oxlint-disable-next-line typescript/no-explicit-any
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
@@ -31,7 +78,7 @@ describe("signal createSignalEventHandler inbound contract", () => {
|
|||||||
|
|
||||||
expect(capture.ctx).toBeTruthy();
|
expect(capture.ctx).toBeTruthy();
|
||||||
expectInboundContextContract(capture.ctx!);
|
expectInboundContextContract(capture.ctx!);
|
||||||
const contextWithBody = capture.ctx as unknown as { Body?: string };
|
const contextWithBody = capture.ctx!;
|
||||||
// Sender should appear as prefix in group messages (no redundant [from:] suffix)
|
// Sender should appear as prefix in group messages (no redundant [from:] suffix)
|
||||||
expect(String(contextWithBody.Body ?? "")).toContain("Alice");
|
expect(String(contextWithBody.Body ?? "")).toContain("Alice");
|
||||||
expect(String(contextWithBody.Body ?? "")).toMatch(/Alice.*:/);
|
expect(String(contextWithBody.Body ?? "")).toMatch(/Alice.*:/);
|
||||||
@@ -39,8 +86,6 @@ describe("signal createSignalEventHandler inbound contract", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("normalizes direct chat To/OriginatingTo targets to canonical Signal ids", async () => {
|
it("normalizes direct chat To/OriginatingTo targets to canonical Signal ids", async () => {
|
||||||
capture.ctx = undefined;
|
|
||||||
|
|
||||||
const handler = createSignalEventHandler(
|
const handler = createSignalEventHandler(
|
||||||
createBaseSignalEventHandlerDeps({
|
createBaseSignalEventHandlerDeps({
|
||||||
// oxlint-disable-next-line typescript/no-explicit-any
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
@@ -62,13 +107,40 @@ describe("signal createSignalEventHandler inbound contract", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(capture.ctx).toBeTruthy();
|
expect(capture.ctx).toBeTruthy();
|
||||||
const context = capture.ctx as unknown as {
|
const context = capture.ctx!;
|
||||||
ChatType?: string;
|
|
||||||
To?: string;
|
|
||||||
OriginatingTo?: string;
|
|
||||||
};
|
|
||||||
expect(context.ChatType).toBe("direct");
|
expect(context.ChatType).toBe("direct");
|
||||||
expect(context.To).toBe("+15550002222");
|
expect(context.To).toBe("+15550002222");
|
||||||
expect(context.OriginatingTo).toBe("+15550002222");
|
expect(context.OriginatingTo).toBe("+15550002222");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sends typing + read receipt for allowed DMs", async () => {
|
||||||
|
const handler = createSignalEventHandler(
|
||||||
|
createBaseSignalEventHandlerDeps({
|
||||||
|
cfg: {
|
||||||
|
messages: { inbound: { debounceMs: 0 } },
|
||||||
|
channels: { signal: { dmPolicy: "open", allowFrom: ["*"] } },
|
||||||
|
},
|
||||||
|
account: "+15550009999",
|
||||||
|
blockStreaming: false,
|
||||||
|
historyLimit: 0,
|
||||||
|
groupHistories: new Map(),
|
||||||
|
sendReadReceipts: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await handler(
|
||||||
|
createSignalReceiveEvent({
|
||||||
|
dataMessage: {
|
||||||
|
message: "hi",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(sendTypingMock).toHaveBeenCalledWith("+15550001111", expect.any(Object));
|
||||||
|
expect(sendReadReceiptMock).toHaveBeenCalledWith(
|
||||||
|
"signal:+15550001111",
|
||||||
|
1700000000000,
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user