From f297deeffc94d3e2e95c35af3fb4d2e7c1a3a1ce Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 15:42:05 +0100 Subject: [PATCH] test: clear synology chat channel broad matchers --- extensions/synology-chat/src/channel.test.ts | 146 +++++++++---------- 1 file changed, 66 insertions(+), 80 deletions(-) diff --git a/extensions/synology-chat/src/channel.test.ts b/extensions/synology-chat/src/channel.test.ts index 7ae82f1bb75..338bda78dae 100644 --- a/extensions/synology-chat/src/channel.test.ts +++ b/extensions/synology-chat/src/channel.test.ts @@ -26,6 +26,14 @@ function makeSecurityAccount( return { ...securityAccountDefaults, ...overrides }; } +function expectIncludesSubstring(values: readonly string[], expected: string): void { + expect(values.some((value) => value.includes(expected))).toBe(true); +} + +function mockStringMessages(mock: { mock: { calls: unknown[][] } }): string[] { + return mock.mock.calls.map((call) => String(call[0] ?? "")); +} + const clientModule = await import("./client.js"); const gatewayRuntimeModule = await import("./gateway-runtime.js"); const mockSendMessage = vi.spyOn(clientModule, "sendMessage").mockResolvedValue(true); @@ -112,16 +120,14 @@ describe("createSynologyChatPlugin", () => { }; const plugin = createSynologyChatPlugin(); const account = plugin.config.resolveAccount(cfg, "office"); - expect(account).toMatchObject({ - accountId: "office", - token: "office-token", - incomingUrl: "https://nas/base", - nasHost: "nas-base", - allowedUserIds: ["base-user"], - rateLimitPerMinute: 45, - botName: "Base Bot", - allowInsecureSsl: true, - }); + expect(account.accountId).toBe("office"); + expect(account.token).toBe("office-token"); + expect(account.incomingUrl).toBe("https://nas/base"); + expect(account.nasHost).toBe("nas-base"); + expect(account.allowedUserIds).toEqual(["base-user"]); + expect(account.rateLimitPerMinute).toBe(45); + expect(account.botName).toBe("Base Bot"); + expect(account.allowInsecureSsl).toBe(true); }); it("defaultAccountId returns 'default'", () => { @@ -256,23 +262,21 @@ describe("createSynologyChatPlugin", () => { const plugin = createSynologyChatPlugin(); const account = makeSecurityAccount({ token: "" }); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining("token")])); + expectIncludesSubstring(warnings, "token"); }); it("warns when allowInsecureSsl is true", () => { const plugin = createSynologyChatPlugin(); const account = makeSecurityAccount({ allowInsecureSsl: true }); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining("SSL")])); + expectIncludesSubstring(warnings, "SSL"); }); it("warns when dangerous name matching is enabled", () => { const plugin = createSynologyChatPlugin(); const account = makeSecurityAccount({ dangerouslyAllowNameMatching: true }); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual( - expect.arrayContaining([expect.stringContaining("dangerouslyAllowNameMatching")]), - ); + expectIncludesSubstring(warnings, "dangerouslyAllowNameMatching"); }); it("warns when inherited shared webhookPath is dangerously re-enabled", () => { @@ -283,36 +287,28 @@ describe("createSynologyChatPlugin", () => { dangerouslyAllowInheritedWebhookPath: true, }); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual( - expect.arrayContaining([ - expect.stringContaining("dangerouslyAllowInheritedWebhookPath=true"), - ]), - ); + expectIncludesSubstring(warnings, "dangerouslyAllowInheritedWebhookPath=true"); }); it("warns when dmPolicy is open", () => { const plugin = createSynologyChatPlugin(); const account = makeSecurityAccount({ dmPolicy: "open", allowedUserIds: ["*"] }); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining("open")])); + expectIncludesSubstring(warnings, "open"); }); it("warns when dmPolicy is open and allowedUserIds is empty", () => { const plugin = createSynologyChatPlugin(); const account = makeSecurityAccount({ dmPolicy: "open", allowedUserIds: [] }); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual( - expect.arrayContaining([expect.stringContaining("empty allowedUserIds")]), - ); + expectIncludesSubstring(warnings, "empty allowedUserIds"); }); it("warns when dmPolicy is allowlist and allowedUserIds is empty", () => { const plugin = createSynologyChatPlugin(); const account = makeSecurityAccount(); const warnings = plugin.security.collectWarnings({ cfg: {}, account }); - expect(warnings).toEqual( - expect.arrayContaining([expect.stringContaining("empty allowedUserIds")]), - ); + expectIncludesSubstring(warnings, "empty allowedUserIds"); }); it("warns when named multi-account routes inherit a shared webhookPath", () => { @@ -320,9 +316,7 @@ describe("createSynologyChatPlugin", () => { const cfg = makeSharedWebhookConfig(); const account = plugin.config.resolveAccount(cfg, "alerts"); const warnings = plugin.security.collectWarnings({ cfg, account }); - expect(warnings).toEqual( - expect.arrayContaining([expect.stringContaining("must set an explicit webhookPath")]), - ); + expectIncludesSubstring(warnings, "must set an explicit webhookPath"); }); it("warns when enabled accounts share the same exact webhookPath", () => { @@ -342,9 +336,7 @@ describe("createSynologyChatPlugin", () => { }; const account = plugin.config.resolveAccount(cfg, "alerts"); const warnings = plugin.security.collectWarnings({ cfg, account }); - expect(warnings).toEqual( - expect.arrayContaining([expect.stringContaining("conflicts on webhookPath")]), - ); + expectIncludesSubstring(warnings, "conflicts on webhookPath"); }); it("returns no warnings for fully configured account", () => { @@ -410,42 +402,41 @@ describe("createSynologyChatPlugin", () => { }, }; - await expect( - verifyChannelMessageAdapterCapabilityProofs({ - adapterName: "synology-chat", - adapter: plugin.message, - proofs: { - text: async () => { - const result = await plugin.message.send?.text?.({ - cfg, - text: "hello", - to: "user1", - }); - expect(result?.receipt.parts[0]?.kind).toBe("text"); - expect(result?.receipt.platformMessageIds).toHaveLength(1); - }, - media: async () => { - const result = await plugin.message.send?.media?.({ - cfg, - text: "image", - mediaUrl: "https://example.com/img.png", - to: "user1", - }); - expect(result?.receipt.parts[0]?.kind).toBe("media"); - expect(result?.receipt.platformMessageIds).toHaveLength(1); - }, - messageSendingHooks: () => { - expect(plugin.message.durableFinal?.capabilities?.messageSendingHooks).toBe(true); - }, + const results = await verifyChannelMessageAdapterCapabilityProofs({ + adapterName: "synology-chat", + adapter: plugin.message, + proofs: { + text: async () => { + const result = await plugin.message.send?.text?.({ + cfg, + text: "hello", + to: "user1", + }); + expect(result?.receipt.parts[0]?.kind).toBe("text"); + expect(result?.receipt.platformMessageIds).toHaveLength(1); }, - }), - ).resolves.toEqual( - expect.arrayContaining([ - { capability: "text", status: "verified" }, - { capability: "media", status: "verified" }, - { capability: "messageSendingHooks", status: "verified" }, - ]), + media: async () => { + const result = await plugin.message.send?.media?.({ + cfg, + text: "image", + mediaUrl: "https://example.com/img.png", + to: "user1", + }); + expect(result?.receipt.parts[0]?.kind).toBe("media"); + expect(result?.receipt.platformMessageIds).toHaveLength(1); + }, + messageSendingHooks: () => { + expect(plugin.message.durableFinal?.capabilities?.messageSendingHooks).toBe(true); + }, + }, + }); + + const statusByCapability = new Map( + results.map(({ capability, status }) => [capability, status]), ); + expect(statusByCapability.get("text")).toBe("verified"); + expect(statusByCapability.get("media")).toBe("verified"); + expect(statusByCapability.get("messageSendingHooks")).toBe("verified"); }); it("sendText throws when no incomingUrl", async () => { @@ -479,10 +470,8 @@ describe("createSynologyChatPlugin", () => { text: "hello", to: "user1", }); - expect(result).toMatchObject({ - channel: "synology-chat", - chatId: "user1", - }); + expect(result.channel).toBe("synology-chat"); + expect(result.chatId).toBe("user1"); expect(result.messageId).toMatch(/^sc-\d+$/); expect(result.receipt.primaryPlatformMessageId).toBe(result.messageId); expect(result.receipt.parts[0]?.kind).toBe("text"); @@ -605,7 +594,7 @@ describe("createSynologyChatPlugin", () => { const result = plugin.gateway.startAccount(ctx); await expectPendingStartAccountPromise(result, abortController); - expect(ctx.log.warn).toHaveBeenCalledWith(expect.stringContaining("empty allowedUserIds")); + expectIncludesSubstring(mockStringMessages(ctx.log.warn), "empty allowedUserIds"); expect(registerMock).not.toHaveBeenCalled(); }); @@ -623,8 +612,9 @@ describe("createSynologyChatPlugin", () => { const result = plugin.gateway.startAccount(ctx); await expectPendingStartAccountPromise(result, abortController); - expect(ctx.log.warn).toHaveBeenCalledWith( - expect.stringContaining("dmPolicy=open but empty allowedUserIds"), + expectIncludesSubstring( + mockStringMessages(ctx.log.warn), + "dmPolicy=open but empty allowedUserIds", ); expect(registerMock).not.toHaveBeenCalled(); }); @@ -639,9 +629,7 @@ describe("createSynologyChatPlugin", () => { const result = plugin.gateway.startAccount(ctx); await expectPendingStartAccountPromise(result, abortController); - expect(ctx.log.warn).toHaveBeenCalledWith( - expect.stringContaining("must set an explicit webhookPath"), - ); + expectIncludesSubstring(mockStringMessages(ctx.log.warn), "must set an explicit webhookPath"); expect(registerMock).not.toHaveBeenCalled(); }); @@ -656,9 +644,7 @@ describe("createSynologyChatPlugin", () => { const result = plugin.gateway.startAccount(ctx); await expectPendingStartAccountPromise(result, abortController); - expect(ctx.log.warn).toHaveBeenCalledWith( - expect.stringContaining("conflicts on webhookPath"), - ); + expectIncludesSubstring(mockStringMessages(ctx.log.warn), "conflicts on webhookPath"); expect(registerMock).not.toHaveBeenCalled(); });