113 lines
5.1 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|