From 2bc62c67e7860d731248d4d79e2a26ba821b389f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 14 Mar 2026 23:19:20 -0700 Subject: [PATCH] Synology Chat: ignore non-registerable path collisions --- extensions/synology-chat/src/accounts.ts | 7 +++- extensions/synology-chat/src/channel.test.ts | 41 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/extensions/synology-chat/src/accounts.ts b/extensions/synology-chat/src/accounts.ts index 7b730d5856d..7732efd6a97 100644 --- a/extensions/synology-chat/src/accounts.ts +++ b/extensions/synology-chat/src/accounts.ts @@ -98,7 +98,7 @@ export function resolveAccount(cfg: any, accountId?: string | null): ResolvedSyn export function findConflictingWebhookPathAccountIds(cfg: any, accountId: string): string[] { const current = resolveAccount(cfg, accountId); const currentPath = current.webhookPath.trim(); - if (!current.enabled || !current.token.trim() || !currentPath) { + if (!current.enabled || !current.token.trim() || !current.incomingUrl.trim() || !currentPath) { return []; } @@ -107,7 +107,10 @@ export function findConflictingWebhookPathAccountIds(cfg: any, accountId: string .map((candidateId) => resolveAccount(cfg, candidateId)) .filter( (candidate) => - candidate.enabled && candidate.token.trim() && candidate.webhookPath.trim() === currentPath, + candidate.enabled && + candidate.token.trim() && + candidate.incomingUrl.trim() && + candidate.webhookPath.trim() === currentPath, ) .map((candidate) => candidate.accountId); } diff --git a/extensions/synology-chat/src/channel.test.ts b/extensions/synology-chat/src/channel.test.ts index 6c06ada44ef..7bfa0f7d96a 100644 --- a/extensions/synology-chat/src/channel.test.ts +++ b/extensions/synology-chat/src/channel.test.ts @@ -375,5 +375,46 @@ describe("createSynologyChatPlugin", () => { expect.stringContaining("Each enabled account must use a unique webhookPath."), ); }); + + it("ignores accounts with a shared path when they are missing incomingUrl", async () => { + const registerMock = registerPluginHttpRouteMock; + registerMock.mockClear(); + const plugin = createSynologyChatPlugin(); + const abortController = new AbortController(); + const log = { info: vi.fn(), warn: vi.fn(), error: vi.fn() }; + + const result = plugin.gateway.startAccount({ + cfg: { + channels: { + "synology-chat": { + enabled: true, + token: "base-token", + incomingUrl: "https://nas/incoming", + webhookPath: "/webhook/synology/shared", + dmPolicy: "allowlist", + allowedUserIds: ["owner-a"], + accounts: { + alerts: { + enabled: true, + token: "alerts-token", + incomingUrl: "", + webhookPath: "/webhook/synology/shared", + dmPolicy: "open", + }, + }, + }, + }, + }, + accountId: "default", + log, + abortSignal: abortController.signal, + }); + + await expectPendingStartAccountPromise(result, abortController); + expect(registerMock).toHaveBeenCalledTimes(1); + expect(log.error).not.toHaveBeenCalledWith( + expect.stringContaining("Each enabled account must use a unique webhookPath."), + ); + }); }); });