Files
3x-ui-agent/internal/web/service/tgbot/numeric_input_test.go
T
Muse ceca96571d P9(5): restore upstream Telegram bot (settings, backup, notifications)
Per user decision 2026-10-21 (reverses the 2026-10-20 drop-Telegram
call): port the upstream 3x-ui v3.7.0-generation Telegram bot wholesale
instead of rewriting a minimal set, adapted to this fork.

- new internal/web/service/tgbot package (telego): bot lifecycle,
  commands, reports, callback router, send helpers; token validation via
  telego; custom API server URL passes the fork's SSRF sanitizer
  (loopback/private bases fall back to the default endpoint, logged)
- settings: full upstream key set (tgBotEnable/Token/Proxy/APIServer/
  ChatId, tgRunTime, tgBotBackup, tgCpu/tgMemory, tgLang,
  tgEnabledEvents) with getters/setters; settings page gains a Telegram
  tab + sidebar entry; token shown masked, clearTgBotToken honored
- wiring (web.go): stats-notify cron registered from tgRunTime when the
  bot is enabled; CheckHashStorage job; CPU/memory alarm jobs gated on
  tgEnabledEvents + thresholds; login notifications via event bus;
  POST /panel/api/setting/testTgBot and /panel/api/backuptotgbot
- lite adaptation: StatsNotifyJob no longer gates on a local Xray core
  (this panel never runs one) so the periodic report and the scheduled
  DB backup (sendDocument) can actually fire
- translations: upstream TG keys restored across all locales

End-to-end smoke (real binary + fake Telegram API over HTTPS at a
public-looking address, browser form wire format): bot starts on
settings change (getMe), testTgBot delivers, panel restart re-arms the
bot, tgRunTime='@every 1m' cron really fires sendDocument carrying the
panel SQLite DB (magic verified) plus the report sendMessage, login
notification delivered, dead-endpoint failure is logged not silent.
13/13 PASS — see P9-REPORT.md.
2026-10-21 17:16:32 +00:00

82 lines
2.2 KiB
Go

package tgbot
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"testing"
)
func TestUpdateNumericInput(t *testing.T) {
tests := []struct {
name string
value, key int
want int
}{
{name: "append digit", value: 12, key: 3, want: 123},
{name: "append zero", value: 12, key: 0, want: 120},
{name: "backspace", value: 123, key: -1, want: 12},
{name: "backspace zero", value: 0, key: -1, want: 0},
{name: "clear", value: 123, key: -2, want: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := updateNumericInput(tt.value, tt.key); got != tt.want {
t.Fatalf("updateNumericInput(%d, %d) = %d, want %d", tt.value, tt.key, got, tt.want)
}
})
}
}
func TestNumericInputTransitionIsUsedByEveryKeypad(t *testing.T) {
entries, err := os.ReadDir(".")
if err != nil {
t.Fatalf("read tgbot package: %v", err)
}
fset := token.NewFileSet()
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() || filepath.Ext(name) != ".go" || name == "numeric_input.go" || filepath.Ext(strings.TrimSuffix(name, "_test.go")) != ".go" {
continue
}
parsed, err := parser.ParseFile(fset, name, nil, 0)
if err != nil {
t.Fatalf("parse %s: %v", name, err)
}
ast.Inspect(parsed, func(node ast.Node) bool {
switchStmt, ok := node.(*ast.SwitchStmt)
if !ok {
return true
}
hasClear, hasBackspace, hasDefault := false, false, false
for _, stmt := range switchStmt.Body.List {
clause := stmt.(*ast.CaseClause)
if clause.List == nil {
hasDefault = true
}
for _, expr := range clause.List {
hasClear = hasClear || numericKeyLiteral(expr, "2")
hasBackspace = hasBackspace || numericKeyLiteral(expr, "1")
}
}
if hasClear && hasBackspace && hasDefault {
position := fset.Position(switchStmt.Pos())
t.Errorf("open-coded numeric keypad transition at %s; use updateNumericInput", position)
}
return true
})
}
}
func numericKeyLiteral(expr ast.Expr, magnitude string) bool {
unary, ok := expr.(*ast.UnaryExpr)
if !ok || unary.Op != token.SUB {
return false
}
literal, ok := unary.X.(*ast.BasicLit)
return ok && literal.Kind == token.INT && literal.Value == magnitude
}