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) } }