























@@ -0,0 +1,139 @@
1+import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
2+import type { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
3+import { beforeEach, describe, expect, it, vi } from "vitest";
4+import type { createIMessageRpcClient } from "./client.js";
5+import { monitorIMessageProvider } from "./monitor.js";
6+7+const waitForTransportReadyMock = vi.hoisted(() =>
8+vi.fn<typeof waitForTransportReady>(async () => {}),
9+);
10+const createIMessageRpcClientMock = vi.hoisted(() => vi.fn<typeof createIMessageRpcClient>());
11+const readChannelAllowFromStoreMock = vi.hoisted(() => vi.fn(async () => [] as string[]));
12+const recordInboundSessionMock = vi.hoisted(() => vi.fn(async (_params: unknown) => {}));
13+const dispatchInboundMessageMock = vi.hoisted(() =>
14+vi.fn(
15+async (_params: { ctx: MsgContext }) =>
16+({ queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } }) as const,
17+),
18+);
19+20+vi.mock("openclaw/plugin-sdk/transport-ready-runtime", () => ({
21+waitForTransportReady: waitForTransportReadyMock,
22+}));
23+24+vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
25+const actual = await importOriginal<typeof import("openclaw/plugin-sdk/conversation-runtime")>();
26+return {
27+ ...actual,
28+readChannelAllowFromStore: readChannelAllowFromStoreMock,
29+recordInboundSession: recordInboundSessionMock,
30+upsertChannelPairingRequest: vi.fn(),
31+};
32+});
33+34+vi.mock("openclaw/plugin-sdk/channel-inbound", async (importOriginal) => {
35+const actual = await importOriginal<typeof import("openclaw/plugin-sdk/channel-inbound")>();
36+return {
37+ ...actual,
38+createChannelInboundDebouncer: vi.fn((opts) => ({
39+debouncer: {
40+enqueue: async (entry: unknown) => await opts.onFlush([entry]),
41+},
42+})),
43+shouldDebounceTextInbound: vi.fn(() => false),
44+};
45+});
46+47+vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
48+const actual = await importOriginal<typeof import("openclaw/plugin-sdk/reply-runtime")>();
49+return {
50+ ...actual,
51+dispatchInboundMessage: dispatchInboundMessageMock,
52+};
53+});
54+55+vi.mock("./client.js", () => ({
56+createIMessageRpcClient: createIMessageRpcClientMock,
57+}));
58+59+vi.mock("./monitor/abort-handler.js", () => ({
60+attachIMessageMonitorAbortHandler: vi.fn(() => () => {}),
61+}));
62+63+describe("iMessage monitor last-route updates", () => {
64+beforeEach(() => {
65+waitForTransportReadyMock.mockReset().mockResolvedValue(undefined);
66+createIMessageRpcClientMock.mockReset();
67+readChannelAllowFromStoreMock.mockReset().mockResolvedValue([]);
68+recordInboundSessionMock.mockClear();
69+dispatchInboundMessageMock.mockClear();
70+});
71+72+it("keeps per-channel-peer direct-message last-route writes on the isolated session", async () => {
73+const runtimeErrorMock = vi.fn();
74+let onNotification: ((message: { method: string; params: unknown }) => void) | undefined;
75+const client = {
76+request: vi.fn(async () => ({ subscription: 1 })),
77+waitForClose: vi.fn(async () => {
78+onNotification?.({
79+method: "message",
80+params: {
81+message: {
82+id: 1,
83+chat_id: 123,
84+sender: "+15550001111",
85+is_from_me: false,
86+text: "hello from imessage",
87+is_group: false,
88+date: 1_714_000_000_000,
89+},
90+},
91+});
92+await Promise.resolve();
93+await Promise.resolve();
94+}),
95+stop: vi.fn(async () => {}),
96+};
97+createIMessageRpcClientMock.mockImplementation(async (params) => {
98+if (!params?.onNotification) {
99+throw new Error("expected iMessage notification handler");
100+}
101+onNotification = params.onNotification;
102+return client as never;
103+});
104+105+await monitorIMessageProvider({
106+config: {
107+channels: { imessage: { dmPolicy: "allowlist", allowFrom: ["+15550001111"] } },
108+messages: { inbound: { debounceMs: 0 } },
109+session: { dmScope: "per-channel-peer", mainKey: "main" },
110+} as never,
111+runtime: { error: runtimeErrorMock, exit: vi.fn(), log: vi.fn() },
112+});
113+114+await vi.waitFor(() => {
115+expect(readChannelAllowFromStoreMock).toHaveBeenCalledTimes(1);
116+});
117+expect(runtimeErrorMock).not.toHaveBeenCalled();
118+await vi.waitFor(() => {
119+expect(recordInboundSessionMock).toHaveBeenCalledTimes(1);
120+});
121+const recordParams = recordInboundSessionMock.mock.calls.at(0)?.[0] as
122+| {
123+sessionKey?: string;
124+updateLastRoute?: {
125+channel?: string;
126+mainDmOwnerPin?: unknown;
127+sessionKey?: string;
128+to?: string;
129+};
130+}
131+| undefined;
132+expect(recordParams?.sessionKey).toBe("agent:main:imessage:direct:+15550001111");
133+expect(recordParams?.updateLastRoute?.sessionKey).toBe(recordParams?.sessionKey);
134+expect(recordParams?.updateLastRoute?.sessionKey).not.toBe("agent:main:main");
135+expect(recordParams?.updateLastRoute?.channel).toBe("imessage");
136+expect(recordParams?.updateLastRoute?.to).toBe("+15550001111");
137+expect(recordParams?.updateLastRoute?.mainDmOwnerPin).toBeUndefined();
138+});
139+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。