196 lines
9.0 KiB
Go
196 lines
9.0 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"path/filepath"
|
||
"strconv"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/glebarez/sqlite"
|
||
"gorm.io/gorm"
|
||
businessbiz "kra/internal/biz/business"
|
||
"kra/internal/biz/system"
|
||
businessdata "kra/internal/data/business"
|
||
systemdata "kra/internal/data/system"
|
||
"kra/internal/server/handler"
|
||
businessservice "kra/internal/service/business"
|
||
)
|
||
|
||
func TestChannelPortraitHTTPTotalTiersAndScope(t *testing.T) {
|
||
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "channel-portrait.db")), &gorm.Config{})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
pool, _ := db.DB()
|
||
t.Cleanup(func() { _ = pool.Close() })
|
||
for _, step := range append(systemdata.Migrations(), businessdata.Migrations()...) {
|
||
if err := step.Migrate(db); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
for _, sql := range []string{
|
||
"INSERT INTO sys_users(id,uuid,username,level,parent_id,id_path,enable) VALUES(1,'root','root',0,0,'0',1),(7,'owner','owner',1,0,'0',1),(9,'leaf','leaf',3,7,'0,7',1),(99,'outside','outside',1,0,'0',1)",
|
||
"INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,platform_busier_account,status) VALUES(9,'lx',14,'sdk','0'),(99,'hw',20,'other','0')",
|
||
"CREATE TABLE tab_promote(id INTEGER,source_db TEXT,account TEXT,nickname TEXT,parent_name TEXT,real_name TEXT,promote_level INTEGER,register_type INTEGER,status INTEGER,lock_status INTEGER,parent_id INTEGER,top_promote_id INTEGER,busier_id INTEGER,create_time INTEGER,last_login_time INTEGER)",
|
||
"INSERT INTO tab_promote VALUES(100,'lx','root-account','主渠道','','负责人',1,1,-3,0,0,0,14,1000,2000),(101,'lx','child-account','子渠道','旧上级名称','',2,0,1,0,100,100,14,1000,0),(100,'hw','outside-channel','','','',1,0,1,0,0,0,20,1000,0)",
|
||
"CREATE TABLE tab_promote_apply(source_db TEXT,promote_id INTEGER,game_id INTEGER,status INTEGER)",
|
||
"INSERT INTO tab_promote_apply VALUES('lx',100,1,1),('lx',100,1,1),('lx',100,2,1)",
|
||
"CREATE TABLE tab_game(source_db TEXT,id INTEGER,game_name TEXT)",
|
||
"INSERT INTO tab_game VALUES('lx',1,'游戏甲'),('lx',2,'游戏乙')",
|
||
"CREATE TABLE tab_sw_report_day(source_db TEXT,promote_id INTEGER,game_id INTEGER,dedup_mode INTEGER,stat_date DATE,total_pay DECIMAL(20,2))",
|
||
"CREATE TABLE tab_sw_report_day_key(source_db TEXT,promote_id INTEGER,game_id INTEGER,dedup_mode INTEGER,stat_date DATE,metric INTEGER,identity_key TEXT)",
|
||
"CREATE TABLE tab_user(source_db TEXT,id INTEGER,promote_id INTEGER,puid INTEGER,idcard TEXT,register_time INTEGER)",
|
||
"CREATE TABLE tab_spend(source_db TEXT,promote_id INTEGER,user_id INTEGER,pay_status INTEGER,pay_time INTEGER,pay_amount DECIMAL(20,2))",
|
||
} {
|
||
if err := db.Exec(sql).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
now := time.Now().In(time.FixedZone("CST", 8*3600))
|
||
today := now.Format("2006-01-02")
|
||
day20 := now.AddDate(0, 0, -20).Format("2006-01-02")
|
||
day30 := now.AddDate(0, 0, -30).Format("2006-01-02")
|
||
previous := now.AddDate(0, 0, -7).Format("2006-01-02")
|
||
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Unix()
|
||
for _, row := range []struct {
|
||
promote, mode int
|
||
date, amount string
|
||
}{{100, 0, day30, "20.00"}, {100, 1, day20, "777.00"}, {100, 0, day20, "10.00"}, {100, 0, previous, "6.00"}, {100, 0, today, "12.00"}, {101, 0, today, "3.00"}} {
|
||
if err := db.Exec("INSERT INTO tab_sw_report_day VALUES('lx',?,1,?,?,?)", row.promote, row.mode, row.date, row.amount).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
cards := []string{"A-X", " A-x ", "B", "C", "D"}
|
||
for i, card := range cards {
|
||
promote := 100
|
||
if i == 1 {
|
||
promote = 101
|
||
}
|
||
if err := db.Exec("INSERT INTO tab_user VALUES('lx',?,?,0,?,?)", i+1, promote, card, start+3600).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
for _, mode := range []int{0, 1} {
|
||
key := strconv.Itoa(i + 1)
|
||
if mode == 1 {
|
||
key = card
|
||
if i < 2 {
|
||
key = "A-x"
|
||
}
|
||
}
|
||
for _, metric := range []int{1, 2} {
|
||
if err := db.Exec("INSERT INTO tab_sw_report_day_key VALUES('lx',?,1,?,?,?,?)", promote, mode, today, metric, key).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if err := db.Exec("INSERT INTO tab_sw_report_day_key VALUES('lx',100,1,1,?,3,'A-x')", today).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
for _, row := range []struct {
|
||
promote, user int
|
||
timestamp int64
|
||
amount string
|
||
}{{100, 1, 1000, "9999.50"}, {101, 2, 2000000000, "0.50"}, {100, 3, start + 3600, "1000.00"}, {100, 4, start + 3600, "0.01"}, {100, 5, start + 3600, "0.00"}} {
|
||
if err := db.Exec("INSERT INTO tab_spend VALUES('lx',?,?,1,?,?)", row.promote, row.user, row.timestamp, row.amount).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
provider := businessHTTPData{db}
|
||
channels := businessdata.NewChannelRepo(provider)
|
||
accounts := businessbiz.NewAccountUsecase(businessdata.NewAccountRepo(provider))
|
||
prefs := businessbiz.NewUserPreferenceUsecase(businessdata.NewUserPreferenceRepo(provider))
|
||
uc := businessbiz.NewChannelPortraitUsecase(businessdata.NewChannelPortraitRepo(provider), channels, businessdata.NewBusinessPortraitRepo(provider, channelHTTPDatabases{}), accounts, prefs)
|
||
h := handler.NewChannelPortrait(businessservice.NewChannelPortraitService(uc))
|
||
engine := gin.New()
|
||
engine.Use(func(c *gin.Context) {
|
||
id, _ := strconv.ParseUint(c.GetHeader("X-Test-User"), 10, 64)
|
||
aid := uint(901)
|
||
if id == 1 {
|
||
aid = 888
|
||
}
|
||
c.Request = c.Request.WithContext(system.NewActorContext(c.Request.Context(), system.Actor{UserID: uint(id), AuthorityID: aid}))
|
||
c.Next()
|
||
})
|
||
engine.GET("/sw/promote/portrait", h.Portrait)
|
||
server := httptest.NewServer(engine)
|
||
defer server.Close()
|
||
call := func(user int, query string, expected int) map[string]any {
|
||
t.Helper()
|
||
req, _ := http.NewRequest("GET", server.URL+"/sw/promote/portrait?"+query, nil)
|
||
req.Header.Set("X-Test-User", strconv.Itoa(user))
|
||
res, err := server.Client().Do(req)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer res.Body.Close()
|
||
var body map[string]any
|
||
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if body["code"] != float64(expected) {
|
||
t.Fatalf("画像 %s 用户%d响应错误: %+v", query, user, body)
|
||
}
|
||
return body["data"].(map[string]any)
|
||
}
|
||
data := call(7, "sourceDb=lx&promoteId=100&period=today", 0)
|
||
basic := data["basic"].(map[string]any)
|
||
metrics := data["metrics"].(map[string]any)
|
||
if basic["statusLabel"] != "商务新增" || basic["gameCount"] != float64(2) || basic["parentPromoteId"] != nil || basic["lastLoginTime"] == nil || basic["busierDisplayName"] != "sdk(leaf)" {
|
||
t.Fatalf("基本信息错误: %+v", basic)
|
||
}
|
||
if metrics["register"].(map[string]any)["value"] != "4" || metrics["active"].(map[string]any)["value"] != "4" || metrics["payRate"].(map[string]any)["value"] != "25.0" || metrics["payAmount"].(map[string]any)["value"] != "15.00" {
|
||
t.Fatalf("本期指标错误: %+v", metrics)
|
||
}
|
||
if _, ok := metrics["arpu"]; ok {
|
||
t.Fatal("渠道第四项是活跃人数,不是商务 ARPU")
|
||
}
|
||
tiers := data["payTiers"].([]any)
|
||
for _, raw := range tiers {
|
||
tier := raw.(map[string]any)
|
||
if tier["count"] != float64(1) || tier["ratio"] != "25.0" {
|
||
t.Fatalf("累计付费分层错误: %+v", tiers)
|
||
}
|
||
}
|
||
if data["gameDistribution"].([]any)[0].(map[string]any)["payAmount"] != "31.00" {
|
||
t.Fatal("游戏分布应始终累计,不随顶部今日切换")
|
||
}
|
||
data = call(7, "sourceDb=lx&promoteId=100&period=total", 0)
|
||
metrics = data["metrics"].(map[string]any)
|
||
if metrics["payAmount"].(map[string]any)["value"] != "31.00" || len(data["registerTrend"].([]any)) != 21 {
|
||
t.Fatal("累计模式没有使用当前去重口径最早日期")
|
||
}
|
||
for _, raw := range metrics {
|
||
metric := raw.(map[string]any)
|
||
if metric["compareValue"] != nil || metric["changeRate"] != nil || metric["changeDiff"] != nil || metric["compareLabel"] != nil {
|
||
t.Fatal("累计模式不应有环比字段值")
|
||
}
|
||
}
|
||
if err := db.Exec("INSERT INTO sys_user_preference(user_id,include_sub_promote,idcard_dedup) VALUES(7,1,0)").Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
data = call(7, "sourceDb=lx&promoteId=100&period=total", 0)
|
||
tiers = data["payTiers"].([]any)
|
||
if data["metrics"].(map[string]any)["payAmount"].(map[string]any)["value"] != "51.00" || tiers[0].(map[string]any)["count"] != float64(0) || tiers[1].(map[string]any)["count"] != float64(2) {
|
||
t.Fatal("模式0应按原始付款账号分类,且使用自己的累计起点")
|
||
}
|
||
data = call(7, "sourceDb=lx&promoteId=101&period=last7", 0)
|
||
basic = data["basic"].(map[string]any)
|
||
if basic["parentPromoteId"] != float64(100) || basic["parentPromoteName"] != "root-account" || basic["lastLoginTime"] != nil {
|
||
t.Fatal("上级应读同平台当前账号,不能沿用旧快照名")
|
||
}
|
||
call(99, "sourceDb=lx&promoteId=100", 7)
|
||
call(7, "sourceDb=hw&promoteId=100", 7)
|
||
if err := db.Exec("DROP TABLE tab_sw_report_day_key").Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
data = call(7, "sourceDb=lx&promoteId=100", 0)
|
||
if data["available"] != false || data["metrics"] != nil || data["basic"].(map[string]any)["promoteId"] != float64(100) {
|
||
t.Fatal("缺数据时应保留已授权基本资料并标明未知")
|
||
}
|
||
}
|