Files
kra-new/cmd/analytics_http_test.go
T

201 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"strconv"
"testing"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
"github.com/xuri/excelize/v2"
"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)",
"ALTER TABLE tab_promote ADD COLUMN nickname TEXT",
"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)), nil)
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("渠道树去重或商务展示未保留")
}
t.Run("export includes every authorized row and all totals", func(t *testing.T) {
req, _ := http.NewRequest("GET", server.URL+"/sw/channelData/list?export=true&sourceDb=lx,hw&beginTime=2026-11-04&endTime=2026-11-04&pageSize=1&pageNum=99", nil)
req.Header.Set("X-Test-User", "7")
res, err := server.Client().Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if res.Header.Get("success") != "true" {
t.Fatalf("export failed: %s", body)
}
book, err := excelize.OpenReader(bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
defer book.Close()
rows, err := book.GetRows("渠道数据统计")
if err != nil {
t.Fatal(err)
}
if len(rows) != 4 || rows[1][0] != "龙翔" || rows[2][0] != "龙翔" || rows[3][0] != "全部" || rows[3][1] != "合计(有效端 0)" || rows[3][9] != "66.67%" {
t.Fatalf("导出必须包含可见全量及完整合计,不受分页影响: %+v", rows)
}
amount, _ := book.GetCellValue("渠道数据统计", "G4", excelize.Options{RawCellValue: true})
if amount != "60.00" {
t.Fatalf("export amount=%s", amount)
}
})
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("缺汇总数据不应提供假零卡片")
}
}