package distribution import ( "context" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "hfb_sys/backend/internal/model" "hfb_sys/backend/internal/modules/auth" ) func TestOnboardSiteAndLoginFlow(t *testing.T) { db := distributionTestDB(t) repo := NewRepository(db) jwtMgr := auth.NewJWTManager("test_secret_32_bytes_long_keys_123") svc := NewService(repo, jwtMgr) ctx := context.Background() // 1. Onboard new site req := OnboardSiteRequest{ SiteCode: "gz_dist", SiteName: "广州分销站", Domain: "gz.distribution.com", AdminUsername: "gz_operator", AdminPassword: "Password123!", AdminNickname: "广州运营小李", MemberType: "operator", } resp, err := svc.OnboardSite(ctx, req) if err != nil { t.Fatalf("failed to onboard site: %v", err) } if resp.Site.SiteCode != "gz_dist" { t.Fatalf("expected SiteCode gz_dist, got %s", resp.Site.SiteCode) } if resp.Member.Username != "gz_operator" { t.Fatalf("expected Username gz_operator, got %s", resp.Member.Username) } if resp.Member.IsSuperadmin { t.Fatalf("expected IsSuperadmin=false for site member") } if resp.Member.SiteID == nil || *resp.Member.SiteID != resp.Site.ID { t.Fatalf("expected SiteID=%d, got %v", resp.Site.ID, resp.Member.SiteID) } if resp.Rule.DistributorReceiveRatio != 35 || resp.Rule.PlatformSellRatio != 40 || resp.Rule.ServiceFeeValue != 25 { t.Fatalf("expected default 35/40/25, got %+v", resp.Rule) } if resp.Rule.Status != "active" { t.Fatalf("onboard rules should be active by default, got %s", resp.Rule.Status) } // 2. Duplicate site code or domain check dupReq := req dupReq.AdminUsername = "gz_operator_2" if _, err := svc.OnboardSite(ctx, dupReq); err != ErrDuplicateSiteCodeOrDomain { t.Fatalf("expected ErrDuplicateSiteCodeOrDomain, got %v", err) } // Duplicate username check dupUserReq := req dupUserReq.SiteCode = "gz_dist_2" dupUserReq.Domain = "gz2.distribution.com" if _, err := svc.OnboardSite(ctx, dupUserReq); err != ErrDuplicateUsername { t.Fatalf("expected ErrDuplicateUsername, got %v", err) } // 3. Login with onboarded credentials loginResp, err := svc.Login(ctx, DistributionLoginRequest{ Username: "gz_operator", Password: "Password123!", }, "127.0.0.1") if err != nil { t.Fatalf("failed to login: %v", err) } if loginResp.Token == "" { t.Fatalf("expected token, got empty") } if loginResp.Member.SiteCode != "gz_dist" { t.Fatalf("expected site code gz_dist, got %s", loginResp.Member.SiteCode) } // 4. Login with wrong password _, err = svc.Login(ctx, DistributionLoginRequest{ Username: "gz_operator", Password: "wrong_password", }, "127.0.0.1") if err != ErrInvalidCredential { t.Fatalf("expected ErrInvalidCredential, got %v", err) } // 5. Login with non-existent user _, err = svc.Login(ctx, DistributionLoginRequest{ Username: "non_existent", Password: "Password123!", }, "127.0.0.1") if err != ErrInvalidCredential { t.Fatalf("expected ErrInvalidCredential, got %v", err) } // 6. Test disabled user if err := db.Model(&model.AdminUser{}).Where("username = ?", "gz_operator").Update("status", "disabled").Error; err != nil { t.Fatal(err) } _, err = svc.Login(ctx, DistributionLoginRequest{ Username: "gz_operator", Password: "Password123!", }, "127.0.0.1") if err != ErrAdminDisabled { t.Fatalf("expected ErrAdminDisabled, got %v", err) } hash, _ := bcrypt.GenerateFromPassword([]byte("SuperAdmin123!"), bcrypt.DefaultCost) plainAdmin := model.AdminUser{ Username: "plain_admin", PasswordHash: string(hash), Nickname: "无角色管理员", Status: "active", TokenVersion: 1, } if err := db.Create(&plainAdmin).Error; err != nil { t.Fatal(err) } plainLogin, err := svc.Login(ctx, DistributionLoginRequest{ Username: "plain_admin", Password: "SuperAdmin123!", }, "127.0.0.1") if err != nil { t.Fatalf("plain admin login failed: %v", err) } if plainLogin.Member.IsSuperadmin { t.Fatalf("admin without super_admin role or all_view must not be superadmin") } superRole := model.Role{Code: "super_admin", Name: "超级管理员"} if err := db.Create(&superRole).Error; err != nil { t.Fatal(err) } superAdmin := model.AdminUser{ Username: "platform_admin", PasswordHash: string(hash), Nickname: "平台超级管理员", Status: "active", TokenVersion: 1, } if err := db.Create(&superAdmin).Error; err != nil { t.Fatal(err) } if err := db.Create(&model.AdminUserRole{AdminID: superAdmin.ID, RoleID: superRole.ID}).Error; err != nil { t.Fatal(err) } superLoginResp, err := svc.Login(ctx, DistributionLoginRequest{ Username: "platform_admin", Password: "SuperAdmin123!", }, "127.0.0.1") if err != nil { t.Fatalf("superadmin login failed: %v", err) } if !superLoginResp.Member.IsSuperadmin { t.Fatalf("expected IsSuperadmin=true for platform superadmin") } if superLoginResp.Member.SiteID != nil { t.Fatalf("expected nil SiteID for superadmin, got %v", superLoginResp.Member.SiteID) } // 8. Test Me profile profile, err := svc.Me(ctx, superAdmin.ID) if err != nil { t.Fatalf("failed to get profile: %v", err) } if profile.Username != "platform_admin" || !profile.IsSuperadmin { t.Fatalf("unexpected profile: %#v", profile) } } func TestGetPublicSiteInfoHeaderFallback(t *testing.T) { db := distributionTestDB(t) repo := NewRepository(db) svc := NewService(repo, nil) handler := NewHandler(svc) site := model.DistributionSite{ SiteCode: "header_site", SiteName: "头部测试站", Domain: "header.example.com", Status: "active", } if err := db.Create(&site).Error; err != nil { t.Fatal(err) } gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) req, _ := http.NewRequest("GET", "/api/v1/distribution/public/site-info", nil) req.Header.Set("X-Distribution-Site-Code", "header_site") c.Request = req handler.GetPublicSiteInfo(c) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d, body: %s", w.Code, w.Body.String()) } }