- 修复 FindOrder 在收号站模式(two_party_distributor_receive / three_party)下的 403 权限判定,放行参与方站点成员并精准计算收益 - 串联 distribution_site_payment_channels 至 payment.Start 核心支付发起流程,分销订单优先使用绑定的专属商户通道 - 修复数据大盘 GetDashboardAnalytics 趋势中 ReceivedCount 恒为 0 的问题,并对齐今日订单与 GMV 统计 - 修复 dispute/arbitration.go 仲裁完成时向分销租客发放成长积分的泄漏风险 - 提现管理新增 distribution_site_name/distribution_site_code 字段并在后台列表与详情弹窗中区分展示分销站 - GetPublicSiteInfo 增加 X-Distribution-Site-Code 请求头兜底支持
211 lines
6.0 KiB
Go
211 lines
6.0 KiB
Go
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())
|
|
}
|
|
}
|