perf(test): eliminate resetModules via injectable seams

This commit is contained in:
Peter Steinberger
2026-02-13 16:20:31 +00:00
parent a844fb161c
commit b272158fe4
21 changed files with 223 additions and 188 deletions

View File

@@ -1,49 +1,32 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const loadSendMessageIMessage = async () => await import("./send.js");
import type { ResolvedIMessageAccount } from "./accounts.js";
import { sendMessageIMessage } from "./send.js";
const requestMock = vi.fn();
const stopMock = vi.fn();
vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: () => ({}),
};
});
vi.mock("./client.js", () => ({
createIMessageRpcClient: vi.fn().mockResolvedValue({
request: (...args: unknown[]) => requestMock(...args),
stop: (...args: unknown[]) => stopMock(...args),
}),
}));
vi.mock("../web/media.js", () => ({
loadWebMedia: vi.fn().mockResolvedValue({
buffer: Buffer.from("data"),
contentType: "image/jpeg",
}),
}));
vi.mock("../media/store.js", () => ({
saveMediaBuffer: vi.fn().mockResolvedValue({
path: "/tmp/imessage-media.jpg",
contentType: "image/jpeg",
}),
}));
const defaultAccount: ResolvedIMessageAccount = {
accountId: "default",
enabled: true,
configured: false,
config: {},
};
describe("sendMessageIMessage", () => {
beforeEach(() => {
requestMock.mockReset().mockResolvedValue({ ok: true });
stopMock.mockReset().mockResolvedValue(undefined);
vi.resetModules();
});
it("sends to chat_id targets", async () => {
const { sendMessageIMessage } = await loadSendMessageIMessage();
await sendMessageIMessage("chat_id:123", "hi");
await sendMessageIMessage("chat_id:123", "hi", {
account: defaultAccount,
config: {},
client: {
request: (...args: unknown[]) => requestMock(...args),
stop: (...args: unknown[]) => stopMock(...args),
} as unknown as import("./client.js").IMessageRpcClient,
});
const params = requestMock.mock.calls[0]?.[1] as Record<string, unknown>;
expect(requestMock).toHaveBeenCalledWith("send", expect.any(Object), expect.any(Object));
expect(params.chat_id).toBe(123);
@@ -51,16 +34,33 @@ describe("sendMessageIMessage", () => {
});
it("applies sms service prefix", async () => {
const { sendMessageIMessage } = await loadSendMessageIMessage();
await sendMessageIMessage("sms:+1555", "hello");
await sendMessageIMessage("sms:+1555", "hello", {
account: defaultAccount,
config: {},
client: {
request: (...args: unknown[]) => requestMock(...args),
stop: (...args: unknown[]) => stopMock(...args),
} as unknown as import("./client.js").IMessageRpcClient,
});
const params = requestMock.mock.calls[0]?.[1] as Record<string, unknown>;
expect(params.service).toBe("sms");
expect(params.to).toBe("+1555");
});
it("adds file attachment with placeholder text", async () => {
const { sendMessageIMessage } = await loadSendMessageIMessage();
await sendMessageIMessage("chat_id:7", "", { mediaUrl: "http://x/y.jpg" });
await sendMessageIMessage("chat_id:7", "", {
mediaUrl: "http://x/y.jpg",
account: defaultAccount,
config: {},
resolveAttachmentImpl: async () => ({
path: "/tmp/imessage-media.jpg",
contentType: "image/jpeg",
}),
client: {
request: (...args: unknown[]) => requestMock(...args),
stop: (...args: unknown[]) => stopMock(...args),
} as unknown as import("./client.js").IMessageRpcClient,
});
const params = requestMock.mock.calls[0]?.[1] as Record<string, unknown>;
expect(params.file).toBe("/tmp/imessage-media.jpg");
expect(params.text).toBe("<media:image>");
@@ -68,8 +68,14 @@ describe("sendMessageIMessage", () => {
it("returns message id when rpc provides one", async () => {
requestMock.mockResolvedValue({ ok: true, id: 123 });
const { sendMessageIMessage } = await loadSendMessageIMessage();
const result = await sendMessageIMessage("chat_id:7", "hello");
const result = await sendMessageIMessage("chat_id:7", "hello", {
account: defaultAccount,
config: {},
client: {
request: (...args: unknown[]) => requestMock(...args),
stop: (...args: unknown[]) => stopMock(...args),
} as unknown as import("./client.js").IMessageRpcClient,
});
expect(result.messageId).toBe("123");
});
});

View File

@@ -4,7 +4,7 @@ import { convertMarkdownTables } from "../markdown/tables.js";
import { mediaKindFromMime } from "../media/constants.js";
import { saveMediaBuffer } from "../media/store.js";
import { loadWebMedia } from "../web/media.js";
import { resolveIMessageAccount } from "./accounts.js";
import { resolveIMessageAccount, type ResolvedIMessageAccount } from "./accounts.js";
import { createIMessageRpcClient, type IMessageRpcClient } from "./client.js";
import { formatIMessageChatTarget, type IMessageService, parseIMessageTarget } from "./targets.js";
@@ -19,6 +19,13 @@ export type IMessageSendOpts = {
timeoutMs?: number;
chatId?: number;
client?: IMessageRpcClient;
config?: ReturnType<typeof loadConfig>;
account?: ResolvedIMessageAccount;
resolveAttachmentImpl?: (
mediaUrl: string,
maxBytes: number,
) => Promise<{ path: string; contentType?: string }>;
createClient?: (params: { cliPath: string; dbPath?: string }) => Promise<IMessageRpcClient>;
};
export type IMessageSendResult = {
@@ -58,11 +65,13 @@ export async function sendMessageIMessage(
text: string,
opts: IMessageSendOpts = {},
): Promise<IMessageSendResult> {
const cfg = loadConfig();
const account = resolveIMessageAccount({
cfg,
accountId: opts.accountId,
});
const cfg = opts.config ?? loadConfig();
const account =
opts.account ??
resolveIMessageAccount({
cfg,
accountId: opts.accountId,
});
const cliPath = opts.cliPath?.trim() || account.config.cliPath?.trim() || "imsg";
const dbPath = opts.dbPath?.trim() || account.config.dbPath?.trim();
const target = parseIMessageTarget(opts.chatId ? formatIMessageChatTarget(opts.chatId) : to);
@@ -81,7 +90,8 @@ export async function sendMessageIMessage(
let filePath: string | undefined;
if (opts.mediaUrl?.trim()) {
const resolved = await resolveAttachment(opts.mediaUrl.trim(), maxBytes);
const resolveAttachmentFn = opts.resolveAttachmentImpl ?? resolveAttachment;
const resolved = await resolveAttachmentFn(opts.mediaUrl.trim(), maxBytes);
filePath = resolved.path;
if (!message.trim()) {
const kind = mediaKindFromMime(resolved.contentType ?? undefined);
@@ -122,7 +132,11 @@ export async function sendMessageIMessage(
params.to = target.to;
}
const client = opts.client ?? (await createIMessageRpcClient({ cliPath, dbPath }));
const client =
opts.client ??
(opts.createClient
? await opts.createClient({ cliPath, dbPath })
: await createIMessageRpcClient({ cliPath, dbPath }));
const shouldClose = !opts.client;
try {
const result = await client.request<{ ok?: string }>("send", params, {