165 lines
7.6 KiB
Go
165 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"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 TestChannelAndGameAnalyticsHTTP(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "analytics.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,'other','other',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,'其他账号','0')",
|
|
"CREATE TABLE tab_promote(id INTEGER,source_db TEXT,account TEXT,promote_level INTEGER,top_promote_id INTEGER,busier_id INTEGER,register_type INTEGER)",
|
|
"INSERT INTO tab_promote VALUES(100,'lx','root-channel',1,0,14,0),(101,'lx','child-channel',2,100,14,0),(200,'lx','guild-channel',1,0,14,1),(100,'hw','other-platform',1,0,20,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),('hw',100,1,1),('lx',200,2,1)",
|
|
"CREATE TABLE tab_game(id INTEGER,source_db TEXT,game_name TEXT)",
|
|
"INSERT INTO tab_game VALUES(1,'lx','当前游戏'),(2,'lx','公会游戏'),(1,'hw','外服游戏')",
|
|
"CREATE TABLE tab_sw_report_day(source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,dedup_mode INTEGER,stat_date DATE,total_pay DECIMAL(20,2))",
|
|
"INSERT INTO tab_sw_report_day VALUES('lx',100,1,'旧游戏名',0,'2026-11-04',10),('lx',101,1,'旧游戏名',0,'2026-11-04',20),('lx',200,2,'公会游戏',0,'2026-11-04',30),('hw',100,1,'旧游戏名',0,'2026-11-04',40),('lx',0,1,'旧游戏名',0,'2026-11-04',5),('lx',999,1,'旧游戏名',0,'2026-11-04',7),('lx',100,1,'旧游戏名',1,'2026-11-04',9999)",
|
|
"CREATE TABLE tab_sw_report_day_key(source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,dedup_mode INTEGER,stat_date DATE,metric INTEGER,identity_key TEXT)",
|
|
} {
|
|
if err := db.Exec(sql).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, row := range []struct {
|
|
source string
|
|
promote, game int
|
|
key string
|
|
}{{"lx", 100, 1, "A"}, {"lx", 101, 1, "A"}, {"lx", 200, 2, "A"}, {"hw", 100, 1, "A"}, {"lx", 0, 1, "official"}, {"lx", 999, 1, "orphan"}} {
|
|
name := "旧游戏名"
|
|
if row.game == 2 {
|
|
name = "公会游戏"
|
|
}
|
|
for _, metric := range []int{1, 2, 3, 4} {
|
|
if err := db.Exec("INSERT INTO tab_sw_report_day_key VALUES(?,?,?,?,1,'2026-11-04',?,?)", row.source, row.promote, row.game, name, metric, row.key).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
if err := db.Exec("INSERT INTO tab_sw_report_day_key VALUES('lx',101,1,'旧游戏名',1,'2026-11-04',2,'B')").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, row := range []struct {
|
|
promote, game int
|
|
id string
|
|
}{{100, 1, "1"}, {101, 1, "2"}, {200, 2, "3"}} {
|
|
for _, metric := range []int{1, 2, 3, 4} {
|
|
if err := db.Exec("INSERT INTO tab_sw_report_day_key VALUES('lx',?,?,'旧游戏名',0,'2026-11-04',?,?)", row.promote, row.game, metric, row.id).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
provider := businessHTTPData{db}
|
|
uc := businessbiz.NewAnalyticsUsecase(businessdata.NewAnalyticsRepo(provider), businessbiz.NewAccountUsecase(businessdata.NewAccountRepo(provider)), businessbiz.NewUserPreferenceUsecase(businessdata.NewUserPreferenceRepo(provider)))
|
|
h := handler.NewAnalytics(businessservice.NewAnalyticsService(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/channelData/list", h.Channels)
|
|
engine.GET("/sw/gameData/list", h.Games)
|
|
server := httptest.NewServer(engine)
|
|
defer server.Close()
|
|
call := func(user int, kind, query string, expected int) map[string]any {
|
|
t.Helper()
|
|
req, _ := http.NewRequest("GET", server.URL+"/sw/"+kind+"/list?beginTime=2026-11-04&endTime=2026-11-04&"+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 %s: %+v", kind, query, body)
|
|
}
|
|
return body["data"].(map[string]any)
|
|
}
|
|
data := call(7, "channelData", "sourceDb=lx,hw&pageSize=1", 0)
|
|
if data["total"] != float64(2) || len(data["rows"].([]any)) != 1 || data["channelCount"] != float64(2) || data["totalRegisterCount"] != float64(2) || data["totalPayAmount"] != "60.00" || data["avgPayRate"] != "66.67" {
|
|
t.Fatalf("普通商务渠道汇总或分页错误: %+v", data)
|
|
}
|
|
first := data["rows"].([]any)[0].(map[string]any)
|
|
if first["busierName"] != "SDK账号" || first["newRegisterCount"] != float64(1) {
|
|
t.Fatal("渠道树去重或商务展示未保留")
|
|
}
|
|
data = call(1, "channelData", "sourceDb=lx,hw", 0)
|
|
if data["total"] != float64(5) || data["totalRegisterCount"] != float64(3) || data["totalPayAmount"] != "112.00" || data["avgPayRate"] != "75.00" {
|
|
t.Fatalf("超管应全局去重并包含官方/未知渠道: %+v", data)
|
|
}
|
|
data = call(7, "gameData", "sourceDb=lx,hw&pageSize=1", 0)
|
|
sum := data["summary"].(map[string]any)
|
|
if data["total"] != float64(2) || len(data["rows"].([]any)) != 1 || sum["totalPay"] != "60.00" || sum["newRegisterCount"] != float64(2) || sum["arpu"] != "20.00" || sum["arppu"] != "30.00" {
|
|
t.Fatalf("游戏汇总应覆盖整份筛选结果并使用活跃/付费分母: %+v", data)
|
|
}
|
|
data = call(1, "gameData", "sourceDb=lx,hw", 0)
|
|
if data["summary"].(map[string]any)["newRegisterCount"] != float64(5) {
|
|
t.Fatal("游戏汇总不能偷换成全局去重人数")
|
|
}
|
|
data = call(7, "channelData", "sourceDb=lx&promoteName=child", 0)
|
|
if data["total"] != float64(1) || data["totalPayAmount"] != "30.00" {
|
|
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, "channelData", "sourceDb=lx", 0)
|
|
if data["totalRegisterCount"] != float64(3) || data["totalPayAmount"] != "60.00" {
|
|
t.Fatal("关闭身份证去重改变了金额或未改变人数")
|
|
}
|
|
if err := db.Exec("UPDATE sys_user_preference SET include_sub_promote=0 WHERE user_id=7").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data = call(7, "channelData", "sourceDb=lx", 0)
|
|
if data["totalPayAmount"] != "40.00" {
|
|
t.Fatal("不包含下级仍计入子渠道金额")
|
|
}
|
|
data = call(99, "gameData", "sourceDb=lx", 0)
|
|
if data["total"] != float64(0) {
|
|
t.Fatal("其他平台绑定用户越权查询 lx")
|
|
}
|
|
if err := db.Exec("DROP TABLE tab_sw_report_day_key").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data = call(7, "channelData", "sourceDb=lx", 0)
|
|
if data["available"] != false || data["summary"] != nil || data["totalPayAmount"] != nil {
|
|
t.Fatal("缺汇总数据不应提供假零卡片")
|
|
}
|
|
}
|