Files
kra-new/internal/biz/business/alert_trigger_test.go
T

113 lines
5.1 KiB
Go

package business
import (
"context"
"errors"
"strings"
"testing"
"time"
)
type triggerRepoFake struct {
input *AlertTriggerInput
metrics map[ChannelKey]AlertComparison
hits []*AlertRecord
metricCalls int
current *AlertRecord
failure error
}
func (r *triggerRepoFake) Input(context.Context) (*AlertTriggerInput, error) { return r.input, nil }
func (r *triggerRepoFake) Comparison(context.Context, int, []ChannelKey, time.Time, int) (map[ChannelKey]AlertComparison, error) {
r.metricCalls++
return r.metrics, r.failure
}
func (r *triggerRepoFake) StoreHit(_ context.Context, hit *AlertRecord, _ []int, decide func(*AlertRecord) bool) (bool, error) {
if !decide(r.current) {
return false, nil
}
r.hits = append(r.hits, hit)
return true, r.failure
}
func triggerFixture() (*triggerRepoFake, time.Time) {
now := time.Date(2026, 11, 4, 12, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600))
r := &triggerRepoFake{input: &AlertTriggerInput{Accounts: []*Account{
{ID: 1, AuthorityID: 888}, {ID: 10, Level: 1, Username: "一级甲"}, {ID: 11, Level: 1, Username: "一级乙"},
{ID: 20, Level: 2, IDPath: "0,10"}, {ID: 30, Level: 3, IDPath: "0,10,20"}, {ID: 31, Level: 3, IDPath: "0,11,21"},
}, Bindings: []*Binding{
{UserID: 30, PlatformKey: PlatformKey{SourceDB: "lx", BusierID: 7}, Status: "0"},
{UserID: 31, PlatformKey: PlatformKey{SourceDB: "hw", BusierID: 7}, Status: "0"},
{UserID: 30, PlatformKey: PlatformKey{SourceDB: "lx", BusierID: 8}, Status: "0"},
{UserID: 31, PlatformKey: PlatformKey{SourceDB: "lx", BusierID: 8}, Status: "0"},
}, Channels: []*AlertTriggerChannel{
{ChannelKey: ChannelKey{"lx", 10}, BusierID: 7, Account: "甲渠道", CreatedUnix: now.AddDate(0, 0, -3).Unix()},
{ChannelKey: ChannelKey{"hw", 10}, BusierID: 7, Account: "乙渠道", LastLoginUnix: now.AddDate(0, 0, -10).Unix()},
{ChannelKey: ChannelKey{"lx", 11}, BusierID: 8, Account: "冲突渠道", CreatedUnix: now.AddDate(0, 0, -10).Unix()},
}}}
return r, now
}
func TestAlertTriggerSeparatesOwnerPlatformAndConflictingBindings(t *testing.T) {
r, now := triggerFixture()
days := 3
r.input.Rules = []*AlertRule{{ID: 1, CreatorID: 10, Type: "未登录", Metric: 3, UnloginDays: &days}, {ID: 2, CreatorID: 20, Type: "越级", Metric: 3, UnloginDays: &days}}
result, err := NewAlertTriggerUsecase(r).Detect(context.Background(), now)
if err != nil || result.Written != 1 || result.Skipped != 1 || len(r.hits) != 1 {
t.Fatalf("检测结果 %+v %v", result, err)
}
hit := r.hits[0]
if hit.BusinessID != 10 || hit.PlatformCode != "lx" || hit.TargetPromoteID != 10 || hit.AlertContent != "连续3天未登录" {
t.Fatalf("归属或自然日错误: %+v", hit)
}
r.hits = nil
r.current = &AlertRecord{Status: 2}
result, err = NewAlertTriggerUsecase(r).Detect(context.Background(), now)
if err != nil || result.Written != 0 || len(r.hits) != 0 {
t.Fatalf("在途记录不应刷新: %+v %v", result, err)
}
}
func TestAlertTriggerMoMBoundariesAndSnapshots(t *testing.T) {
r, now := triggerFixture()
days, direction := 2, 1
threshold := "10"
r.input.Rules = []*AlertRule{{ID: 1, CreatorID: 10, Type: "充值", Metric: 2, StatPeriodDays: &days, MomDirection: &direction, MomThreshold: &threshold},
{ID: 2, CreatorID: 10, Type: "另一个充值规则", Metric: 2, StatPeriodDays: &days, MomDirection: &direction, MomThreshold: &threshold}}
r.metrics = map[ChannelKey]AlertComparison{{"lx", 10}: {FirstUnix: now.AddDate(0, 0, -4).Unix(), Current: "110000", Previous: "100000"}}
result, err := NewAlertTriggerUsecase(r).Detect(context.Background(), now)
if err != nil || result.Written != 2 || r.metricCalls != 1 {
t.Fatalf("同窗口应共享指标: %+v calls=%d %v", result, r.metricCalls, err)
}
if r.hits[0].AlertContent != "截至昨日2个完整自然日充值¥11.00万,较此前2个完整自然日(¥10.00万)环比上升10.00%" {
t.Fatal(r.hits[0].AlertContent)
}
r.metrics[ChannelKey{"lx", 10}] = AlertComparison{FirstUnix: now.AddDate(0, 0, -3).Unix(), Current: "110000", Previous: "100000"}
r.hits = nil
result, err = NewAlertTriggerUsecase(r).Detect(context.Background(), now)
if err != nil || result.Written != 0 {
t.Fatalf("不足双周期不应触发: %+v %v", result, err)
}
r.failure = errors.New("指标不可用")
_, err = NewAlertTriggerUsecase(r).Detect(context.Background(), now)
if err == nil || !strings.Contains(err.Error(), "2 项失败") {
t.Fatalf("读取失败不能报告成功: %v", err)
}
}
func TestAlertTriggerRoundedThresholdAndNoBaseline(t *testing.T) {
days, direction := 1, 1
threshold := "10"
rule := &AlertRule{Metric: 1, StatPeriodDays: &days, MomDirection: &direction, MomThreshold: &threshold}
_, now := triggerFixture()
for _, test := range []struct {
current, previous string
hit bool
}{{"109.995", "100", true}, {"109.994", "100", false}, {"1", "0", false}, {"1", "-1", false}} {
_, hit, err := triggerContent(rule, &AlertTriggerChannel{}, AlertComparison{FirstUnix: now.AddDate(0, 0, -2).Unix(), Current: test.current, Previous: test.previous}, now)
if err != nil || hit != test.hit {
t.Fatalf("%+v: hit=%v err=%v", test, hit, err)
}
}
}