feat: 迁移渠道列表新增和游戏累计分布
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/integration/gameplatform"
|
||||
"kra/internal/server/handler"
|
||||
"kra/internal/server/router"
|
||||
businessservice "kra/internal/service/business"
|
||||
)
|
||||
|
||||
type channelHTTPDatabases struct{ platform *gorm.DB }
|
||||
|
||||
func (d channelHTTPDatabases) Database(name string) (*gorm.DB, error) {
|
||||
if name == "lx" {
|
||||
return d.platform, nil
|
||||
}
|
||||
return nil, fmt.Errorf("未配置")
|
||||
}
|
||||
func TestChannelHTTPListsMetricsOptionsAndAtomicCreation(t *testing.T) {
|
||||
open := func(name string) *gorm.DB {
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), name)), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pool, _ := db.DB()
|
||||
t.Cleanup(func() { _ = pool.Close() })
|
||||
return db
|
||||
}
|
||||
db, platform := open("main.db"), open("platform.db")
|
||||
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','admin',0,0,'0',1),(7,'owner','owner',1,0,'0',1),(8,'child','child',2,7,'0,7',1),(9,'leaf','leaf',3,8,'0,7,8',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-account','0'),(99,'hw',14,'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)",
|
||||
"INSERT INTO tab_promote VALUES(100,'lx','main-channel','主渠道','','负责人',1,0,1,0,0,0,14,1000),(101,'lx','sub-channel','下级渠道','main-channel','',2,1,1,-1,100,100,14,1000),(100,'hw','other-platform','','','',1,0,1,0,0,0,14,1000)",
|
||||
"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',101,2,1)",
|
||||
"CREATE TABLE tab_user(source_db TEXT,id INTEGER,promote_id INTEGER,puid INTEGER,idcard TEXT)",
|
||||
"INSERT INTO tab_user VALUES('lx',1,100,0,'A-X'),('lx',2,101,0,'A-x'),('lx',3,101,0,''),('lx',4,101,2,'sub'),('hw',1,100,0,'other')",
|
||||
"CREATE TABLE tab_spend(source_db TEXT,promote_id INTEGER,pay_status INTEGER,pay_amount DECIMAL(20,2))",
|
||||
"INSERT INTO tab_spend VALUES('lx',100,1,10.10),('lx',101,1,20.20),('lx',100,0,999),('hw',100,1,999)",
|
||||
} {
|
||||
if err := db.Exec(sql).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, sql := range []string{
|
||||
"CREATE TABLE tab_promote(id INTEGER PRIMARY KEY AUTOINCREMENT,account TEXT UNIQUE,password TEXT,status INTEGER,busier_id INTEGER,create_time INTEGER,register_type INTEGER,parent_id INTEGER,promote_level INTEGER,top_promote_id INTEGER,game_ids2 TEXT)",
|
||||
"CREATE TABLE tab_promote_settlement_time(id INTEGER PRIMARY KEY AUTOINCREMENT,promote_id INTEGER,promote_account TEXT,time_type INTEGER,day_period INTEGER,create_time INTEGER,update_time INTEGER,next_count_time INTEGER)",
|
||||
"CREATE TABLE tab_promote_business(id INTEGER PRIMARY KEY,promote_ids TEXT)",
|
||||
"INSERT INTO tab_promote_business VALUES(14,'5,6')",
|
||||
} {
|
||||
if err := platform.Exec(sql).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
provider := businessHTTPData{db}
|
||||
passwords := &gameplatform.Passwords{AuthCode: "test-auth"}
|
||||
uc := businessbiz.NewChannelUsecase(businessdata.NewChannelRepo(provider), businessdata.NewChannelWriteRepo(channelHTTPDatabases{platform}), businessbiz.NewAccountUsecase(businessdata.NewAccountRepo(provider)), businessbiz.NewUserPreferenceUsecase(businessdata.NewUserPreferenceRepo(provider)), passwords)
|
||||
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()
|
||||
})
|
||||
router.RegisterChannel(engine.Group(""), handler.NewChannel(businessservice.NewChannelService(uc)))
|
||||
server := httptest.NewServer(engine)
|
||||
defer server.Close()
|
||||
call := func(user int, method, path, body string, expected int) any {
|
||||
t.Helper()
|
||||
req, _ := http.NewRequest(method, server.URL+path, strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
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 payload map[string]any
|
||||
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if payload["code"] != float64(expected) {
|
||||
t.Fatalf("%s %s 用户%d: %+v", method, path, user, payload)
|
||||
}
|
||||
return payload["data"]
|
||||
}
|
||||
list := func(user int, query string, total int) map[string]any {
|
||||
t.Helper()
|
||||
data := call(user, "GET", "/sw/promote/list"+query, "", 0).(map[string]any)
|
||||
if data["total"] != float64(total) {
|
||||
t.Fatalf("范围错误: %+v", data)
|
||||
}
|
||||
return data
|
||||
}
|
||||
rootRow := func(data map[string]any) map[string]any {
|
||||
for _, raw := range data["rows"].([]any) {
|
||||
row := raw.(map[string]any)
|
||||
if row["id"] == float64(100) && row["sourceDb"] == "lx" {
|
||||
return row
|
||||
}
|
||||
}
|
||||
t.Fatal("主渠道不见了")
|
||||
return nil
|
||||
}
|
||||
row := rootRow(list(8, "", 2))
|
||||
if row["gameCount"] != float64(1) || row["playerCount"] != float64(1) || row["totalPayAmount"] != "30.30" || row["busierDisplayName"] != "sdk-account(leaf)" {
|
||||
t.Fatalf("默认偏好或展示字段错误: %+v", row)
|
||||
}
|
||||
if err := db.Exec("INSERT INTO sys_user_preference(user_id,include_sub_promote,idcard_dedup) VALUES(8,1,0)").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
row = rootRow(list(8, "", 2))
|
||||
if row["playerCount"] != float64(3) || row["totalPayAmount"] != "30.30" {
|
||||
t.Fatal("关闭身份证去重不应改变金额")
|
||||
}
|
||||
if err := db.Exec("UPDATE sys_user_preference SET include_sub_promote=0 WHERE user_id=8").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
row = rootRow(list(8, "", 2))
|
||||
if row["playerCount"] != float64(1) || row["totalPayAmount"] != "10.10" {
|
||||
t.Fatal("关闭下级没有改变统计范围")
|
||||
}
|
||||
list(1, "", 3)
|
||||
list(8, "?sourceDb=hw", 0)
|
||||
list(8, "?busierUserId=99", 0)
|
||||
list(8, "?busierUserId=8", 0)
|
||||
list(8, "?lockStatus=-1", 1)
|
||||
list(1, "?busierAccount=leaf", 2)
|
||||
options := call(8, "GET", "/sw/promote/bindOptions?sourceDb=lx", "", 0).([]any)
|
||||
if len(options) != 1 || options[0].(map[string]any)["platformBusierId"] != float64(14) {
|
||||
t.Fatal("上级未获取下级正常绑定")
|
||||
}
|
||||
body := `{"sourceDb":"lx","platformBusierId":"14","account":"中文渠道","password":"demo123","registerType":1}`
|
||||
created := call(8, "POST", "/sw/promote", body, 0).(map[string]any)
|
||||
if created["sourceDb"] != "lx" {
|
||||
t.Fatal("返回平台丢失")
|
||||
}
|
||||
var persisted struct {
|
||||
Status, PromoteLevel int
|
||||
Password string
|
||||
}
|
||||
platform.Table("tab_promote").Take(&persisted)
|
||||
if persisted.Status != -3 || persisted.PromoteLevel != 1 || persisted.Password != "###eef6d9b7160f32a869f3e03b7ea575a5" {
|
||||
t.Fatalf("源平台创建不兼容: %+v", persisted)
|
||||
}
|
||||
call(8, "POST", "/sw/promote", body, 7)
|
||||
call(99, "POST", "/sw/promote", body, 7)
|
||||
list(8, "", 2)
|
||||
call(8, "GET", "/sw/promote/game-distribution?sourceDb=hw&promoteId=100", "", 7)
|
||||
call(8, "GET", "/sw/promote/game-distribution?sourceDb=lx&promoteId=100", "", 7)
|
||||
for _, sql := range []string{
|
||||
"CREATE TABLE tab_game(source_db TEXT,id INTEGER,game_name TEXT)",
|
||||
"INSERT INTO tab_game VALUES('lx',1,'汇总游戏')",
|
||||
"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))",
|
||||
"INSERT INTO tab_sw_report_day VALUES('lx',100,1,0,'2000-01-01',70.00),('lx',100,1,1,'2000-01-01',700.00)",
|
||||
"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)",
|
||||
"INSERT INTO tab_sw_report_day_key VALUES('lx',100,1,0,'2000-01-01',1,'one'),('lx',100,1,0,'2000-01-01',1,'two')",
|
||||
} {
|
||||
if err := db.Exec(sql).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
games := call(8, "GET", "/sw/promote/game-distribution?sourceDb=lx&promoteId=100", "", 0).([]any)
|
||||
if len(games) != 1 {
|
||||
t.Fatal("游戏分布没有按当前渠道的推广游戏过滤")
|
||||
}
|
||||
game := games[0].(map[string]any)
|
||||
if game["gameName"] != "汇总游戏" || game["registerCount"] != float64(2) || game["payAmount"] != "70.00" {
|
||||
t.Fatalf("游戏分布误用了原始注册/充值或错误金额口径: %+v", game)
|
||||
}
|
||||
if err := db.Exec("DROP TABLE tab_promote").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data := list(8, "", 0)
|
||||
if data["available"] != false {
|
||||
t.Fatal("缺业务表必须明确未接入")
|
||||
}
|
||||
}
|
||||
Generated
+10
-3
@@ -82,9 +82,17 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
||||
auditRecorderUsecase := system2.NewAuditRecorderUsecase(auditRecordRepo)
|
||||
auditRecorder := system3.NewAuditRecorder(auditRecorderUsecase)
|
||||
registry := taskRegistry()
|
||||
activationRepo := business.NewActivationRepo(dataData, dataData)
|
||||
channelRepo := business.NewChannelRepo(dataData)
|
||||
channelWriteRepo := business.NewChannelWriteRepo(dataData)
|
||||
accountRepo := business.NewAccountRepo(dataData)
|
||||
accountUsecase := business2.NewAccountUsecase(accountRepo)
|
||||
userPreferenceRepo := business.NewUserPreferenceRepo(dataData)
|
||||
userPreferenceUsecase := business2.NewUserPreferenceUsecase(userPreferenceRepo)
|
||||
platformPasswordEncoder := gameplatform.NewPasswordEncoder()
|
||||
channelUsecase := business2.NewChannelUsecase(channelRepo, channelWriteRepo, accountUsecase, userPreferenceUsecase, platformPasswordEncoder)
|
||||
channelService := business3.NewChannelService(channelUsecase)
|
||||
channel := handler.NewChannel(channelService)
|
||||
activationRepo := business.NewActivationRepo(dataData, dataData)
|
||||
activationUsecase := business2.NewActivationUsecase(activationRepo, accountUsecase)
|
||||
activationService := business3.NewActivationService(activationUsecase)
|
||||
activation := handler.NewActivation(activationService)
|
||||
@@ -100,8 +108,6 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
||||
businessPromoteUsecase := business2.NewBusinessPromoteUsecase(businessPromoteRepo, accountUsecase)
|
||||
businessPromoteService := business3.NewBusinessPromoteService(businessPromoteUsecase)
|
||||
businessPromote := handler.NewBusinessPromote(businessPromoteService)
|
||||
userPreferenceRepo := business.NewUserPreferenceRepo(dataData)
|
||||
userPreferenceUsecase := business2.NewUserPreferenceUsecase(userPreferenceRepo)
|
||||
userPreferenceService := business3.NewUserPreferenceService(userPreferenceUsecase)
|
||||
accountService := business3.NewAccountService(accountUsecase)
|
||||
bindingRepo := business.NewBindingRepo(dataData)
|
||||
@@ -213,6 +219,7 @@ func wireApp(configServer *config.Server, store *config.Store, logger *slog.Logg
|
||||
integrationConfigService := integration2.NewIntegrationConfigService(integrationConfigUsecase)
|
||||
integrationConfig := handler.NewIntegrationConfig(integrationConfigService)
|
||||
set := &handler.Set{
|
||||
Channel: channel,
|
||||
Activation: activation,
|
||||
AlertRecord: alertRecord,
|
||||
AlertRule: alertRule,
|
||||
|
||||
Reference in New Issue
Block a user