mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 12:36:37 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
@@ -2,14 +2,11 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/config"
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
@@ -18,13 +15,7 @@ import (
|
||||
|
||||
func newBotTestService(t *testing.T) (*repository.Container, *TelegramBotService) {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(model.AllModels()...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db := newServiceTestDB(t, model.AllModels()...)
|
||||
repos := repository.New(db)
|
||||
cfg := &config.Config{}
|
||||
cfg.Secrets.JWTSecret = "test-secret"
|
||||
@@ -503,242 +494,6 @@ func TestBotAdminCommandsManageDevicePolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotCleanupRulesDefaultToEmpty(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, _ := newBotTestService(t)
|
||||
|
||||
cfg := loadBotConfig(ctx, repos)
|
||||
if len(cfg.AccountCleanupRules) != 0 {
|
||||
t.Fatalf("default cleanup rules should be empty, got %+v", cfg.AccountCleanupRules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotCleanupRulesCanBeDeletedUntilEmpty(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
admin := &model.User{Username: "root", PasswordHash: "x", Role: "admin", IsActive: true}
|
||||
if err := repos.User.Create(ctx, admin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9001"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9001, Username: "root"}, Chat: TelegramChat{ID: 9001, Type: "private"}}
|
||||
|
||||
if _, err := bot.executeCommand(ctx, channel, msg, "/cleanup_rule add watch_hours watch_3_5d_6h 观看3到5天满6小时 3 5 6"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/cleanup_rule del watch_3_5d_6h")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := loadBotConfig(ctx, repos)
|
||||
if len(cfg.AccountCleanupRules) != 0 {
|
||||
t.Fatalf("cleanup rules should stay empty after deleting the last rule; reply=%q rules=%+v", reply.Text, cfg.AccountCleanupRules)
|
||||
}
|
||||
|
||||
reply, err = bot.executeCommand(ctx, channel, msg, "/cleanup_rule list")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "暂无规则") {
|
||||
t.Fatalf("expected empty rule list, got %q", reply.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotCleanupRunPreviewsBeforeConfirm(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
admin := &model.User{Username: "root", PasswordHash: "x", Role: "admin", IsActive: true}
|
||||
if err := repos.User.Create(ctx, admin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
old := now.Add(-30 * 24 * time.Hour)
|
||||
stale := &model.User{Username: "stale", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
stale.CreatedAt = old
|
||||
stale.LastLoginAt = &old
|
||||
recent := &model.User{Username: "recent", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
recent.CreatedAt = old
|
||||
recent.LastLoginAt = &now
|
||||
newUser := &model.User{Username: "newbie", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
newUser.CreatedAt = now
|
||||
for _, user := range []*model.User{stale, recent, newUser} {
|
||||
if err := repos.User.Create(ctx, user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupEnabled, "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupKeepMode, "any"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupRules, `[
|
||||
{"id":"login_7d","name":"最近登录","type":"recent_login","enabled":true,"window_days_max":7},
|
||||
{"id":"new_7d","name":"新号宽限","type":"account_age_grace","enabled":true,"min_count":7}
|
||||
]`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9001"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9001, Username: "root"}, Chat: TelegramChat{ID: 9001, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/cleanup run")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "当前只是预览") || !strings.Contains(reply.Text, "stale") || !strings.Contains(reply.Text, "/cleanup run confirm") {
|
||||
t.Fatalf("cleanup run should preview candidates and confirmation command, got %q", reply.Text)
|
||||
}
|
||||
if got, _ := repos.User.FindByID(ctx, stale.ID); got == nil {
|
||||
t.Fatal("cleanup preview must not delete the stale user")
|
||||
}
|
||||
|
||||
reply, err = bot.executeCommand(ctx, channel, msg, "/deleted")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "当前只是预览") {
|
||||
t.Fatalf("/deleted alias should preview only, got %q", reply.Text)
|
||||
}
|
||||
if got, _ := repos.User.FindByID(ctx, stale.ID); got == nil {
|
||||
t.Fatal("/deleted preview alias must not delete users")
|
||||
}
|
||||
|
||||
reply, err = bot.executeCommand(ctx, channel, msg, "/cleanup run confirm")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "已清理 <b>1</b>") {
|
||||
t.Fatalf("cleanup confirm should delete exactly one stale user, got %q", reply.Text)
|
||||
}
|
||||
if got, _ := repos.User.FindByID(ctx, stale.ID); got != nil {
|
||||
t.Fatal("stale user should be deleted after explicit confirmation")
|
||||
}
|
||||
for _, user := range []*model.User{recent, newUser, admin} {
|
||||
if got, _ := repos.User.FindByID(ctx, user.ID); got == nil {
|
||||
t.Fatalf("%s should be kept by保号 rules/protection", user.Username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotCleanupLegacyCountModeStillKeepsSingleMatchedRule(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
admin := &model.User{Username: "root", PasswordHash: "x", Role: "admin", IsActive: true}
|
||||
if err := repos.User.Create(ctx, admin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
old := now.Add(-30 * 24 * time.Hour)
|
||||
recent := &model.User{Username: "recent", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
recent.CreatedAt = old
|
||||
recent.LastLoginAt = &now
|
||||
stale := &model.User{Username: "stale", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
stale.CreatedAt = old
|
||||
stale.LastLoginAt = &old
|
||||
for _, user := range []*model.User{recent, stale} {
|
||||
if err := repos.User.Create(ctx, user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupEnabled, "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupKeepMode, "count"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupRequiredCount, "2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupRules, `[
|
||||
{"id":"login_7d","name":"最近登录","type":"recent_login","enabled":true,"window_days_max":7},
|
||||
{"id":"new_7d","name":"新号宽限","type":"account_age_grace","enabled":true,"min_count":7}
|
||||
]`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9001"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9001, Username: "root"}, Chat: TelegramChat{ID: 9001, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/cleanup run")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(reply.Text, "recent") {
|
||||
t.Fatalf("user matching one keep rule must not be a cleanup candidate, got %q", reply.Text)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "stale") {
|
||||
t.Fatalf("user matching no keep rules should be a candidate, got %q", reply.Text)
|
||||
}
|
||||
|
||||
reply, err = bot.executeCommand(ctx, channel, msg, "/cleanup run confirm")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, _ := repos.User.FindByID(ctx, recent.ID); got == nil {
|
||||
t.Fatal("legacy count mode must not delete a user matching one keep rule")
|
||||
}
|
||||
if got, _ := repos.User.FindByID(ctx, stale.ID); got != nil {
|
||||
t.Fatalf("stale user should be deleted after confirm, reply=%q", reply.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotCleanupConfirmRequiresEnabledRules(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
user := &model.User{Username: "viewer", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
user.CreatedAt = time.Now().Add(-30 * 24 * time.Hour)
|
||||
if err := repos.User.Create(ctx, user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupEnabled, "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9001"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9001, Username: "root"}, Chat: TelegramChat{ID: 9001, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/cleanup run confirm")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "没有启用的保号规则") {
|
||||
t.Fatalf("cleanup confirm without rules should be blocked, got %q", reply.Text)
|
||||
}
|
||||
if got, _ := repos.User.FindByID(ctx, user.ID); got == nil {
|
||||
t.Fatal("cleanup confirm without enabled rules must not delete users")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotCleanupRuleListInfersDaysAndHidesDuplicateNames(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
admin := &model.User{Username: "root", PasswordHash: "x", Role: "admin", IsActive: true}
|
||||
if err := repos.User.Create(ctx, admin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Setting.Set(ctx, SettingAccountCleanupRules, `[
|
||||
{"id":"login_7d","name":"login_7d","type":"recent_login","enabled":true,"window_days_min":1,"window_days_max":5,"min_count":1},
|
||||
{"id":"new_7d","name":"new_7d","type":"account_age_grace","enabled":true,"window_days_min":1,"window_days_max":1,"min_count":1}
|
||||
]`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9001"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9001, Username: "root"}, Chat: TelegramChat{ID: 9001, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/cleanup_rule")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, bad := range []string{"login_7d</code> · login_7d", "new_7d</code> · new_7d", "5 天内登录", "新号宽限 1 天", "add watch_hours", "Mgo 保号规则命令"} {
|
||||
if strings.Contains(reply.Text, bad) {
|
||||
t.Fatalf("rule list still contains bad fragment %q: %s", bad, reply.Text)
|
||||
}
|
||||
}
|
||||
for _, want := range []string{"login_7d", "7 天内登录", "new_7d", "新号宽限 7 天"} {
|
||||
if !strings.Contains(reply.Text, want) {
|
||||
t.Fatalf("rule list missing %q: %s", want, reply.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotRegistrationCommandUsesOpenRegQuota(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
@@ -828,137 +583,6 @@ func TestBotUserCommandsAndAdminGate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotRedeemRegisterRequiresAllowedTelegramUser(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, bot := newBotTestService(t)
|
||||
code, err := bot.generateCode(ctx, model.RegistrationCodeRegister, 30, 0, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9001"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9201, Username: "outsider"}, Chat: TelegramChat{ID: 9201, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/redeem_register "+code.Code)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "不在管理员配置") {
|
||||
t.Fatalf("outsider should not redeem register code, got %q", reply.Text)
|
||||
}
|
||||
|
||||
channel.Config = `{"admin_user_ids":"9201"}`
|
||||
reply, err = bot.executeCommand(ctx, channel, msg, "/redeem_register "+code.Code)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "兑换成功") {
|
||||
t.Fatalf("allowed user should redeem register code, got %q", reply.Text)
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9201); binding == nil {
|
||||
t.Fatal("redeemed account should be bound to telegram user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotRedeemRegisterCodeCreatesOnlyOneAccount(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
code, err := bot.generateCode(ctx, model.RegistrationCodeRegister, 30, 0, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9201,9202"}`}
|
||||
|
||||
first := &TelegramMessage{From: TelegramUser{ID: 9201, Username: "first"}, Chat: TelegramChat{ID: 9201, Type: "private"}}
|
||||
reply, err := bot.executeCommand(ctx, channel, first, "/redeem_register "+code.Code)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "兑换成功") {
|
||||
t.Fatalf("first redeem should succeed, got %q", reply.Text)
|
||||
}
|
||||
|
||||
second := &TelegramMessage{From: TelegramUser{ID: 9202, Username: "second"}, Chat: TelegramChat{ID: 9202, Type: "private"}}
|
||||
reply, err = bot.executeCommand(ctx, channel, second, "/redeem_register "+code.Code)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "兑换码已被使用") && !strings.Contains(reply.Text, "兑换码刚刚被使用") {
|
||||
t.Fatalf("second redeem should be rejected as used, got %q", reply.Text)
|
||||
}
|
||||
var users int64
|
||||
if err := repos.DB.Model(&model.User{}).Count(&users).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if users != 1 {
|
||||
t.Fatalf("one register code must create exactly one user, got %d", users)
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9202); binding != nil {
|
||||
t.Fatal("second telegram user must not be bound by an already-used register code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotRegisterCommandAcceptsRegistrationCode(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, bot := newBotTestService(t)
|
||||
code, err := bot.generateCode(ctx, model.RegistrationCodeRegister, 30, 0, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9301"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9301, Username: "codeuser"}, Chat: TelegramChat{ID: 9301, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/register "+strings.ToLower(code.Code[:4])+"-"+strings.ToLower(code.Code[4:]))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "兑换成功") {
|
||||
t.Fatalf("/register CODE should redeem registration code, got %q", reply.Text)
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9301); binding == nil {
|
||||
t.Fatal("register code should bind the newly created account")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotPlainRegistrationCodeMessageRedeems(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
code, err := bot.generateCode(ctx, model.RegistrationCodeRegister, 30, 0, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.DB.Create(&model.NotifyChannel{
|
||||
Name: "Telegram",
|
||||
Type: "telegram",
|
||||
Enabled: true,
|
||||
Config: `{"admin_user_ids":"9302"}`,
|
||||
}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
update, _ := json.Marshal(TelegramUpdate{
|
||||
UpdateID: 1,
|
||||
Message: &TelegramMessage{
|
||||
MessageID: 12,
|
||||
Text: strings.ToLower(code.Code),
|
||||
From: TelegramUser{ID: 9302, Username: "plaincode"},
|
||||
Chat: TelegramChat{ID: 9302, Type: "private"},
|
||||
},
|
||||
})
|
||||
|
||||
if err := bot.HandleWebhook(ctx, update); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9302); binding == nil {
|
||||
t.Fatal("plain code private message should redeem and bind account")
|
||||
}
|
||||
var used model.RegistrationCode
|
||||
if err := repos.DB.Where("code = ?", code.Code).First(&used).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if used.UsedAt == nil || used.UsedByUserID == "" {
|
||||
t.Fatal("plain code message should mark registration code as used")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotAdminCodeAndUserCommands(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
@@ -1032,122 +656,3 @@ func TestBotGroupMenuShowsAdminActionsOnlyForAdmins(t *testing.T) {
|
||||
t.Fatalf("non-admin group menu must not expose management actions, got %#v", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotAdminUnbindMultipleUsers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
admin := &model.User{Username: "root", PasswordHash: "x", Role: "admin", IsActive: true}
|
||||
viewer := &model.User{Username: "viewer", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
guest := &model.User{Username: "guest", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
for _, user := range []*model.User{admin, viewer, guest} {
|
||||
if err := repos.User.Create(ctx, user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
bindings := []model.TelegramBinding{
|
||||
{TelegramUserID: 9401, TelegramName: "@root", ChatID: 9401, UserID: admin.ID},
|
||||
{TelegramUserID: 9402, TelegramName: "@viewer", ChatID: 9402, UserID: viewer.ID},
|
||||
{TelegramUserID: 9403, TelegramName: "@guest", ChatID: 9403, UserID: guest.ID},
|
||||
}
|
||||
for i := range bindings {
|
||||
if err := repos.DB.Create(&bindings[i]).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9401"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9401, Username: "root"}, Chat: TelegramChat{ID: 9401, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/unbind viewer,guest missing root")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "已解绑:<b>2</b>") || !strings.Contains(reply.Text, "root(管理员)") || !strings.Contains(reply.Text, "missing") {
|
||||
t.Fatalf("unexpected unbind reply: %q", reply.Text)
|
||||
}
|
||||
for _, user := range []*model.User{viewer, guest} {
|
||||
var count int64
|
||||
if err := repos.DB.Model(&model.TelegramBinding{}).Where("user_id = ?", user.ID).Count(&count).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("%s binding count = %d, want 0", user.Username, count)
|
||||
}
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9401); binding == nil {
|
||||
t.Fatal("admin binding should be protected from /unbind by username")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotAdminUnbindInactiveAndInvalidBindings(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repos, bot := newBotTestService(t)
|
||||
oldTime := time.Now().Add(-45 * 24 * time.Hour)
|
||||
recentTime := time.Now().Add(-2 * 24 * time.Hour)
|
||||
admin := &model.User{Username: "root", PasswordHash: "x", Role: "admin", IsActive: true, LastLoginAt: &oldTime}
|
||||
oldUser := &model.User{Username: "old", PasswordHash: "x", Role: "user", IsActive: true, LastLoginAt: &oldTime}
|
||||
recentUser := &model.User{Username: "recent", PasswordHash: "x", Role: "user", IsActive: true, LastLoginAt: &recentTime}
|
||||
for _, user := range []*model.User{admin, oldUser, recentUser} {
|
||||
if err := repos.User.Create(ctx, user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, binding := range []model.TelegramBinding{
|
||||
{TelegramUserID: 9501, TelegramName: "@root", ChatID: 9501, UserID: admin.ID},
|
||||
{TelegramUserID: 9502, TelegramName: "@old", ChatID: 9502, UserID: oldUser.ID},
|
||||
{TelegramUserID: 9503, TelegramName: "@recent", ChatID: 9503, UserID: recentUser.ID},
|
||||
{TelegramUserID: 9504, TelegramName: "@ghost", ChatID: 9504, UserID: "missing-user"},
|
||||
} {
|
||||
row := binding
|
||||
if err := repos.DB.Create(&row).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"admin_user_ids":"9501"}`}
|
||||
msg := &TelegramMessage{From: TelegramUser{ID: 9501, Username: "root"}, Chat: TelegramChat{ID: 9501, Type: "private"}}
|
||||
|
||||
reply, err := bot.executeCommand(ctx, channel, msg, "/unbind_inactive 30")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "已解绑:<b>1</b>") || !strings.Contains(reply.Text, "old") {
|
||||
t.Fatalf("unexpected inactive unbind reply: %q", reply.Text)
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9502); binding != nil {
|
||||
t.Fatal("old user binding should be removed")
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9501); binding == nil {
|
||||
t.Fatal("admin binding should be skipped by inactive cleanup")
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9503); binding == nil {
|
||||
t.Fatal("recent user binding should remain")
|
||||
}
|
||||
|
||||
reply, err = bot.executeCommand(ctx, channel, msg, "/unbind_duplicates")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "已解绑:<b>1</b>") || !strings.Contains(reply.Text, "tg:9504") {
|
||||
t.Fatalf("unexpected duplicate cleanup reply: %q", reply.Text)
|
||||
}
|
||||
if binding := bot.telegramBinding(ctx, 9504); binding != nil {
|
||||
t.Fatal("invalid binding should be removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramMembershipChatIDsIncludesCommandChatID(t *testing.T) {
|
||||
_, bot := newBotTestService(t)
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"command_chat_id":"-100123"}`}
|
||||
got := bot.telegramMembershipChatIDs(channel)
|
||||
if len(got) != 1 || got[0] != "-100123" {
|
||||
t.Fatalf("telegramMembershipChatIDs() = %#v, want command_chat_id", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramMembershipChatIDsDedupesGroupChannelAndCommandIDs(t *testing.T) {
|
||||
_, bot := newBotTestService(t)
|
||||
channel := &model.NotifyChannel{Name: "Telegram", Type: "telegram", Enabled: true, Config: `{"group_chat_id":"-100123","channel_chat_id":"-100124","command_chat_id":"-100123"}`}
|
||||
got := bot.telegramMembershipChatIDs(channel)
|
||||
if len(got) != 2 || got[0] != "-100123" || got[1] != "-100124" {
|
||||
t.Fatalf("telegramMembershipChatIDs() = %#v, want deduped ids", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user