Files
MeBox/internal/service/device_notify_test.go
T
2026-09-22 16:22:29 +08:00

156 lines
5.1 KiB
Go

package service
import (
"context"
"strings"
"testing"
"go.uber.org/zap"
"github.com/truewhile/MeBox/internal/model"
"github.com/truewhile/MeBox/internal/repository"
)
func newDeviceServiceForTest(t *testing.T) (*DeviceService, *repository.Container, string) {
t.Helper()
repos := repository.New(newServiceTestDB(t))
const userID = "user-1"
if err := repos.User.Create(context.Background(), &model.User{
Base: model.Base{ID: userID},
Username: "tester",
PasswordHash: "x",
Role: "user",
IsActive: true,
}); err != nil {
t.Fatal(err)
}
svc := NewDeviceService(zap.NewNop(), repos)
svc.SetSessionTracker(NewSessionTrackerService(zap.NewNop()))
return svc, repos, userID
}
// 新终端首次登录必须通知用户,否则「谁在用我的账号」永远无从察觉。
func TestRecordLoginNotifiesOnNewDevice(t *testing.T) {
svc, _, userID := newDeviceServiceForTest(t)
type call struct{ userID, text string }
var calls []call
svc.SetNotifier(func(_ context.Context, uid, text string) {
calls = append(calls, call{uid, text})
})
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone", "Infuse", "1.2.3.4")
if len(calls) != 1 {
t.Fatalf("notifier calls = %d, want 1", len(calls))
}
if calls[0].userID != userID {
t.Fatalf("notifier user = %q, want %q", calls[0].userID, userID)
}
if !strings.Contains(calls[0].text, "新设备") {
t.Fatalf("notifier text = %q, want it to mention 新设备", calls[0].text)
}
}
// 已知终端重复登录不应刷屏:只在首次建档时通知。
func TestRecordLoginDoesNotNotifyOnKnownDevice(t *testing.T) {
svc, _, userID := newDeviceServiceForTest(t)
var count int
svc.SetNotifier(func(context.Context, string, string) { count++ })
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone", "Infuse", "1.2.3.4")
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone", "Infuse", "1.2.3.4")
if count != 1 {
t.Fatalf("notifier calls = %d, want exactly 1", count)
}
}
// 一键踢下线后必须告知用户,否则只会表现为「播放莫名失败」。
func TestKickDeviceNotifiesUser(t *testing.T) {
svc, _, userID := newDeviceServiceForTest(t)
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone", "Infuse", "1.2.3.4")
var kickText string
svc.SetNotifier(func(_ context.Context, _, text string) { kickText = text })
if err := svc.KickAllDevices(context.Background(), userID); err != nil {
t.Fatal(err)
}
if kickText == "" {
t.Fatal("expected a notification after kicking devices")
}
if !strings.Contains(kickText, "已下线") && !strings.Contains(kickText, "踢") {
t.Fatalf("kick notification text = %q, want it to describe the kick", kickText)
}
}
// 未接线 notifier 时(例如测试环境或 Bot 未配置),所有路径必须保持可用。
func TestDeviceServiceWorksWithoutNotifier(t *testing.T) {
svc, _, userID := newDeviceServiceForTest(t)
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone", "Infuse", "1.2.3.4")
if err := svc.KickAllDevices(context.Background(), userID); err != nil {
t.Fatal(err)
}
}
// 设备指纹警告除通知用户外,管理员也必须同步收到告警。
func TestFingerprintWarnNotifiesAdmin(t *testing.T) {
svc, repos, userID := newDeviceServiceForTest(t)
// 启用防共享策略
if err := repos.Setting.Set(context.Background(), SettingAntiShareEnabled, "true"); err != nil {
t.Fatal(err)
}
type call struct{ text string }
var adminCalls []call
svc.SetAdminNotifier(func(_ context.Context, text string) {
adminCalls = append(adminCalls, call{text})
})
svc.SetNotifier(func(context.Context, string, string) {}) // 用户通知静默接收
// 第一次登录注册设备
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone-A", "Infuse", "1.2.3.4")
// 同设备 ID 换设备名 → 触发指纹变更警告
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone-B", "Infuse", "1.2.3.4")
if len(adminCalls) == 0 {
t.Fatal("admin must be notified on fingerprint warn")
}
}
// 账号因设备策略被禁用时,管理员也必须收到告警。
func TestPolicyDisableNotifiesAdmin(t *testing.T) {
svc, repos, userID := newDeviceServiceForTest(t)
// 启用防共享策略,设置最大并发客户端为 1
for _, kv := range [][2]string{
{SettingAntiShareEnabled, "true"},
{SettingMaxLoggedClients, "1"},
{SettingClientActiveDays, "30"},
} {
if err := repos.Setting.Set(context.Background(), kv[0], kv[1]); err != nil {
t.Fatal(err)
}
}
var adminCalls []string
svc.SetAdminNotifier(func(_ context.Context, text string) {
adminCalls = append(adminCalls, text)
})
svc.SetNotifier(func(context.Context, string, string) {})
// 两台不同设备登录,超出上限 → 触发禁用
svc.RecordLogin(context.Background(), userID, "dev-1", "Phone", "Infuse", "1.2.3.4")
svc.RecordLogin(context.Background(), userID, "dev-2", "TV", "Emby", "1.2.3.5")
if len(adminCalls) == 0 {
t.Fatal("admin must be notified when account is disabled by policy")
}
if !strings.Contains(adminCalls[0], "禁用") {
t.Fatalf("admin notification = %q, want it to mention 禁用", adminCalls[0])
}
}