Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
314 lines
10 KiB
TypeScript
314 lines
10 KiB
TypeScript
// Telegram tests cover targets plugin behavior.
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
isNumericTelegramSenderUserId,
|
|
isNumericTelegramUserId,
|
|
normalizeTelegramAllowFromEntry,
|
|
} from "./allow-from.js";
|
|
import {
|
|
resolveTelegramGroupRequireMention,
|
|
resolveTelegramGroupToolPolicy,
|
|
} from "./group-policy.js";
|
|
import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize.js";
|
|
import { installMaybePersistResolvedTelegramTargetTests } from "./target-writeback.test-shared.js";
|
|
import {
|
|
isNumericTelegramChatId,
|
|
normalizeTelegramChatId,
|
|
normalizeTelegramLookupTarget,
|
|
normalizeTelegramOutboundTarget,
|
|
parseTelegramTarget,
|
|
stripTelegramInternalPrefixes,
|
|
} from "./targets.js";
|
|
|
|
const numericTelegramTargetNormalizers = [
|
|
{ name: "normalizeTelegramChatId", normalize: normalizeTelegramChatId },
|
|
{ name: "normalizeTelegramLookupTarget", normalize: normalizeTelegramLookupTarget },
|
|
];
|
|
|
|
describe("stripTelegramInternalPrefixes", () => {
|
|
it("strips telegram prefix", () => {
|
|
expect(stripTelegramInternalPrefixes("telegram:123")).toBe("123");
|
|
});
|
|
|
|
it("strips telegram+group prefixes", () => {
|
|
expect(stripTelegramInternalPrefixes("telegram:group:-100123")).toBe("-100123");
|
|
});
|
|
|
|
it("does not strip group prefix without telegram prefix", () => {
|
|
expect(stripTelegramInternalPrefixes("group:-100123")).toBe("group:-100123");
|
|
});
|
|
|
|
it("is idempotent", () => {
|
|
expect(stripTelegramInternalPrefixes("@mychannel")).toBe("@mychannel");
|
|
});
|
|
});
|
|
|
|
describe("parseTelegramTarget", () => {
|
|
it("parses plain chatId", () => {
|
|
expect(parseTelegramTarget("-1001234567890")).toEqual({
|
|
chatId: "-1001234567890",
|
|
chatType: "group",
|
|
});
|
|
});
|
|
|
|
it("parses @username", () => {
|
|
expect(parseTelegramTarget("@mychannel")).toEqual({
|
|
chatId: "@mychannel",
|
|
chatType: "unknown",
|
|
});
|
|
});
|
|
|
|
it("parses chatId:topicId format", () => {
|
|
expect(parseTelegramTarget("-1001234567890:123")).toEqual({
|
|
chatId: "-1001234567890",
|
|
messageThreadId: 123,
|
|
chatType: "group",
|
|
});
|
|
});
|
|
|
|
it("parses chatId:topic:topicId format", () => {
|
|
expect(parseTelegramTarget("-1001234567890:topic:456")).toEqual({
|
|
chatId: "-1001234567890",
|
|
messageThreadId: 456,
|
|
chatType: "group",
|
|
});
|
|
});
|
|
|
|
it("trims whitespace", () => {
|
|
expect(parseTelegramTarget(" -1001234567890:99 ")).toEqual({
|
|
chatId: "-1001234567890",
|
|
messageThreadId: 99,
|
|
chatType: "group",
|
|
});
|
|
});
|
|
|
|
it("does not treat non-numeric suffix as topicId", () => {
|
|
expect(parseTelegramTarget("-1001234567890:abc")).toEqual({
|
|
chatId: "-1001234567890:abc",
|
|
chatType: "unknown",
|
|
});
|
|
});
|
|
|
|
it("does not route unsafe topic suffixes", () => {
|
|
expect(parseTelegramTarget("-1001234567890:9007199254740992")).toEqual({
|
|
chatId: "-1001234567890:9007199254740992",
|
|
chatType: "unknown",
|
|
});
|
|
expect(parseTelegramTarget("-1001234567890:topic:9007199254740992")).toEqual({
|
|
chatId: "-1001234567890:topic:9007199254740992",
|
|
chatType: "unknown",
|
|
});
|
|
});
|
|
|
|
it("strips internal prefixes before parsing", () => {
|
|
expect(parseTelegramTarget("telegram:group:-1001234567890:topic:456")).toEqual({
|
|
chatId: "-1001234567890",
|
|
messageThreadId: 456,
|
|
chatType: "group",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("telegram numeric target normalization", () => {
|
|
it.each(numericTelegramTargetNormalizers)(
|
|
"$name keeps numeric chat ids unchanged",
|
|
({ normalize }) => {
|
|
expect(normalize("-1001234567890")).toBe("-1001234567890");
|
|
expect(normalize("123456789")).toBe("123456789");
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("normalizeTelegramOutboundTarget", () => {
|
|
it("normalizes legacy durable group retry targets for Telegram sends", () => {
|
|
expect(normalizeTelegramOutboundTarget("group:-1001234567890")).toBe("-1001234567890");
|
|
});
|
|
|
|
it("normalizes legacy durable group retry targets with topic suffixes", () => {
|
|
expect(normalizeTelegramOutboundTarget("group:-1001234567890:topic:77")).toBe(
|
|
"-1001234567890:topic:77",
|
|
);
|
|
expect(normalizeTelegramOutboundTarget("group:-1001234567890:77")).toBe("-1001234567890:77");
|
|
});
|
|
|
|
it("keeps already-valid numeric and non-numeric targets on the send path", () => {
|
|
expect(normalizeTelegramOutboundTarget("-1001234567890")).toBe("-1001234567890");
|
|
expect(normalizeTelegramOutboundTarget("group:not-a-number")).toBe("group:not-a-number");
|
|
expect(normalizeTelegramOutboundTarget("@mychannel")).toBe("@mychannel");
|
|
});
|
|
});
|
|
|
|
describe("normalizeTelegramChatId", () => {
|
|
it("rejects username and t.me forms", () => {
|
|
expect(normalizeTelegramChatId("telegram:https://t.me/MyChannel")).toBeUndefined();
|
|
expect(normalizeTelegramChatId("tg:t.me/mychannel")).toBeUndefined();
|
|
expect(normalizeTelegramChatId("@MyChannel")).toBeUndefined();
|
|
expect(normalizeTelegramChatId("MyChannel")).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for empty input", () => {
|
|
expect(normalizeTelegramChatId(" ")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("normalizeTelegramLookupTarget", () => {
|
|
it("normalizes legacy t.me and username targets", () => {
|
|
expect(normalizeTelegramLookupTarget("telegram:https://t.me/MyChannel")).toBe("@MyChannel");
|
|
expect(normalizeTelegramLookupTarget("tg:t.me/mychannel")).toBe("@mychannel");
|
|
expect(normalizeTelegramLookupTarget("@MyChannel")).toBe("@MyChannel");
|
|
expect(normalizeTelegramLookupTarget("MyChannel")).toBe("@MyChannel");
|
|
});
|
|
|
|
it("rejects invalid username forms", () => {
|
|
expect(normalizeTelegramLookupTarget("@bad-handle")).toBeUndefined();
|
|
expect(normalizeTelegramLookupTarget("bad-handle")).toBeUndefined();
|
|
expect(normalizeTelegramLookupTarget("ab")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("isNumericTelegramChatId", () => {
|
|
it("matches numeric telegram chat ids", () => {
|
|
expect(isNumericTelegramChatId("-1001234567890")).toBe(true);
|
|
expect(isNumericTelegramChatId("123456789")).toBe(true);
|
|
});
|
|
|
|
it("rejects non-numeric chat ids", () => {
|
|
expect(isNumericTelegramChatId("@mychannel")).toBe(false);
|
|
expect(isNumericTelegramChatId("t.me/mychannel")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("telegram group policy", () => {
|
|
it("resolves topic-level requireMention and chat-level tools for topic ids", () => {
|
|
const telegramCfg = {
|
|
channels: {
|
|
telegram: {
|
|
botToken: "telegram-test",
|
|
groups: {
|
|
"-1001": {
|
|
requireMention: true,
|
|
tools: { allow: ["message.send"] },
|
|
topics: {
|
|
"77": {
|
|
requireMention: false,
|
|
},
|
|
},
|
|
},
|
|
"*": {
|
|
requireMention: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as any;
|
|
expect(
|
|
resolveTelegramGroupRequireMention({ cfg: telegramCfg, groupId: "-1001:topic:77" }),
|
|
).toBe(false);
|
|
expect(resolveTelegramGroupToolPolicy({ cfg: telegramCfg, groupId: "-1001:topic:77" })).toEqual(
|
|
{
|
|
allow: ["message.send"],
|
|
},
|
|
);
|
|
});
|
|
|
|
it("honors account-scoped topic requireMention overrides", () => {
|
|
const telegramCfg = {
|
|
channels: {
|
|
telegram: {
|
|
botToken: "telegram-test",
|
|
groups: {
|
|
"-1001": {
|
|
requireMention: true,
|
|
topics: {
|
|
"77": {
|
|
requireMention: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
accounts: {
|
|
work: {
|
|
botToken: "telegram-work",
|
|
groups: {
|
|
"-1001": {
|
|
topics: {
|
|
"77": {
|
|
requireMention: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as any;
|
|
|
|
expect(
|
|
resolveTelegramGroupRequireMention({
|
|
cfg: telegramCfg,
|
|
accountId: "work",
|
|
groupId: "-1001:topic:77",
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("telegram allow-from helpers", () => {
|
|
it("normalizes tg/telegram prefixes", () => {
|
|
const cases = [
|
|
{ value: " TG:123 ", expected: "123" },
|
|
{ value: "telegram:@someone", expected: "@someone" },
|
|
] as const;
|
|
for (const testCase of cases) {
|
|
expect(normalizeTelegramAllowFromEntry(testCase.value)).toBe(testCase.expected);
|
|
}
|
|
});
|
|
|
|
it("accepts signed numeric IDs", () => {
|
|
const cases = [
|
|
{ value: "123456789", expected: true },
|
|
{ value: "-1001234567890", expected: true },
|
|
{ value: "@someone", expected: false },
|
|
{ value: "12 34", expected: false },
|
|
] as const;
|
|
for (const testCase of cases) {
|
|
expect(isNumericTelegramUserId(testCase.value)).toBe(testCase.expected);
|
|
}
|
|
});
|
|
|
|
it("accepts only positive numeric sender user IDs", () => {
|
|
const cases = [
|
|
{ value: "123456789", expected: true },
|
|
{ value: "-1001234567890", expected: false },
|
|
{ value: "@someone", expected: false },
|
|
] as const;
|
|
for (const testCase of cases) {
|
|
expect(isNumericTelegramSenderUserId(testCase.value)).toBe(testCase.expected);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("telegram target normalization", () => {
|
|
it("normalizes telegram prefixes, group targets, and topic suffixes", () => {
|
|
expect(normalizeTelegramMessagingTarget("telegram:123456")).toBe("telegram:123456");
|
|
expect(normalizeTelegramMessagingTarget("tg:group:-100123")).toBe("telegram:group:-100123");
|
|
expect(normalizeTelegramMessagingTarget("telegram:-100123:topic:99")).toBe(
|
|
"telegram:-100123:topic:99",
|
|
);
|
|
});
|
|
|
|
it("returns undefined for invalid telegram recipients", () => {
|
|
expect(normalizeTelegramMessagingTarget("telegram:")).toBeUndefined();
|
|
expect(normalizeTelegramMessagingTarget(" ")).toBeUndefined();
|
|
});
|
|
|
|
it("detects valid telegram target identifiers", () => {
|
|
expect(looksLikeTelegramTargetId("telegram:123456")).toBe(true);
|
|
expect(looksLikeTelegramTargetId("tg:group:-100123")).toBe(true);
|
|
expect(looksLikeTelegramTargetId("hello world")).toBe(false);
|
|
});
|
|
});
|
|
|
|
installMaybePersistResolvedTelegramTargetTests({ includeGatewayScopeCases: true });
|