From 2904bbfc475c1bf53ea39d7d3f4bca3cd8afd71f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 9 Aug 2026 13:33:30 -0700 Subject: [PATCH] test(imessage): exercise chat lookup through runtime boundary (#121205) --- extensions/imessage/src/actions-chat-guid.ts | 11 - .../imessage/src/actions.runtime.test.ts | 210 +++++++++--------- extensions/imessage/src/actions.runtime.ts | 7 +- 3 files changed, 111 insertions(+), 117 deletions(-) diff --git a/extensions/imessage/src/actions-chat-guid.ts b/extensions/imessage/src/actions-chat-guid.ts index 93639a762074..dfe7a95a417c 100644 --- a/extensions/imessage/src/actions-chat-guid.ts +++ b/extensions/imessage/src/actions-chat-guid.ts @@ -72,17 +72,6 @@ function chatListCacheSet( } } -export function normalizeDirectChatIdentifierForTest(raw: string): string { - return normalizeDirectChatIdentifier(raw); -} - -export function findChatGuidForTest( - chats: readonly Record[], - target: Extract, -): string | null { - return findChatGuid(chats, target); -} - function findChatGuid( chats: readonly Record[], target: Extract, diff --git a/extensions/imessage/src/actions.runtime.test.ts b/extensions/imessage/src/actions.runtime.test.ts index de6ef9ad236e..923217ae1cd0 100644 --- a/extensions/imessage/src/actions.runtime.test.ts +++ b/extensions/imessage/src/actions.runtime.test.ts @@ -2,6 +2,7 @@ import { access, readFile } from "node:fs/promises"; import { basename, dirname } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { normalizeDirectChatIdentifier } from "./chat-context.js"; const createIMessageRpcClientMock = vi.hoisted(() => vi.fn()); const runIMessageCliJsonCommandMock = vi.hoisted(() => vi.fn()); @@ -19,8 +20,7 @@ vi.mock("./remote-file.js", () => ({ withIMessageRemoteFile: withIMessageRemoteFileMock, })); -const { imessageActionsRuntime, findChatGuidForTest, normalizeDirectChatIdentifierForTest } = - await import("./actions.runtime.js"); +const { imessageActionsRuntime } = await import("./actions.runtime.js"); afterEach(() => { vi.restoreAllMocks(); @@ -672,111 +672,121 @@ describe("findChatGuid cross-format identifier resolution", () => { }, ]; - it("matches a synthesized iMessage;-; target against the chats.list identifier", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "iMessage;-;+12069106512", - }); - expect(result).toBe("any;-;+12069106512"); - }); + type ChatGuidCase = { + name: string; + cliPath: string; + chats: Array>; + target: + | { kind: "chat_id"; chatId: number } + | { kind: "chat_identifier"; chatIdentifier: string }; + expected: string | null; + }; - it("matches a synthesized SMS;-; target the same way", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "SMS;-;+12069106512", - }); - expect(result).toBe("any;-;+12069106512"); - }); + it.each([ + { + name: "matches a synthesized iMessage;-; target against the chats.list identifier", + cliPath: "imsg-cross-format-imessage", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "iMessage;-;+12069106512" }, + expected: "any;-;+12069106512", + }, + { + name: "matches a synthesized SMS;-; target the same way", + cliPath: "imsg-cross-format-sms", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "SMS;-;+12069106512" }, + expected: "any;-;+12069106512", + }, + { + name: "matches a bare identifier exactly", + cliPath: "imsg-cross-format-bare", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "+12069106512" }, + expected: "any;-;+12069106512", + }, + { + name: "matches an any;-; guid form against the chats.list guid column", + cliPath: "imsg-cross-format-any", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "any;-;+12069106512" }, + expected: "any;-;+12069106512", + }, + { + name: "matches a group chat by exact guid", + cliPath: "imsg-cross-format-group-guid", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "iMessage;+;chat0000" }, + expected: "iMessage;+;chat0000", + }, + { + name: "matches a group chat by chat_id", + cliPath: "imsg-cross-format-chat-id", + chats: chatsList, + target: { kind: "chat_id", chatId: 7 }, + expected: "iMessage;+;chat0000", + }, + { + name: "does not coerce non-decimal chat ids from chats.list", + cliPath: "imsg-cross-format-nondecimal-id", + chats: [{ id: "0x7", identifier: "wrong", guid: "iMessage;+;wrong" }], + target: { kind: "chat_id", chatId: 7 }, + expected: null, + }, + { + name: "returns null for a phone number that does not exist in chats.list", + cliPath: "imsg-cross-format-missing-phone", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "iMessage;-;+19999999999" }, + expected: null, + }, + { + name: "does not cross-match different phone numbers via the prefix-stripping path", + cliPath: "imsg-cross-format-different-phone", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "iMessage;-;+18001234567" }, + expected: null, + }, + { + name: "does not match a DM target against a group's chat_identifier", + cliPath: "imsg-cross-format-group-mismatch", + chats: chatsList, + target: { kind: "chat_identifier", chatIdentifier: "iMessage;+;chat-not-here" }, + expected: null, + }, + ] satisfies ChatGuidCase[])("$name", async ({ cliPath, chats, target, expected }) => { + const client = mockRpcChatList(chats); - it("matches a bare identifier exactly", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "+12069106512", - }); - expect(result).toBe("any;-;+12069106512"); - }); - - it("matches an any;-; guid form against the chats.list guid column", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "any;-;+12069106512", - }); - expect(result).toBe("any;-;+12069106512"); - }); - - it("matches a group chat by exact guid", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "iMessage;+;chat0000", - }); - expect(result).toBe("iMessage;+;chat0000"); - }); - - it("matches a group chat by chat_id", () => { - const result = findChatGuidForTest(chatsList, { kind: "chat_id", chatId: 7 }); - expect(result).toBe("iMessage;+;chat0000"); - }); - - it("does not coerce non-decimal chat ids from chats.list", () => { - const result = findChatGuidForTest( - [ - { - id: "0x7", - identifier: "wrong", - guid: "iMessage;+;wrong", - }, - ], - { kind: "chat_id", chatId: 7 }, + await expect( + imessageActionsRuntime.resolveChatGuidForTarget({ + target, + options: { cliPath }, + conversationReadOrigin: "delegated", + }), + ).resolves.toBe(expected); + expect(client.request).toHaveBeenCalledWith( + "chats.list", + { limit: 1000 }, + { timeoutMs: undefined }, ); - expect(result).toBeNull(); - }); - - it("returns null for a phone number that does not exist in chats.list", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "iMessage;-;+19999999999", - }); - expect(result).toBeNull(); - }); - - it("does not cross-match different phone numbers via the prefix-stripping path", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "iMessage;-;+18001234567", - }); - expect(result).toBeNull(); - }); - - it("does not match a DM target against a group's chat_identifier", () => { - const result = findChatGuidForTest(chatsList, { - kind: "chat_identifier", - chatIdentifier: "iMessage;+;chat-not-here", - }); - expect(result).toBeNull(); + expect(client.stop).toHaveBeenCalledOnce(); }); }); describe("normalizeDirectChatIdentifier", () => { - it("strips the iMessage;-; prefix", () => { - expect(normalizeDirectChatIdentifierForTest("iMessage;-;+12069106512")).toBe("+12069106512"); - }); - it("strips the SMS;-; prefix", () => { - expect(normalizeDirectChatIdentifierForTest("SMS;-;+12069106512")).toBe("+12069106512"); - }); - it("strips the any;-; prefix", () => { - expect(normalizeDirectChatIdentifierForTest("any;-;+12069106512")).toBe("+12069106512"); - }); - it("matches case-insensitively", () => { - expect(normalizeDirectChatIdentifierForTest("IMESSAGE;-;+12069106512")).toBe("+12069106512"); - }); - it("leaves group identifiers (iMessage;+;chat...) unchanged", () => { - expect(normalizeDirectChatIdentifierForTest("iMessage;+;chat0000")).toBe("iMessage;+;chat0000"); - expect(normalizeDirectChatIdentifierForTest("iMessage;+;Some@example.com")).toBe( + it.each([ + ["strips the iMessage;-; prefix", "iMessage;-;+12069106512", "+12069106512"], + ["strips the SMS;-; prefix", "SMS;-;+12069106512", "+12069106512"], + ["strips the any;-; prefix", "any;-;+12069106512", "+12069106512"], + ["matches case-insensitively", "IMESSAGE;-;+12069106512", "+12069106512"], + ["leaves group identifiers unchanged", "iMessage;+;chat0000", "iMessage;+;chat0000"], + [ + "leaves group email identifiers unchanged", "iMessage;+;Some@example.com", - ); - }); - it("leaves bare values unchanged", () => { - expect(normalizeDirectChatIdentifierForTest("+12069106512")).toBe("+12069106512"); - expect(normalizeDirectChatIdentifierForTest("foo@bar.com")).toBe("foo@bar.com"); + "iMessage;+;Some@example.com", + ], + ["leaves bare phone values unchanged", "+12069106512", "+12069106512"], + ["leaves bare email values unchanged", "foo@bar.com", "foo@bar.com"], + ])("%s", (_name, input, expected) => { + expect(normalizeDirectChatIdentifier(input)).toBe(expected); }); }); diff --git a/extensions/imessage/src/actions.runtime.ts b/extensions/imessage/src/actions.runtime.ts index a27b031245f3..e3b7b7176c8a 100644 --- a/extensions/imessage/src/actions.runtime.ts +++ b/extensions/imessage/src/actions.runtime.ts @@ -3,11 +3,7 @@ import { basename, parse, win32 } from "node:path"; import { sanitizeUntrustedFileName } from "openclaw/plugin-sdk/security-runtime"; import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path"; import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; -import { - findChatGuidForTest, - normalizeDirectChatIdentifierForTest, - resolveIMessageActionChatGuid, -} from "./actions-chat-guid.js"; +import { resolveIMessageActionChatGuid } from "./actions-chat-guid.js"; import { type IMessageActionTransportOptions, requestIMessageActionRpc, @@ -21,7 +17,6 @@ import { } from "./monitor-reply-cache.js"; import { sanitizeIMessageFinalOutboundText } from "./monitor/sanitize-outbound.js"; import { withIMessageRemoteFile } from "./remote-file.js"; -export { findChatGuidForTest, normalizeDirectChatIdentifierForTest }; type IMessageBridgeActionOptions = IMessageActionTransportOptions & { chatGuid: string;