diff --git a/extensions/telegram/src/api-fetch.test.ts b/extensions/telegram/src/api-fetch.test.ts index a5cafdc60b1..464057347fc 100644 --- a/extensions/telegram/src/api-fetch.test.ts +++ b/extensions/telegram/src/api-fetch.test.ts @@ -1,5 +1,5 @@ import { createRequire } from "node:module"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { fetchTelegramChatId } from "./api-fetch.js"; const require = createRequire(import.meta.url); @@ -10,6 +10,31 @@ const { kHttpsProxyAgent, kNoProxyAgent } = require("undici/lib/core/symbols.js" kHttpsProxyAgent: symbol; kNoProxyAgent: symbol; }; +const proxyMocks = vi.hoisted(() => { + const undiciFetch = vi.fn(); + const proxyAgentSpy = vi.fn(); + const setGlobalDispatcher = vi.fn(); + class ProxyAgent { + static lastCreated: ProxyAgent | undefined; + proxyUrl: string; + constructor(proxyUrl: string) { + this.proxyUrl = proxyUrl; + ProxyAgent.lastCreated = this; + proxyAgentSpy(proxyUrl); + } + } + + return { + ProxyAgent, + undiciFetch, + proxyAgentSpy, + setGlobalDispatcher, + getLastAgent: () => ProxyAgent.lastCreated, + }; +}); + +let getProxyUrlFromFetch: typeof import("./proxy.js").getProxyUrlFromFetch; +let makeProxyFetch: typeof import("./proxy.js").makeProxyFetch; function getOwnSymbolValue( target: Record, @@ -136,3 +161,40 @@ describe("undici env proxy semantics", () => { ); }); }); + +describe("makeProxyFetch", () => { + beforeEach(async () => { + vi.resetModules(); + proxyMocks.undiciFetch.mockReset(); + proxyMocks.proxyAgentSpy.mockClear(); + proxyMocks.setGlobalDispatcher.mockClear(); + vi.doMock("undici", () => ({ + ProxyAgent: proxyMocks.ProxyAgent, + fetch: proxyMocks.undiciFetch, + setGlobalDispatcher: proxyMocks.setGlobalDispatcher, + })); + ({ getProxyUrlFromFetch, makeProxyFetch } = await import("./proxy.js")); + }); + + it("uses undici fetch with ProxyAgent dispatcher", async () => { + const proxyUrl = "http://proxy.test:8080"; + proxyMocks.undiciFetch.mockResolvedValue({ ok: true }); + + const proxyFetch = makeProxyFetch(proxyUrl); + await proxyFetch("https://api.telegram.org/bot123/getMe"); + + expect(proxyMocks.proxyAgentSpy).toHaveBeenCalledWith(proxyUrl); + expect(proxyMocks.undiciFetch).toHaveBeenCalledWith( + "https://api.telegram.org/bot123/getMe", + expect.objectContaining({ dispatcher: proxyMocks.getLastAgent() }), + ); + expect(proxyMocks.setGlobalDispatcher).not.toHaveBeenCalled(); + }); + + it("attaches proxy metadata for resolver transport handling", () => { + const proxyUrl = "http://proxy.test:8080"; + const proxyFetch = makeProxyFetch(proxyUrl); + + expect(getProxyUrlFromFetch(proxyFetch)).toBe(proxyUrl); + }); +}); diff --git a/extensions/telegram/src/proxy.test.ts b/extensions/telegram/src/proxy.test.ts deleted file mode 100644 index 28f31e70784..00000000000 --- a/extensions/telegram/src/proxy.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const mocks = vi.hoisted(() => { - const undiciFetch = vi.fn(); - const proxyAgentSpy = vi.fn(); - const setGlobalDispatcher = vi.fn(); - class ProxyAgent { - static lastCreated: ProxyAgent | undefined; - proxyUrl: string; - constructor(proxyUrl: string) { - this.proxyUrl = proxyUrl; - ProxyAgent.lastCreated = this; - proxyAgentSpy(proxyUrl); - } - } - - return { - ProxyAgent, - undiciFetch, - proxyAgentSpy, - setGlobalDispatcher, - getLastAgent: () => ProxyAgent.lastCreated, - }; -}); - -let getProxyUrlFromFetch: typeof import("./proxy.js").getProxyUrlFromFetch; -let makeProxyFetch: typeof import("./proxy.js").makeProxyFetch; - -describe("makeProxyFetch", () => { - beforeEach(async () => { - vi.resetModules(); - mocks.undiciFetch.mockReset(); - mocks.proxyAgentSpy.mockClear(); - mocks.setGlobalDispatcher.mockClear(); - vi.doMock("undici", () => ({ - ProxyAgent: mocks.ProxyAgent, - fetch: mocks.undiciFetch, - setGlobalDispatcher: mocks.setGlobalDispatcher, - })); - ({ getProxyUrlFromFetch, makeProxyFetch } = await import("./proxy.js")); - }); - - it("uses undici fetch with ProxyAgent dispatcher", async () => { - const proxyUrl = "http://proxy.test:8080"; - mocks.undiciFetch.mockResolvedValue({ ok: true }); - - const proxyFetch = makeProxyFetch(proxyUrl); - await proxyFetch("https://api.telegram.org/bot123/getMe"); - - expect(mocks.proxyAgentSpy).toHaveBeenCalledWith(proxyUrl); - expect(mocks.undiciFetch).toHaveBeenCalledWith( - "https://api.telegram.org/bot123/getMe", - expect.objectContaining({ dispatcher: mocks.getLastAgent() }), - ); - expect(mocks.setGlobalDispatcher).not.toHaveBeenCalled(); - }); - - it("attaches proxy metadata for resolver transport handling", () => { - const proxyUrl = "http://proxy.test:8080"; - const proxyFetch = makeProxyFetch(proxyUrl); - - expect(getProxyUrlFromFetch(proxyFetch)).toBe(proxyUrl); - }); -}); diff --git a/extensions/telegram/src/status-issues.test.ts b/extensions/telegram/src/status-issues.test.ts deleted file mode 100644 index 1788d000514..00000000000 --- a/extensions/telegram/src/status-issues.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract"; -import { describe, expect, it } from "vitest"; -import { collectTelegramStatusIssues } from "./status-issues.js"; - -describe("collectTelegramStatusIssues", () => { - it("reports privacy-mode and wildcard unmentioned-group configuration risks", () => { - const issues = collectTelegramStatusIssues([ - { - accountId: "main", - enabled: true, - configured: true, - allowUnmentionedGroups: true, - audit: { - hasWildcardUnmentionedGroups: true, - unresolvedGroups: 2, - }, - } as ChannelAccountSnapshot, - ]); - - expect(issues).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - channel: "telegram", - accountId: "main", - kind: "config", - }), - ]), - ); - expect(issues.some((issue) => issue.message.includes("privacy mode"))).toBe(true); - expect(issues.some((issue) => issue.message.includes('uses "*"'))).toBe(true); - expect(issues.some((issue) => issue.message.includes("unresolvedGroups=2"))).toBe(true); - }); - - it("reports unreachable groups with match metadata", () => { - const issues = collectTelegramStatusIssues([ - { - accountId: "main", - enabled: true, - configured: true, - audit: { - groups: [ - { - chatId: "-100123", - ok: false, - status: "left", - error: "403", - matchKey: "alerts", - matchSource: "channels.telegram.groups", - }, - ], - }, - } as ChannelAccountSnapshot, - ]); - - expect(issues).toHaveLength(1); - expect(issues[0]).toMatchObject({ - channel: "telegram", - accountId: "main", - kind: "runtime", - }); - expect(issues[0]?.message).toContain("Group -100123 not reachable"); - expect(issues[0]?.message).toContain("alerts"); - expect(issues[0]?.message).toContain("channels.telegram.groups"); - }); - - it("ignores accounts that are not both enabled and configured", () => { - expect( - collectTelegramStatusIssues([ - { - accountId: "main", - enabled: false, - configured: true, - } as ChannelAccountSnapshot, - ]), - ).toEqual([]); - }); -}); diff --git a/extensions/telegram/src/status-reaction-variants.test.ts b/extensions/telegram/src/status.test.ts similarity index 71% rename from extensions/telegram/src/status-reaction-variants.test.ts rename to extensions/telegram/src/status.test.ts index 123334fcaad..38261182597 100644 --- a/extensions/telegram/src/status-reaction-variants.test.ts +++ b/extensions/telegram/src/status.test.ts @@ -1,5 +1,7 @@ +import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract"; import { describe, expect, it } from "vitest"; import { DEFAULT_EMOJIS } from "../../../src/channels/status-reactions.js"; +import { collectTelegramStatusIssues } from "./status-issues.js"; import { buildTelegramStatusReactionVariants, extractTelegramAllowedEmojiReactions, @@ -9,6 +11,80 @@ import { resolveTelegramStatusReactionEmojis, } from "./status-reaction-variants.js"; +describe("collectTelegramStatusIssues", () => { + it("reports privacy-mode and wildcard unmentioned-group configuration risks", () => { + const issues = collectTelegramStatusIssues([ + { + accountId: "main", + enabled: true, + configured: true, + allowUnmentionedGroups: true, + audit: { + hasWildcardUnmentionedGroups: true, + unresolvedGroups: 2, + }, + } as ChannelAccountSnapshot, + ]); + + expect(issues).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + channel: "telegram", + accountId: "main", + kind: "config", + }), + ]), + ); + expect(issues.some((issue) => issue.message.includes("privacy mode"))).toBe(true); + expect(issues.some((issue) => issue.message.includes('uses "*"'))).toBe(true); + expect(issues.some((issue) => issue.message.includes("unresolvedGroups=2"))).toBe(true); + }); + + it("reports unreachable groups with match metadata", () => { + const issues = collectTelegramStatusIssues([ + { + accountId: "main", + enabled: true, + configured: true, + audit: { + groups: [ + { + chatId: "-100123", + ok: false, + status: "left", + error: "403", + matchKey: "alerts", + matchSource: "channels.telegram.groups", + }, + ], + }, + } as ChannelAccountSnapshot, + ]); + + expect(issues).toHaveLength(1); + expect(issues[0]).toMatchObject({ + channel: "telegram", + accountId: "main", + kind: "runtime", + }); + expect(issues[0]?.message).toContain("Group -100123 not reachable"); + expect(issues[0]?.message).toContain("alerts"); + expect(issues[0]?.message).toContain("channels.telegram.groups"); + }); + + it("ignores accounts that are not both enabled and configured", () => { + expect( + collectTelegramStatusIssues([ + { + accountId: "main", + enabled: false, + configured: true, + } as ChannelAccountSnapshot, + ]), + ).toEqual([]); + }); +}); + describe("resolveTelegramStatusReactionEmojis", () => { it("falls back to Telegram-safe defaults for empty overrides", () => { const result = resolveTelegramStatusReactionEmojis({