From 93d25a520f4f77dbf07f7a047dc473d0473dc5a8 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 9 Mar 2026 05:41:46 -0400 Subject: [PATCH] Matrix: cover client wrapper helpers --- .../matrix/src/matrix/actions/client.test.ts | 80 ++++++++++++++++++- .../matrix/client-resolver.test-helpers.ts | 2 + .../matrix/src/matrix/send/client.test.ts | 29 ++++++- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/extensions/matrix/src/matrix/actions/client.test.ts b/extensions/matrix/src/matrix/actions/client.test.ts index 5f7f122a823..8d34f0a7375 100644 --- a/extensions/matrix/src/matrix/actions/client.test.ts +++ b/extensions/matrix/src/matrix/actions/client.test.ts @@ -5,6 +5,8 @@ import { primeMatrixClientResolverMocks, } from "../client-resolver.test-helpers.js"; +const resolveMatrixRoomIdMock = vi.fn(); + const { loadConfigMock, getMatrixRuntimeMock, @@ -30,14 +32,29 @@ vi.mock("../client.js", () => ({ resolveMatrixAuthContext: resolveMatrixAuthContextMock, })); +vi.mock("../send.js", () => ({ + resolveMatrixRoomId: (...args: unknown[]) => resolveMatrixRoomIdMock(...args), +})); + let resolveActionClient: typeof import("./client.js").resolveActionClient; +let withResolvedActionClient: typeof import("./client.js").withResolvedActionClient; +let withResolvedRoomAction: typeof import("./client.js").withResolvedRoomAction; +let withStartedActionClient: typeof import("./client.js").withStartedActionClient; describe("resolveActionClient", () => { beforeEach(async () => { vi.resetModules(); primeMatrixClientResolverMocks(); + resolveMatrixRoomIdMock + .mockReset() + .mockImplementation(async (_client, roomId: string) => roomId); - ({ resolveActionClient } = await import("./client.js")); + ({ + resolveActionClient, + withResolvedActionClient, + withResolvedRoomAction, + withStartedActionClient, + } = await import("./client.js")); }); afterEach(() => { @@ -162,4 +179,65 @@ describe("resolveActionClient", () => { }), ); }); + + it("stops one-off action clients after wrapped calls succeed", async () => { + const oneOffClient = createMockMatrixClient(); + createMatrixClientMock.mockResolvedValue(oneOffClient); + + const result = await withResolvedActionClient({ accountId: "default" }, async (client) => { + expect(client).toBe(oneOffClient); + return "ok"; + }); + + expect(result).toBe("ok"); + expect(oneOffClient.stop).toHaveBeenCalledTimes(1); + expect(oneOffClient.stopAndPersist).not.toHaveBeenCalled(); + }); + + it("still stops one-off action clients when the wrapped call throws", async () => { + const oneOffClient = createMockMatrixClient(); + createMatrixClientMock.mockResolvedValue(oneOffClient); + + await expect( + withResolvedActionClient({ accountId: "default" }, async () => { + throw new Error("boom"); + }), + ).rejects.toThrow("boom"); + + expect(oneOffClient.stop).toHaveBeenCalledTimes(1); + expect(oneOffClient.stopAndPersist).not.toHaveBeenCalled(); + }); + + it("persists one-off action clients after started wrappers complete", async () => { + const oneOffClient = createMockMatrixClient(); + createMatrixClientMock.mockResolvedValue(oneOffClient); + + await withStartedActionClient({ accountId: "default" }, async (client) => { + expect(client).toBe(oneOffClient); + return undefined; + }); + + expect(oneOffClient.start).toHaveBeenCalledTimes(1); + expect(oneOffClient.stop).not.toHaveBeenCalled(); + expect(oneOffClient.stopAndPersist).toHaveBeenCalledTimes(1); + }); + + it("resolves room ids before running wrapped room actions", async () => { + const oneOffClient = createMockMatrixClient(); + createMatrixClientMock.mockResolvedValue(oneOffClient); + resolveMatrixRoomIdMock.mockResolvedValue("!room:example.org"); + + const result = await withResolvedRoomAction( + "room:#ops:example.org", + { accountId: "default" }, + async (client, resolvedRoom) => { + expect(client).toBe(oneOffClient); + return resolvedRoom; + }, + ); + + expect(resolveMatrixRoomIdMock).toHaveBeenCalledWith(oneOffClient, "room:#ops:example.org"); + expect(result).toBe("!room:example.org"); + expect(oneOffClient.stop).toHaveBeenCalledTimes(1); + }); }); diff --git a/extensions/matrix/src/matrix/client-resolver.test-helpers.ts b/extensions/matrix/src/matrix/client-resolver.test-helpers.ts index 7a2d9c0e3b2..99e9d2f39f0 100644 --- a/extensions/matrix/src/matrix/client-resolver.test-helpers.ts +++ b/extensions/matrix/src/matrix/client-resolver.test-helpers.ts @@ -15,6 +15,8 @@ export function createMockMatrixClient(): MatrixClient { return { prepareForOneOff: vi.fn(async () => undefined), start: vi.fn(async () => undefined), + stop: vi.fn(() => undefined), + stopAndPersist: vi.fn(async () => undefined), } as unknown as MatrixClient; } diff --git a/extensions/matrix/src/matrix/send/client.test.ts b/extensions/matrix/src/matrix/send/client.test.ts index 7a8147cecef..4cc3d09b35b 100644 --- a/extensions/matrix/src/matrix/send/client.test.ts +++ b/extensions/matrix/src/matrix/send/client.test.ts @@ -30,6 +30,7 @@ vi.mock("../../runtime.js", () => ({ })); let resolveMatrixClient: typeof import("./client.js").resolveMatrixClient; +let withResolvedMatrixClient: typeof import("./client.js").withResolvedMatrixClient; describe("resolveMatrixClient", () => { beforeEach(async () => { @@ -38,7 +39,7 @@ describe("resolveMatrixClient", () => { resolved: {}, }); - ({ resolveMatrixClient } = await import("./client.js")); + ({ resolveMatrixClient, withResolvedMatrixClient } = await import("./client.js")); }); afterEach(() => { @@ -104,4 +105,30 @@ describe("resolveMatrixClient", () => { }), ); }); + + it("stops one-off matrix clients after wrapped sends succeed", async () => { + const oneOffClient = createMockMatrixClient(); + createMatrixClientMock.mockResolvedValue(oneOffClient); + + const result = await withResolvedMatrixClient({ accountId: "default" }, async (client) => { + expect(client).toBe(oneOffClient); + return "ok"; + }); + + expect(result).toBe("ok"); + expect(oneOffClient.stop).toHaveBeenCalledTimes(1); + }); + + it("still stops one-off matrix clients when wrapped sends fail", async () => { + const oneOffClient = createMockMatrixClient(); + createMatrixClientMock.mockResolvedValue(oneOffClient); + + await expect( + withResolvedMatrixClient({ accountId: "default" }, async () => { + throw new Error("boom"); + }), + ).rejects.toThrow("boom"); + + expect(oneOffClient.stop).toHaveBeenCalledTimes(1); + }); });