package main import ( "context" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "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/server/handler" "kra/internal/server/router" businessservice "kra/internal/service/business" ) type businessHTTPData struct{ db *gorm.DB } func (d businessHTTPData) DB() *gorm.DB { return d.db } type httpPlatformAccounts struct{} func (httpPlatformAccounts) FindByAccount(context.Context, string, string) (*businessbiz.PlatformAccount, error) { return &businessbiz.PlatformAccount{ID: 77, Status: 1}, nil } type httpPlatformPasswords struct{} func (httpPlatformPasswords) Verify(string, string) (bool, error) { return true, nil } func TestBusinessHTTPListWithSystemOnlyAndCompleteDatabase(t *testing.T) { db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "http.db")), &gorm.Config{}) if err != nil { t.Fatal(err) } sqlDB, err := db.DB() if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = sqlDB.Close() }) for _, step := range append(systemdata.Migrations(), businessdata.Migrations()...) { if err := step.Migrate(db); err != nil { t.Fatal(err) } } for _, statement := range []string{ "INSERT INTO sys_users(id,uuid,username,authority_id,level,id_path) VALUES (1,'admin','admin',888,0,'0'),(2,'child','child',901,1,'0'),(3,'empty','empty',903,3,'0,2')", "INSERT INTO sys_authorities(authority_id,authority_name) VALUES (901,'一级商务')", "INSERT INTO sys_user_bind_platform(user_id,source_db,platform_busier_id,status) VALUES (2,'lx',9,'0')", } { if err := db.Exec(statement).Error; err != nil { t.Fatal(err) } } provider := businessHTTPData{db} accountUsecase := businessbiz.NewAccountUsecase(businessdata.NewAccountRepo(provider)) accounts := businessservice.NewAccountService(accountUsecase) bindings := businessservice.NewBindingService(businessbiz.NewBindingUsecase(businessdata.NewBindingRepo(provider), accountUsecase, httpPlatformAccounts{}, httpPlatformPasswords{})) prefs := businessservice.NewUserPreferenceService(businessbiz.NewUserPreferenceUsecase(businessdata.NewUserPreferenceRepo(provider))) engine := gin.New() // 此契约测试固定已认证身份;真实登录另通过运行实例验证。 engine.Use(func(c *gin.Context) { actor := system.Actor{UserID: 1, AuthorityID: 888} if c.GetHeader("X-Test-Actor") == "3" { actor = system.Actor{UserID: 3, AuthorityID: 903} } c.Request = c.Request.WithContext(system.NewActorContext(c.Request.Context(), actor)) c.Next() }) router.RegisterBusiness(engine.Group(""), handler.NewBusiness(prefs, accounts, bindings)) promotes := handler.NewBusinessPromote(businessservice.NewBusinessPromoteService(businessbiz.NewBusinessPromoteUsecase(businessdata.NewBusinessPromoteRepo(provider), accountUsecase))) engine.GET("/sw/business/:userId/promotes", promotes.List) server := httptest.NewServer(engine) defer server.Close() requestList := func() map[string]any { t.Helper() req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL+"/sw/business/list?pageNum=1&pageSize=10", nil) response, err := server.Client().Do(req) if err != nil { t.Fatal(err) } defer response.Body.Close() var body map[string]any if err := json.NewDecoder(response.Body).Decode(&body); err != nil { t.Fatal(err) } if response.StatusCode != 200 || body["code"] != float64(0) { t.Fatalf("真实 HTTP 列表请求失败: %+v", body) } return body["data"].(map[string]any) } data := requestList() rows := data["rows"].([]any) if data["total"] != float64(3) || len(rows) != 3 { t.Fatalf("系统数据未完整返回: %+v", data) } child := rows[1].(map[string]any) if child["promoteCount"] != nil || child["bindCount"] != float64(1) { t.Fatalf("未接入统计不能伪装为零: %+v", child) } if rows[2].(map[string]any)["promoteCount"] != float64(0) { t.Fatal("无绑定账号的直属渠道应明确为零") } if err := db.Exec("CREATE TABLE tab_promote(id INTEGER,source_db TEXT,busier_id INTEGER,promote_level INTEGER,account TEXT)").Error; err != nil { t.Fatal(err) } if err := db.Exec("INSERT INTO tab_promote VALUES(1,'lx',9,1,'测试渠道'),(2,'lx',9,2,'下级渠道'),(1,'hw',9,1,'其他平台')").Error; err != nil { t.Fatal(err) } data = requestList() child = data["rows"].([]any)[1].(map[string]any) if child["promoteCount"] != float64(1) { t.Fatalf("完整数据应恢复真实且按平台隔离的渠道数: %+v", child) } call := func(method, path, body, actor string) map[string]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-Actor", actor) response, err := server.Client().Do(req) if err != nil { t.Fatal(err) } defer response.Body.Close() var value map[string]any if err := json.NewDecoder(response.Body).Decode(&value); err != nil { t.Fatal(err) } if value["code"] != float64(0) { t.Fatalf("%s %s 失败: %+v", method, path, value) } return value } created := call("POST", "/sw/business", `{"userName":"http_child","realName":"接口测试","password":"abc123","hireDate":"2026-11-01"}`, "")["data"].(map[string]any) channels := call("GET", "/sw/business/2/promotes", "", "")["data"].(map[string]any) if channels["total"] != float64(1) || channels["rows"].([]any)[0].(map[string]any)["promoteName"] != "测试渠道" { t.Fatalf("HTTP 旗下渠道范围错误: %+v", channels) } if created["userId"] != float64(4) || created["level"] != float64(1) || created["authorityId"] != float64(901) { t.Fatalf("HTTP 新增层级或角色不正确: %+v", created) } call("PUT", "/sw/business", `{"userId":4,"realName":"更新姓名"}`, "") updated := call("GET", "/sw/business/4", "", "")["data"].(map[string]any) if updated["realName"] != "更新姓名" || updated["status"] != "0" { t.Fatalf("HTTP 部分编辑不正确: %+v", updated) } call("PUT", "/sw/user/preference", `{"includeSubPromote":false,"idcardDedup":false}`, "") preference := call("GET", "/sw/user/preference", "", "")["data"].(map[string]any) if preference["includeSubPromote"] != false || preference["idcardDedup"] != false { t.Fatalf("HTTP 偏好关闭未保存: %+v", preference) } call("POST", "/sw/business/bindPlatform", `{"sourceDb":"lx","account":"sdk-test","password":"test-only"}`, "3") bound := call("GET", "/sw/business/bindPlatform", "", "3")["data"].(map[string]any) if bound["total"] != float64(1) || bound["rows"].([]any)[0].(map[string]any)["status"] != "3" { t.Fatalf("HTTP 绑定未进入待审核: %+v", bound) } call("DELETE", "/sw/business/bindPlatform", `{"sourceDb":"lx","platformBusierId":77}`, "3") bound = call("GET", "/sw/business/bindPlatform", "", "3")["data"].(map[string]any) if bound["total"] != float64(0) { t.Fatalf("HTTP 解绑未删除当前账号绑定: %+v", bound) } }