package main import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "path/filepath" "testing" "time" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" "gorm.io/gorm" businessbiz "kra/internal/biz/business" taskbiz "kra/internal/biz/task" businessdata "kra/internal/data/business" taskdata "kra/internal/data/task" "kra/internal/server/handler" "kra/internal/server/router" taskservice "kra/internal/service/task" "kra/internal/worker" platformtask "kra/pkg/task" ) type reportTaskHTTPData struct{ businessHTTPData } func (reportTaskHTTPData) DatabaseReady() bool { return true } func TestReportBuildThroughNativeTaskHTTP(t *testing.T) { db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "report-task.db")), &gorm.Config{}) if err != nil { t.Fatal(err) } pool, _ := db.DB() t.Cleanup(func() { _ = pool.Close() }) for _, step := range append(taskdata.Migrations(), businessdata.Migrations()...) { if err := step.Migrate(db); err != nil { t.Fatal(err) } } for _, sql := range []string{ "CREATE TABLE tab_user(id INTEGER,source_db TEXT,puid INTEGER,promote_id INTEGER,fgame_id INTEGER,idcard TEXT,register_time TEXT)", "CREATE TABLE tab_user_day_login(user_id INTEGER,source_db TEXT,promote_id INTEGER,game_id INTEGER,login_time INTEGER)", "CREATE TABLE tab_user_play_info(user_id INTEGER,source_db TEXT,promote_id INTEGER,game_id INTEGER,server_id TEXT,create_time INTEGER,play_time INTEGER)", "CREATE TABLE tab_spend(user_id INTEGER,source_db TEXT,promote_id INTEGER,game_id INTEGER,server_id TEXT,pay_time INTEGER,pay_status INTEGER,pay_amount DECIMAL(20,2))", "CREATE TABLE tab_game(id INTEGER,source_db TEXT,game_name TEXT)", "INSERT INTO tab_game VALUES(1,'lx','核验游戏')", "CREATE TABLE tab_sw_report_day(id INTEGER PRIMARY KEY,stat_date DATE,source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,dedup_mode INTEGER,new_register_cnt INTEGER,active_cnt INTEGER,pay_cnt INTEGER,new_pay_cnt INTEGER,total_pay DECIMAL(20,2))", "CREATE TABLE tab_sw_report_day_key(id INTEGER PRIMARY KEY,stat_date DATE,source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,dedup_mode INTEGER,metric INTEGER,identity_key TEXT)", "CREATE TABLE tab_sw_report_server_day(id INTEGER PRIMARY KEY,stat_date DATE,source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,server_num TEXT,dedup_mode INTEGER,new_register_cnt INTEGER,active_cnt INTEGER,pay_cnt INTEGER,total_pay DECIMAL(20,2))", "CREATE TABLE tab_sw_report_server_day_key(id INTEGER PRIMARY KEY,stat_date DATE,source_db TEXT,promote_id INTEGER,game_id INTEGER,game_name TEXT,server_num TEXT,dedup_mode INTEGER,metric INTEGER,identity_key TEXT)", } { if err := db.Exec(sql).Error; err != nil { t.Fatal(err) } } stamp := time.Date(2026, 11, 4, 12, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)).Unix() for _, sql := range []string{"INSERT INTO tab_user VALUES(1,'lx',0,10,1,'aX',?)", "INSERT INTO tab_user_day_login VALUES(1,'lx',10,1,?)", "INSERT INTO tab_spend VALUES(1,'lx',10,1,'001',?,1,12.50)"} { if err := db.Exec(sql, stamp).Error; err != nil { t.Fatal(err) } } if err := db.Exec("INSERT INTO tab_user_play_info VALUES(1,'lx',10,1,'001',?,?)", stamp, stamp).Error; err != nil { t.Fatal(err) } provider := businessHTTPData{db} registry := platformtask.NewRegistry() tasks := taskbiz.NewTaskUsecaseWithRegistry(taskdata.NewTaskRepo(reportTaskHTTPData{provider}), registry) reports := businessbiz.NewReportBuildUsecase(businessdata.NewReportBuildRepo(provider)) worker.NewTaskMethods(tasks, nil, nil, nil, reports, nil, nil).RegisterTasks(registry) scheduler := worker.NewTaskScheduler(tasks, nil, worker.NewTaskExecutorWithRegistry(tasks, registry), nil) defer func() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = scheduler.Stop(ctx) }() h := handler.NewTask(taskservice.NewTaskService(taskbiz.NewTaskApplicationUsecase(tasks, scheduler))) engine := gin.New() router.RegisterTask(engine.Group(""), h) srv := httptest.NewServer(engine) defer srv.Close() call := func(method, path string, body any) map[string]any { t.Helper() raw, _ := json.Marshal(body) req, _ := http.NewRequest(method, srv.URL+"/timedTask/"+path, bytes.NewReader(raw)) req.Header.Set("Content-Type", "application/json") res, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } defer res.Body.Close() var value map[string]any if err := json.NewDecoder(res.Body).Decode(&value); err != nil { t.Fatal(err) } if value["code"] != float64(0) { t.Fatalf("原生任务接口失败: %+v", value) } return value["data"].(map[string]any) } methods := call("GET", "getRegisteredMethods", nil)["methods"].([]any) if len(methods) != 11 { t.Fatalf("原生任务方法数量不正确: %d", len(methods)) } for index, method := range []string{"swReportDayBackfillTask", "swReportServerDayBackfillTask"} { name := fmt.Sprintf("隔离汇总任务%d", index) call("POST", "createTimedTask", map[string]any{"name": name, "spec": "@daily", "executorType": "method", "methodName": method, "params": map[string]any{"startDate": "2026-11-04", "endDate": "2026-11-04"}, "enabled": false}) var id uint if err := db.Table("sys_timed_tasks").Select("id").Where("name=?", name).Scan(&id).Error; err != nil || id == 0 { t.Fatal("任务创建未落库") } call("POST", "triggerTimedTask", map[string]any{"id": id}) deadline := time.Now().Add(8 * time.Second) done := false for time.Now().Before(deadline) { logs := call("GET", fmt.Sprintf("getTimedTaskLogList?taskId=%d&page=1&pageSize=10", id), nil)["list"].([]any) if len(logs) > 0 { if logs[0].(map[string]any)["status"] != "success" { t.Fatalf("汇总任务失败: %+v", logs) } done = true break } time.Sleep(20 * time.Millisecond) } if !done { t.Fatal("未等到汇总任务完成") } } for _, table := range []string{"tab_sw_report_day", "tab_sw_report_server_day"} { var rows []struct { TotalPay string NewRegisterCnt, ActiveCnt, PayCnt int } if err := db.Table(table).Scan(&rows).Error; err != nil { t.Fatal(err) } if len(rows) != 2 || rows[0].TotalPay != "12.5" || rows[0].NewRegisterCnt != 1 || rows[0].ActiveCnt != 1 || rows[0].PayCnt != 1 { t.Fatalf("任务没有实际生成双口径汇总: %+v", rows) } } var enabled int64 db.Table("sys_timed_tasks").Where("enabled=1").Count(&enabled) if enabled != 0 { t.Fatal("手动回跑不应启用自动计划") } }