mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:21:35 +07:00
test: collapse telegram button and access suites
This commit is contained in:
@@ -1,69 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTelegramInteractiveButtons, resolveTelegramInlineButtons } from "./button-types.js";
|
||||
|
||||
describe("buildTelegramInteractiveButtons", () => {
|
||||
it("maps shared buttons and selects into Telegram inline rows", () => {
|
||||
expect(
|
||||
buildTelegramInteractiveButtons({
|
||||
blocks: [
|
||||
{
|
||||
type: "buttons",
|
||||
buttons: [
|
||||
{ label: "Approve", value: "approve", style: "success" },
|
||||
{ label: "Reject", value: "reject", style: "danger" },
|
||||
{ label: "Later", value: "later" },
|
||||
{ label: "Archive", value: "archive" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
options: [{ label: "Alpha", value: "alpha" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
[
|
||||
{ text: "Approve", callback_data: "approve", style: "success" },
|
||||
{ text: "Reject", callback_data: "reject", style: "danger" },
|
||||
{ text: "Later", callback_data: "later", style: undefined },
|
||||
],
|
||||
[{ text: "Archive", callback_data: "archive", style: undefined }],
|
||||
[{ text: "Alpha", callback_data: "alpha", style: undefined }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramInlineButtons", () => {
|
||||
it("prefers explicit buttons over shared interactive blocks", () => {
|
||||
const explicit = [[{ text: "Keep", callback_data: "keep" }]] as const;
|
||||
|
||||
expect(
|
||||
resolveTelegramInlineButtons({
|
||||
buttons: explicit,
|
||||
interactive: {
|
||||
blocks: [
|
||||
{
|
||||
type: "buttons",
|
||||
buttons: [{ label: "Override", value: "override" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
).toBe(explicit);
|
||||
});
|
||||
|
||||
it("derives buttons from raw interactive payloads", () => {
|
||||
expect(
|
||||
resolveTelegramInlineButtons({
|
||||
interactive: {
|
||||
blocks: [
|
||||
{
|
||||
type: "buttons",
|
||||
buttons: [{ label: "Retry", value: "retry", style: "primary" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
).toEqual([[{ text: "Retry", callback_data: "retry", style: "primary" }]]);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { TelegramAccountConfig } from "../../../src/config/types.js";
|
||||
import { normalizeAllowFrom, type NormalizedAllowFrom } from "./bot-access.js";
|
||||
import { evaluateTelegramGroupBaseAccess } from "./group-access.js";
|
||||
import {
|
||||
evaluateTelegramGroupBaseAccess,
|
||||
evaluateTelegramGroupPolicyAccess,
|
||||
} from "./group-access.js";
|
||||
|
||||
function allow(entries: string[], hasWildcard = false): NormalizedAllowFrom {
|
||||
return {
|
||||
@@ -65,3 +70,210 @@ describe("evaluateTelegramGroupBaseAccess", () => {
|
||||
expect(result).toEqual({ allowed: true });
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Minimal stubs shared across group policy tests.
|
||||
*/
|
||||
const baseCfg = {
|
||||
channels: { telegram: {} },
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const baseTelegramCfg: TelegramAccountConfig = {
|
||||
groupPolicy: "allowlist",
|
||||
} as unknown as TelegramAccountConfig;
|
||||
|
||||
const emptyAllow = { entries: [], hasWildcard: false, hasEntries: false, invalidEntries: [] };
|
||||
const senderAllow = {
|
||||
entries: ["111"],
|
||||
hasWildcard: false,
|
||||
hasEntries: true,
|
||||
invalidEntries: [],
|
||||
};
|
||||
|
||||
type GroupAccessParams = Parameters<typeof evaluateTelegramGroupPolicyAccess>[0];
|
||||
|
||||
const DEFAULT_GROUP_ACCESS_PARAMS: GroupAccessParams = {
|
||||
isGroup: true,
|
||||
chatId: "-100123456",
|
||||
cfg: baseCfg,
|
||||
telegramCfg: baseTelegramCfg,
|
||||
effectiveGroupAllow: emptyAllow,
|
||||
senderId: "999",
|
||||
senderUsername: "user",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
enforcePolicy: true,
|
||||
useTopicAndGroupOverrides: false,
|
||||
enforceAllowlistAuthorization: true,
|
||||
allowEmptyAllowlistEntries: false,
|
||||
requireSenderForAllowlistAuthorization: true,
|
||||
checkChatAllowlist: true,
|
||||
};
|
||||
|
||||
function runAccess(overrides: Partial<GroupAccessParams>) {
|
||||
return evaluateTelegramGroupPolicyAccess({
|
||||
...DEFAULT_GROUP_ACCESS_PARAMS,
|
||||
...overrides,
|
||||
resolveGroupPolicy:
|
||||
overrides.resolveGroupPolicy ?? DEFAULT_GROUP_ACCESS_PARAMS.resolveGroupPolicy,
|
||||
});
|
||||
}
|
||||
|
||||
describe("evaluateTelegramGroupPolicyAccess", () => {
|
||||
it("allows a group explicitly listed in groups config even when no allowFrom entries exist", () => {
|
||||
const result = runAccess({
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "allowlist" });
|
||||
});
|
||||
|
||||
it("still blocks when only wildcard match and no allowFrom entries", () => {
|
||||
const result = runAccess({
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-empty",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a group not in groups config", () => {
|
||||
const result = runAccess({
|
||||
chatId: "-100999999",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-chat-not-allowed",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("still enforces sender allowlist when checkChatAllowlist is disabled", () => {
|
||||
const result = runAccess({
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
checkChatAllowlist: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-empty",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks unauthorized sender even when chat is explicitly allowed and sender entries exist", () => {
|
||||
const result = runAccess({
|
||||
effectiveGroupAllow: senderAllow,
|
||||
senderId: "222",
|
||||
senderUsername: "other",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-unauthorized",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows when groupPolicy is open regardless of allowlist state", () => {
|
||||
const result = runAccess({
|
||||
telegramCfg: { groupPolicy: "open" } as TelegramAccountConfig,
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: false,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "open" });
|
||||
});
|
||||
|
||||
it("rejects when groupPolicy is disabled", () => {
|
||||
const result = runAccess({
|
||||
telegramCfg: { groupPolicy: "disabled" } as TelegramAccountConfig,
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: false,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-disabled",
|
||||
groupPolicy: "disabled",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows non-group messages without any checks", () => {
|
||||
const result = runAccess({
|
||||
isGroup: false,
|
||||
chatId: "12345",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "allowlist" });
|
||||
});
|
||||
|
||||
it("blocks allowlist groups without sender identity before sender matching", () => {
|
||||
const result = runAccess({
|
||||
senderId: undefined,
|
||||
senderUsername: undefined,
|
||||
effectiveGroupAllow: senderAllow,
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-no-sender",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows authorized sender in wildcard-matched group with sender entries", () => {
|
||||
const result = runAccess({
|
||||
effectiveGroupAllow: senderAllow,
|
||||
senderId: "111",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "allowlist" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { TelegramAccountConfig } from "../../../src/config/types.js";
|
||||
import { evaluateTelegramGroupPolicyAccess } from "./group-access.js";
|
||||
|
||||
/**
|
||||
* Minimal stubs shared across tests.
|
||||
*/
|
||||
const baseCfg = {
|
||||
channels: { telegram: {} },
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const baseTelegramCfg: TelegramAccountConfig = {
|
||||
groupPolicy: "allowlist",
|
||||
} as unknown as TelegramAccountConfig;
|
||||
|
||||
const emptyAllow = { entries: [], hasWildcard: false, hasEntries: false, invalidEntries: [] };
|
||||
const senderAllow = {
|
||||
entries: ["111"],
|
||||
hasWildcard: false,
|
||||
hasEntries: true,
|
||||
invalidEntries: [],
|
||||
};
|
||||
|
||||
type GroupAccessParams = Parameters<typeof evaluateTelegramGroupPolicyAccess>[0];
|
||||
|
||||
const DEFAULT_GROUP_ACCESS_PARAMS: GroupAccessParams = {
|
||||
isGroup: true,
|
||||
chatId: "-100123456",
|
||||
cfg: baseCfg,
|
||||
telegramCfg: baseTelegramCfg,
|
||||
effectiveGroupAllow: emptyAllow,
|
||||
senderId: "999",
|
||||
senderUsername: "user",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
enforcePolicy: true,
|
||||
useTopicAndGroupOverrides: false,
|
||||
enforceAllowlistAuthorization: true,
|
||||
allowEmptyAllowlistEntries: false,
|
||||
requireSenderForAllowlistAuthorization: true,
|
||||
checkChatAllowlist: true,
|
||||
};
|
||||
|
||||
function runAccess(overrides: Partial<GroupAccessParams>) {
|
||||
return evaluateTelegramGroupPolicyAccess({
|
||||
...DEFAULT_GROUP_ACCESS_PARAMS,
|
||||
...overrides,
|
||||
resolveGroupPolicy:
|
||||
overrides.resolveGroupPolicy ?? DEFAULT_GROUP_ACCESS_PARAMS.resolveGroupPolicy,
|
||||
});
|
||||
}
|
||||
|
||||
describe("evaluateTelegramGroupPolicyAccess – chat allowlist vs sender allowlist ordering", () => {
|
||||
it("allows a group explicitly listed in groups config even when no allowFrom entries exist", () => {
|
||||
// Issue #30613: a group configured with a dedicated entry (groupConfig set)
|
||||
// should be allowed even without any allowFrom / groupAllowFrom entries.
|
||||
const result = runAccess({
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false }, // dedicated entry — not just wildcard
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "allowlist" });
|
||||
});
|
||||
|
||||
it("still blocks when only wildcard match and no allowFrom entries", () => {
|
||||
// groups: { "*": ... } with no allowFrom → wildcard does NOT bypass sender checks.
|
||||
const result = runAccess({
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: undefined, // wildcard match only — no dedicated entry
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-empty",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a group NOT in groups config", () => {
|
||||
const result = runAccess({
|
||||
chatId: "-100999999",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-chat-not-allowed",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("still enforces sender allowlist when checkChatAllowlist is disabled", () => {
|
||||
const result = runAccess({
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
checkChatAllowlist: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-empty",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks unauthorized sender even when chat is explicitly allowed and sender entries exist", () => {
|
||||
const result = runAccess({
|
||||
effectiveGroupAllow: senderAllow, // entries: ["111"]
|
||||
senderId: "222", // not in senderAllow.entries
|
||||
senderUsername: "other",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
});
|
||||
|
||||
// Chat is explicitly allowed, but sender entries exist and sender is not in them.
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-unauthorized",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows when groupPolicy is open regardless of allowlist state", () => {
|
||||
const result = runAccess({
|
||||
telegramCfg: { groupPolicy: "open" } as unknown as TelegramAccountConfig,
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: false,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "open" });
|
||||
});
|
||||
|
||||
it("rejects when groupPolicy is disabled", () => {
|
||||
const result = runAccess({
|
||||
telegramCfg: { groupPolicy: "disabled" } as unknown as TelegramAccountConfig,
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: false,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-disabled",
|
||||
groupPolicy: "disabled",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows non-group messages without any checks", () => {
|
||||
const result = runAccess({
|
||||
isGroup: false,
|
||||
chatId: "12345",
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: false,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "allowlist" });
|
||||
});
|
||||
|
||||
it("blocks allowlist groups without sender identity before sender matching", () => {
|
||||
const result = runAccess({
|
||||
senderId: undefined,
|
||||
senderUsername: undefined,
|
||||
effectiveGroupAllow: senderAllow,
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: { requireMention: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
allowed: false,
|
||||
reason: "group-policy-allowlist-no-sender",
|
||||
groupPolicy: "allowlist",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows authorized sender in wildcard-matched group with sender entries", () => {
|
||||
const result = runAccess({
|
||||
effectiveGroupAllow: senderAllow, // entries: ["111"]
|
||||
senderId: "111", // IS in senderAllow.entries
|
||||
resolveGroupPolicy: () => ({
|
||||
allowlistEnabled: true,
|
||||
allowed: true,
|
||||
groupConfig: undefined, // wildcard only
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ allowed: true, groupPolicy: "allowlist" });
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTelegramExecApprovalButtons } from "./approval-buttons.js";
|
||||
import { buildTelegramInteractiveButtons, resolveTelegramInlineButtons } from "./button-types.js";
|
||||
import { resolveTelegramTargetChatType } from "./inline-buttons.js";
|
||||
|
||||
describe("telegram approval buttons", () => {
|
||||
@@ -52,3 +53,70 @@ describe("resolveTelegramTargetChatType", () => {
|
||||
expect(resolveTelegramTargetChatType(" ")).toBe("unknown");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildTelegramInteractiveButtons", () => {
|
||||
it("maps shared buttons and selects into Telegram inline rows", () => {
|
||||
expect(
|
||||
buildTelegramInteractiveButtons({
|
||||
blocks: [
|
||||
{
|
||||
type: "buttons",
|
||||
buttons: [
|
||||
{ label: "Approve", value: "approve", style: "success" },
|
||||
{ label: "Reject", value: "reject", style: "danger" },
|
||||
{ label: "Later", value: "later" },
|
||||
{ label: "Archive", value: "archive" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
options: [{ label: "Alpha", value: "alpha" }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
[
|
||||
{ text: "Approve", callback_data: "approve", style: "success" },
|
||||
{ text: "Reject", callback_data: "reject", style: "danger" },
|
||||
{ text: "Later", callback_data: "later", style: undefined },
|
||||
],
|
||||
[{ text: "Archive", callback_data: "archive", style: undefined }],
|
||||
[{ text: "Alpha", callback_data: "alpha", style: undefined }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramInlineButtons", () => {
|
||||
it("prefers explicit buttons over shared interactive blocks", () => {
|
||||
const explicit = [[{ text: "Keep", callback_data: "keep" }]] as const;
|
||||
|
||||
expect(
|
||||
resolveTelegramInlineButtons({
|
||||
buttons: explicit,
|
||||
interactive: {
|
||||
blocks: [
|
||||
{
|
||||
type: "buttons",
|
||||
buttons: [{ label: "Override", value: "override" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
).toBe(explicit);
|
||||
});
|
||||
|
||||
it("derives buttons from raw interactive payloads", () => {
|
||||
expect(
|
||||
resolveTelegramInlineButtons({
|
||||
interactive: {
|
||||
blocks: [
|
||||
{
|
||||
type: "buttons",
|
||||
buttons: [{ label: "Retry", value: "retry", style: "primary" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
).toEqual([[{ text: "Retry", callback_data: "retry", style: "primary" }]]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user