
















@@ -26,6 +26,14 @@ function makeSecurityAccount(
2626return { ...securityAccountDefaults, ...overrides };
2727}
282829+function expectIncludesSubstring(values: readonly string[], expected: string): void {
30+expect(values.some((value) => value.includes(expected))).toBe(true);
31+}
32+33+function mockStringMessages(mock: { mock: { calls: unknown[][] } }): string[] {
34+return mock.mock.calls.map((call) => String(call[0] ?? ""));
35+}
36+2937const clientModule = await import("./client.js");
3038const gatewayRuntimeModule = await import("./gateway-runtime.js");
3139const mockSendMessage = vi.spyOn(clientModule, "sendMessage").mockResolvedValue(true);
@@ -112,16 +120,14 @@ describe("createSynologyChatPlugin", () => {
112120};
113121const plugin = createSynologyChatPlugin();
114122const account = plugin.config.resolveAccount(cfg, "office");
115-expect(account).toMatchObject({
116-accountId: "office",
117-token: "office-token",
118-incomingUrl: "https://nas/base",
119-nasHost: "nas-base",
120-allowedUserIds: ["base-user"],
121-rateLimitPerMinute: 45,
122-botName: "Base Bot",
123-allowInsecureSsl: true,
124-});
123+expect(account.accountId).toBe("office");
124+expect(account.token).toBe("office-token");
125+expect(account.incomingUrl).toBe("https://nas/base");
126+expect(account.nasHost).toBe("nas-base");
127+expect(account.allowedUserIds).toEqual(["base-user"]);
128+expect(account.rateLimitPerMinute).toBe(45);
129+expect(account.botName).toBe("Base Bot");
130+expect(account.allowInsecureSsl).toBe(true);
125131});
126132127133it("defaultAccountId returns 'default'", () => {
@@ -256,23 +262,21 @@ describe("createSynologyChatPlugin", () => {
256262const plugin = createSynologyChatPlugin();
257263const account = makeSecurityAccount({ token: "" });
258264const warnings = plugin.security.collectWarnings({ cfg: {}, account });
259-expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining("token")]));
265+expectIncludesSubstring(warnings, "token");
260266});
261267262268it("warns when allowInsecureSsl is true", () => {
263269const plugin = createSynologyChatPlugin();
264270const account = makeSecurityAccount({ allowInsecureSsl: true });
265271const warnings = plugin.security.collectWarnings({ cfg: {}, account });
266-expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining("SSL")]));
272+expectIncludesSubstring(warnings, "SSL");
267273});
268274269275it("warns when dangerous name matching is enabled", () => {
270276const plugin = createSynologyChatPlugin();
271277const account = makeSecurityAccount({ dangerouslyAllowNameMatching: true });
272278const warnings = plugin.security.collectWarnings({ cfg: {}, account });
273-expect(warnings).toEqual(
274-expect.arrayContaining([expect.stringContaining("dangerouslyAllowNameMatching")]),
275-);
279+expectIncludesSubstring(warnings, "dangerouslyAllowNameMatching");
276280});
277281278282it("warns when inherited shared webhookPath is dangerously re-enabled", () => {
@@ -283,46 +287,36 @@ describe("createSynologyChatPlugin", () => {
283287dangerouslyAllowInheritedWebhookPath: true,
284288});
285289const warnings = plugin.security.collectWarnings({ cfg: {}, account });
286-expect(warnings).toEqual(
287-expect.arrayContaining([
288-expect.stringContaining("dangerouslyAllowInheritedWebhookPath=true"),
289-]),
290-);
290+expectIncludesSubstring(warnings, "dangerouslyAllowInheritedWebhookPath=true");
291291});
292292293293it("warns when dmPolicy is open", () => {
294294const plugin = createSynologyChatPlugin();
295295const account = makeSecurityAccount({ dmPolicy: "open", allowedUserIds: ["*"] });
296296const warnings = plugin.security.collectWarnings({ cfg: {}, account });
297-expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining("open")]));
297+expectIncludesSubstring(warnings, "open");
298298});
299299300300it("warns when dmPolicy is open and allowedUserIds is empty", () => {
301301const plugin = createSynologyChatPlugin();
302302const account = makeSecurityAccount({ dmPolicy: "open", allowedUserIds: [] });
303303const warnings = plugin.security.collectWarnings({ cfg: {}, account });
304-expect(warnings).toEqual(
305-expect.arrayContaining([expect.stringContaining("empty allowedUserIds")]),
306-);
304+expectIncludesSubstring(warnings, "empty allowedUserIds");
307305});
308306309307it("warns when dmPolicy is allowlist and allowedUserIds is empty", () => {
310308const plugin = createSynologyChatPlugin();
311309const account = makeSecurityAccount();
312310const warnings = plugin.security.collectWarnings({ cfg: {}, account });
313-expect(warnings).toEqual(
314-expect.arrayContaining([expect.stringContaining("empty allowedUserIds")]),
315-);
311+expectIncludesSubstring(warnings, "empty allowedUserIds");
316312});
317313318314it("warns when named multi-account routes inherit a shared webhookPath", () => {
319315const plugin = createSynologyChatPlugin();
320316const cfg = makeSharedWebhookConfig();
321317const account = plugin.config.resolveAccount(cfg, "alerts");
322318const warnings = plugin.security.collectWarnings({ cfg, account });
323-expect(warnings).toEqual(
324-expect.arrayContaining([expect.stringContaining("must set an explicit webhookPath")]),
325-);
319+expectIncludesSubstring(warnings, "must set an explicit webhookPath");
326320});
327321328322it("warns when enabled accounts share the same exact webhookPath", () => {
@@ -342,9 +336,7 @@ describe("createSynologyChatPlugin", () => {
342336};
343337const account = plugin.config.resolveAccount(cfg, "alerts");
344338const warnings = plugin.security.collectWarnings({ cfg, account });
345-expect(warnings).toEqual(
346-expect.arrayContaining([expect.stringContaining("conflicts on webhookPath")]),
347-);
339+expectIncludesSubstring(warnings, "conflicts on webhookPath");
348340});
349341350342it("returns no warnings for fully configured account", () => {
@@ -410,42 +402,41 @@ describe("createSynologyChatPlugin", () => {
410402},
411403};
412404413-await expect(
414-verifyChannelMessageAdapterCapabilityProofs({
415-adapterName: "synology-chat",
416-adapter: plugin.message,
417-proofs: {
418-text: async () => {
419-const result = await plugin.message.send?.text?.({
420- cfg,
421-text: "hello",
422-to: "user1",
423-});
424-expect(result?.receipt.parts[0]?.kind).toBe("text");
425-expect(result?.receipt.platformMessageIds).toHaveLength(1);
426-},
427-media: async () => {
428-const result = await plugin.message.send?.media?.({
429- cfg,
430-text: "image",
431-mediaUrl: "https://example.com/img.png",
432-to: "user1",
433-});
434-expect(result?.receipt.parts[0]?.kind).toBe("media");
435-expect(result?.receipt.platformMessageIds).toHaveLength(1);
436-},
437-messageSendingHooks: () => {
438-expect(plugin.message.durableFinal?.capabilities?.messageSendingHooks).toBe(true);
439-},
405+const results = await verifyChannelMessageAdapterCapabilityProofs({
406+adapterName: "synology-chat",
407+adapter: plugin.message,
408+proofs: {
409+text: async () => {
410+const result = await plugin.message.send?.text?.({
411+ cfg,
412+text: "hello",
413+to: "user1",
414+});
415+expect(result?.receipt.parts[0]?.kind).toBe("text");
416+expect(result?.receipt.platformMessageIds).toHaveLength(1);
440417},
441-}),
442-).resolves.toEqual(
443-expect.arrayContaining([
444-{ capability: "text", status: "verified" },
445-{ capability: "media", status: "verified" },
446-{ capability: "messageSendingHooks", status: "verified" },
447-]),
418+media: async () => {
419+const result = await plugin.message.send?.media?.({
420+ cfg,
421+text: "image",
422+mediaUrl: "https://example.com/img.png",
423+to: "user1",
424+});
425+expect(result?.receipt.parts[0]?.kind).toBe("media");
426+expect(result?.receipt.platformMessageIds).toHaveLength(1);
427+},
428+messageSendingHooks: () => {
429+expect(plugin.message.durableFinal?.capabilities?.messageSendingHooks).toBe(true);
430+},
431+},
432+});
433+434+const statusByCapability = new Map(
435+results.map(({ capability, status }) => [capability, status]),
448436);
437+expect(statusByCapability.get("text")).toBe("verified");
438+expect(statusByCapability.get("media")).toBe("verified");
439+expect(statusByCapability.get("messageSendingHooks")).toBe("verified");
449440});
450441451442it("sendText throws when no incomingUrl", async () => {
@@ -479,10 +470,8 @@ describe("createSynologyChatPlugin", () => {
479470text: "hello",
480471to: "user1",
481472});
482-expect(result).toMatchObject({
483-channel: "synology-chat",
484-chatId: "user1",
485-});
473+expect(result.channel).toBe("synology-chat");
474+expect(result.chatId).toBe("user1");
486475expect(result.messageId).toMatch(/^sc-\d+$/);
487476expect(result.receipt.primaryPlatformMessageId).toBe(result.messageId);
488477expect(result.receipt.parts[0]?.kind).toBe("text");
@@ -605,7 +594,7 @@ describe("createSynologyChatPlugin", () => {
605594606595const result = plugin.gateway.startAccount(ctx);
607596await expectPendingStartAccountPromise(result, abortController);
608-expect(ctx.log.warn).toHaveBeenCalledWith(expect.stringContaining("empty allowedUserIds"));
597+expectIncludesSubstring(mockStringMessages(ctx.log.warn), "empty allowedUserIds");
609598expect(registerMock).not.toHaveBeenCalled();
610599});
611600@@ -623,8 +612,9 @@ describe("createSynologyChatPlugin", () => {
623612624613const result = plugin.gateway.startAccount(ctx);
625614await expectPendingStartAccountPromise(result, abortController);
626-expect(ctx.log.warn).toHaveBeenCalledWith(
627-expect.stringContaining("dmPolicy=open but empty allowedUserIds"),
615+expectIncludesSubstring(
616+mockStringMessages(ctx.log.warn),
617+"dmPolicy=open but empty allowedUserIds",
628618);
629619expect(registerMock).not.toHaveBeenCalled();
630620});
@@ -639,9 +629,7 @@ describe("createSynologyChatPlugin", () => {
639629640630const result = plugin.gateway.startAccount(ctx);
641631await expectPendingStartAccountPromise(result, abortController);
642-expect(ctx.log.warn).toHaveBeenCalledWith(
643-expect.stringContaining("must set an explicit webhookPath"),
644-);
632+expectIncludesSubstring(mockStringMessages(ctx.log.warn), "must set an explicit webhookPath");
645633expect(registerMock).not.toHaveBeenCalled();
646634});
647635@@ -656,9 +644,7 @@ describe("createSynologyChatPlugin", () => {
656644657645const result = plugin.gateway.startAccount(ctx);
658646await expectPendingStartAccountPromise(result, abortController);
659-expect(ctx.log.warn).toHaveBeenCalledWith(
660-expect.stringContaining("conflicts on webhookPath"),
661-);
647+expectIncludesSubstring(mockStringMessages(ctx.log.warn), "conflicts on webhookPath");
662648expect(registerMock).not.toHaveBeenCalled();
663649});
664650此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。