test: collapse telegram button and access suites

This commit is contained in:
Peter Steinberger
2026-03-25 11:21:43 +00:00
parent b7f2b0d7b9
commit 12082f47bd
4 changed files with 281 additions and 285 deletions

View File

@@ -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" }]]);
});
});

View File

@@ -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" });
});
});

View File

@@ -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" });
});
});

View File

@@ -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" }]]);
});
});