Files
hfb_sys/backend/internal/modules/distribution/lifecycle_test.go
T
yml2213 550b42eb22 feat(distribution): 独立分销后台账号体系、多租户隔离与主站后台分销管理GUI
- 独立账号鉴权:分销后台支持独立登录页面、Bearer+Cookie 双模鉴权与租户资料接口
- 多租户隔离:分销商成员强制锁定自身站点数据范围,防止横向越权;平台超管享有全局视界
- 一键开通分销商:平台端支持一键录入站点、管理员账号、分配角色并原子化初始化佣金规则
- 主后台分销分组:在主站后台增加「分销管理」导航分组,提供站点管理、规则配置、订单监控与结算管理 GUI
- 门禁与联调:补充完整单测,全量代码门禁 check.sh 100% 通过,dev.sh 支持前后端与分销后台一键同启
2026-09-07 15:45:53 +08:00

192 lines
5.9 KiB
Go

package distribution
import (
"testing"
"time"
"hfb_sys/backend/internal/model"
)
func TestResolveOrderSourceTx(t *testing.T) {
db := distributionTestDB(t)
// 空 siteCode 正常返回 nil
site, rule, err := ResolveOrderSourceTx(db, "", 1, 1, time.Now())
if err != nil || site != nil || rule != nil {
t.Fatalf("expected nil for empty siteCode, got site=%v, rule=%v, err=%v", site, rule, err)
}
// 站点不存在
_, _, err = ResolveOrderSourceTx(db, "unknown_site", 1, 1, time.Now())
if err != ErrSiteNotFound {
t.Fatalf("expected ErrSiteNotFound, got %v", err)
}
// 创建正常站点
now := time.Now()
activeSite := model.DistributionSite{
SiteCode: "site_test",
SiteName: "测试分销站",
Domain: "test.dist.com",
Status: "active",
DisplayAccountEnabled: true,
}
if err := db.Create(&activeSite).Error; err != nil {
t.Fatal(err)
}
// 账号展示关系不存在
_, _, err = ResolveOrderSourceTx(db, "site_test", 10, 20, now)
if err != ErrAccountDisplayNotFound {
t.Fatalf("expected ErrAccountDisplayNotFound, got %v", err)
}
// 创建展示关系
accountDisplay := model.DistributionSiteAccount{
SiteID: activeSite.ID,
AccountID: 20,
ListingID: 10,
DisplayStatus: "enabled",
}
if err := db.Create(&accountDisplay).Error; err != nil {
t.Fatal(err)
}
// 规则不存在
_, _, err = ResolveOrderSourceTx(db, "site_test", 10, 20, now)
if err != ErrRuleNotFound {
t.Fatalf("expected ErrRuleNotFound, got %v", err)
}
// 创建有效规则
ruleVersion := model.DistributionRuleVersion{
SiteID: &activeSite.ID,
Mode: ModeTwoPartyDistributorReceive,
Version: 1,
Status: "active",
DistributorReceiveRatio: 3.5,
PlatformSellRatio: 4.0,
ServiceFeeValue: 2.5,
ServiceFeeUnit: ServiceFeeUnitFixedAmount,
PaymentFeeBPS: 50,
EffectiveFrom: now.Add(-1 * time.Hour),
}
if err := db.Create(&ruleVersion).Error; err != nil {
t.Fatal(err)
}
// 正常解析
foundSite, foundRule, err := ResolveOrderSourceTx(db, "site_test", 10, 20, now)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if foundSite.ID != activeSite.ID || foundRule.ID != ruleVersion.ID {
t.Fatalf("unexpected result: site=%v, rule=%v", foundSite, foundRule)
}
}
func TestSnapshotPaidOrderTxAndApplyRefundTx(t *testing.T) {
db := distributionTestDB(t)
now := time.Now()
site := model.DistributionSite{
SiteCode: "site_pay",
SiteName: "支付测试站",
Domain: "pay.dist.com",
Status: "active",
DisplayAccountEnabled: true,
TaxEnabled: true,
TaxRateBPS: 100,
}
if err := db.Create(&site).Error; err != nil {
t.Fatal(err)
}
rule := model.DistributionRuleVersion{
SiteID: &site.ID,
Mode: ModeTwoPartyDistributorReceive,
Version: 1,
Status: "active",
DistributorReceiveRatio: 3.5,
PlatformSellRatio: 4.0,
ServiceFeeValue: 2.5,
ServiceFeeUnit: ServiceFeeUnitFixedAmount,
PaymentFeeBPS: 50,
TaxEnabled: true,
TaxRateBPS: 100,
EffectiveFrom: now.Add(-1 * time.Hour),
}
if err := db.Create(&rule).Error; err != nil {
t.Fatal(err)
}
order := model.RentalOrder{
OrderNo: "ORD-DIST-1",
ListingID: 101,
AccountID: 201,
OwnerID: 1,
RenterID: 2,
Status: "renting",
PlatformFeeCent: 300,
DistributionSiteID: &site.ID,
DistributionRuleVersionID: &rule.ID,
}
if err := db.Create(&order).Error; err != nil {
t.Fatal(err)
}
// 1. SnapshotPaidOrderTx
grossPaid := int64(10000)
if err := SnapshotPaidOrderTx(db, order.ID, grossPaid); err != nil {
t.Fatalf("SnapshotPaidOrderTx failed: %v", err)
}
var financial model.DistributionOrderFinancial
if err := db.Where("order_id = ?", order.ID).First(&financial).Error; err != nil {
t.Fatalf("find financial failed: %v", err)
}
if financial.GrossPaidAmountCent != grossPaid || financial.SiteID != site.ID {
t.Fatalf("unexpected financial snapshot: %+v", financial)
}
var parties []model.DistributionOrderParty
if err := db.Where("order_id = ?", order.ID).Find(&parties).Error; err != nil || len(parties) != 2 {
t.Fatalf("unexpected parties count: len=%d, err=%v", len(parties), err)
}
// 幂等写入测试:重复调用不报错且不重复追加 party
if err := SnapshotPaidOrderTx(db, order.ID, grossPaid); err != nil {
t.Fatalf("idempotent SnapshotPaidOrderTx failed: %v", err)
}
var partiesAfter []model.DistributionOrderParty
if err := db.Where("order_id = ?", order.ID).Find(&partiesAfter).Error; err != nil || len(partiesAfter) != 2 {
t.Fatalf("idempotency broken: len=%d", len(partiesAfter))
}
// 2. ApplyRefundTx
// 非法金额
if err := ApplyRefundTx(db, order.ID, -10); err != ErrInvalidAmount {
t.Fatalf("expected ErrInvalidAmount for negative refund, got %v", err)
}
if err := ApplyRefundTx(db, order.ID, grossPaid+100); err != ErrInvalidAmount {
t.Fatalf("expected ErrInvalidAmount for excessive refund, got %v", err)
}
// 合法退款
refundAmount := int64(2000)
if err := ApplyRefundTx(db, order.ID, refundAmount); err != nil {
t.Fatalf("ApplyRefundTx failed: %v", err)
}
var updatedFinancial model.DistributionOrderFinancial
if err := db.Where("order_id = ?", order.ID).First(&updatedFinancial).Error; err != nil {
t.Fatalf("find updated financial failed: %v", err)
}
if updatedFinancial.RefundAmountCent != refundAmount {
t.Fatalf("expected refund amount %d, got %d", refundAmount, updatedFinancial.RefundAmountCent)
}
if updatedFinancial.NetPaidAmountCent != grossPaid-refundAmount {
t.Fatalf("expected net paid %d, got %d", grossPaid-refundAmount, updatedFinancial.NetPaidAmountCent)
}
}