测试库不再使用 SQLite 内存库,改为每个测试包独立的 MySQL 库, 表结构由 backend/migrations 的真实迁移建立。 基础设施 - 新增 internal/database/testdb.go:NewTestDB() 保持原签名(28 处调用零改动), 内部连接 MySQL 测试库,首次调用重建库并执行全部真实迁移,之后每次调用清空全表 - 库名 = 测试库基础名 + 包目录名,同包共享、跨包隔离,使 go test ./... 可并发执行 (共用一库时各包会互相 DROP/CREATE 导致 database exists) - 新增 NewDryRunDB():MySQL 方言 + DryRun,供断言 SQL 形态的 5 处测试使用 - 新增 AutoMigrateIfMissing():只对不存在的表 AutoMigrate, 避免改动迁移定义的索引与约束 - 新增 scripts/prepare-test-db.sh:建库与通配授权,可重复执行 - 33 处 sqlite.Open 全部替换,26 个测试文件改用 MySQL - 删除 46 处手写 SQLite 方言 DDL(AUTOINCREMENT / INTEGER PRIMARY KEY / TEXT), 表统一由真实迁移建立 - go.mod 移除 gorm.io/driver/sqlite 与 mattn/go-sqlite3 - 删除生产代码里 distribution/repository.go 的 sqlite 方言分支(strftime) 对齐环境后暴露并修复的真实问题 - DATE(col) 在 MySQL 返回 DATE 类型,使 Date string 字段变成 RFC3339 时间串, 大盘日期会带出时间部分;28 处改为 DATE_FORMAT(col, '%Y-%m-%d') - 系统自动创建分销结算批次时 created_by 写入 0,违反指向 admin_users 的外键, 生产会直接插入失败;改为写 NULL(新增 adminRef helper) - MySQL DATETIME 仅到秒,静默截断 Go 侧写入的时间, 导致租用时长计算与结算时点回读比对偏差; rental_orders 的 9 个业务时间列提到 DATETIME(6) - 测试数据补齐严格模式要求的 NOT NULL 列、修正超出列长的取值、补齐外键父记录 迁移 - 000065 的 ALTER TABLE rental_orders 一并完成业务时间列精度调整, feature 侧不再单开迁移文件 脚本与文档 - check.sh / check-backend-coverage.sh 去掉 -p 1 串行与 SQLite 相关注释 - 更新 docs/Repository层测试补充总结.md 的测试运行说明
555 lines
18 KiB
Go
555 lines
18 KiB
Go
package order
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/database"
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/internal/modules/rentergrowth"
|
|
"hfb_sys/backend/internal/timeutil"
|
|
|
|
"gorm.io/datatypes"
|
|
)
|
|
|
|
func pureCoinDiscountOrder() model.RentalOrder {
|
|
return model.RentalOrder{
|
|
RentAmountCent: 38037,
|
|
OwnerRentAmountCent: 35300,
|
|
DepositAmountCent: 15000,
|
|
RentOriginalAmountCent: 38300,
|
|
RentDiscountAmountCent: 263,
|
|
PureCoinOriginalAmountCent: 26300,
|
|
ExtraItemOriginalAmountCent: 12000,
|
|
RenterDiscountBps: 9900,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 263,
|
|
"seller_coin_base_price": 233,
|
|
"consumable_price": 120
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementUsesOnlyActualPureCoinForDiscount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
consumableAmountCent int64
|
|
coinConsumedM float64
|
|
wantPureCoinCent int64
|
|
wantDiscountCent int64
|
|
wantActualRentCent int64
|
|
wantOvershootCent int64
|
|
}{
|
|
{
|
|
name: "全量纯币和额外物品",
|
|
consumableAmountCent: 12000,
|
|
coinConsumedM: 100,
|
|
wantPureCoinCent: 26300,
|
|
wantDiscountCent: 263,
|
|
wantActualRentCent: 38037,
|
|
wantOvershootCent: 0,
|
|
},
|
|
{
|
|
name: "部分纯币和全量额外物品",
|
|
consumableAmountCent: 12000,
|
|
coinConsumedM: 50,
|
|
wantPureCoinCent: 13150,
|
|
wantDiscountCent: 132,
|
|
wantActualRentCent: 25018,
|
|
wantOvershootCent: 0,
|
|
},
|
|
{
|
|
name: "只使用额外物品",
|
|
consumableAmountCent: 12000,
|
|
coinConsumedM: 0,
|
|
wantPureCoinCent: 0,
|
|
wantDiscountCent: 0,
|
|
wantActualRentCent: 12000,
|
|
wantOvershootCent: 0,
|
|
},
|
|
{
|
|
name: "纯币打超仍参与优惠",
|
|
consumableAmountCent: 12000,
|
|
coinConsumedM: 110,
|
|
wantPureCoinCent: 28930,
|
|
wantDiscountCent: 290,
|
|
wantActualRentCent: 40640,
|
|
wantOvershootCent: 2603,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
settlement := calculateCheckoutSettlement(pureCoinDiscountOrder(), test.consumableAmountCent, test.coinConsumedM, 0)
|
|
if settlement.PureCoinAmountCent != test.wantPureCoinCent || settlement.PureCoinDiscountCent != test.wantDiscountCent {
|
|
t.Fatalf("纯币原价/优惠 = %d/%d, want %d/%d", settlement.PureCoinAmountCent, settlement.PureCoinDiscountCent, test.wantPureCoinCent, test.wantDiscountCent)
|
|
}
|
|
if settlement.ActualRentAmountCent != test.wantActualRentCent || settlement.OvershootAmountCent != test.wantOvershootCent {
|
|
t.Fatalf("实际租金/打超 = %d/%d, want %d/%d", settlement.ActualRentAmountCent, settlement.OvershootAmountCent, test.wantActualRentCent, test.wantOvershootCent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildOrderPricingDoesNotDiscountAllExtraItemOrder(t *testing.T) {
|
|
pricing := buildOrderPricing(
|
|
model.RentalListing{PriceCent: 12000},
|
|
model.GameAccount{AssetSummary: datatypes.JSON([]byte(`{
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 0,
|
|
"seller_coin_base_price": 0,
|
|
"consumable_price": 120,
|
|
"seller_total_price": 120
|
|
}
|
|
}`))},
|
|
)
|
|
if pricing.PureCoinAmountCent != 0 || pricing.ExtraItemAmountCent != 12000 || pricing.PureCoinPlatformFeeCent != 0 {
|
|
t.Fatalf("全额外物品订单纯币/额外物品/纯币差价 = %d/%d/%d, want 0/12000/0", pricing.PureCoinAmountCent, pricing.ExtraItemAmountCent, pricing.PureCoinPlatformFeeCent)
|
|
}
|
|
if discount := rentergrowth.CalculateDiscountCent(pricing.PureCoinAmountCent, pricing.PureCoinPlatformFeeCent, 9900); discount != 0 {
|
|
t.Fatalf("全额外物品订单优惠 = %d, want 0", discount)
|
|
}
|
|
}
|
|
|
|
func TestCheckoutRefundsSiteRent(t *testing.T) {
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 8000,
|
|
OwnerRentAmountCent: 8000,
|
|
PlatformFeeCent: 0,
|
|
BasePriceCent: 10000,
|
|
ShelfRatioDelta: 25,
|
|
DepositAmountCent: 1000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 80,
|
|
"seller_coin_base_price": 80,
|
|
"consumable_price": 0
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
settlement := calculateCheckoutSettlement(order, 0, 100, 0)
|
|
if settlement.RentRefundCent != 0 {
|
|
t.Fatalf("full consume must not refund rent, rent refund = %d", settlement.RentRefundCent)
|
|
}
|
|
zeroUse := calculateCheckoutSettlement(order, 0, 0, 0)
|
|
if zeroUse.RentRefundCent != 8000 {
|
|
t.Fatalf("unused listing refunds site rent, got %d want 8000", zeroUse.RentRefundCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) {
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 38300,
|
|
OwnerRentAmountCent: 35300,
|
|
DepositAmountCent: 15000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 263,
|
|
"seller_coin_base_price": 233,
|
|
"consumable_price": 120
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 700, 90, 0)
|
|
|
|
// 角精度:243.7 = roundMoney(236.7 + 7)
|
|
if settlement.ActualRentAmountCent != 24370 {
|
|
t.Fatalf("ActualRentAmountCent = %d, want 24370", settlement.ActualRentAmountCent)
|
|
}
|
|
// 角精度:216.7 = roundMoney(209.7 + 7) 卖家币价+消耗品
|
|
if settlement.OwnerRentIncomeCent != 21670 {
|
|
t.Fatalf("OwnerRentIncomeCent = %d, want 21670", settlement.OwnerRentIncomeCent)
|
|
}
|
|
// 角精度:27.0 = roundMoney(27) 平台费
|
|
if settlement.PlatformFeeCent != 2700 {
|
|
t.Fatalf("PlatformFeeCent = %d, want 2700", settlement.PlatformFeeCent)
|
|
}
|
|
// 角精度:139.3 = roundMoney(383 - 243.7)
|
|
if settlement.RentRefundCent != 13930 {
|
|
t.Fatalf("RentRefundCent = %d, want 13930", settlement.RentRefundCent)
|
|
}
|
|
if settlement.DepositRefundCent != 15000 {
|
|
t.Fatalf("DepositRefundCent = %d, want 15000", settlement.DepositRefundCent)
|
|
}
|
|
// 角精度:289.3 = roundMoney(139.3 + 150)
|
|
if settlement.RenterRefundCent != 28930 {
|
|
t.Fatalf("RenterRefundCent = %d, want 28930", settlement.RenterRefundCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementUsesBuyerAndSellerRatiosSeparately(t *testing.T) {
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 38300,
|
|
OwnerRentAmountCent: 35300,
|
|
DepositAmountCent: 15000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 263,
|
|
"seller_coin_base_price": 233,
|
|
"consumable_price": 120
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 0, 50, 0)
|
|
|
|
// 角精度:131.5 = roundMoney(131.5)
|
|
if settlement.ActualRentAmountCent != 13150 {
|
|
t.Fatalf("租客侧实际租金 = %d, want 13150", settlement.ActualRentAmountCent)
|
|
}
|
|
// 角精度:116.5 = roundMoney(116.5)
|
|
if settlement.OwnerRentIncomeCent != 11650 {
|
|
t.Fatalf("卖家侧租金收入 = %d, want 11650", settlement.OwnerRentIncomeCent)
|
|
}
|
|
// 角精度:15.0 = roundMoney(15)
|
|
if settlement.PlatformFeeCent != 1500 {
|
|
t.Fatalf("平台差价 = %d, want 1500", settlement.PlatformFeeCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) {
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 38300,
|
|
OwnerRentAmountCent: 35300,
|
|
DepositAmountCent: 15000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 263,
|
|
"seller_coin_base_price": 233,
|
|
"consumable_price": 120
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 12000, 100, 3000)
|
|
|
|
if settlement.ActualRentAmountCent != 38300 {
|
|
t.Fatalf("ActualRentAmountCent = %d, want 38300", settlement.ActualRentAmountCent)
|
|
}
|
|
if settlement.OwnerRentIncomeCent != 35300 {
|
|
t.Fatalf("OwnerRentIncomeCent = %d, want 35300", settlement.OwnerRentIncomeCent)
|
|
}
|
|
if settlement.DepositCompensationCent != 3000 {
|
|
t.Fatalf("DepositCompensationCent = %d, want 3000", settlement.DepositCompensationCent)
|
|
}
|
|
if settlement.OwnerIncomeCent != 38300 {
|
|
t.Fatalf("OwnerIncomeCent = %d, want 38300", settlement.OwnerIncomeCent)
|
|
}
|
|
if settlement.RenterRefundCent != 12000 {
|
|
t.Fatalf("RenterRefundCent = %d, want 12000", settlement.RenterRefundCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementAllowsOvershootFromDeposit(t *testing.T) {
|
|
// 发布 100M,实际消耗 180M;按基价线性外推,打超从押金扣
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 26300, // 仅币预收(无消耗品)
|
|
OwnerRentAmountCent: 23300,
|
|
DepositAmountCent: 30000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 263,
|
|
"seller_coin_base_price": 233,
|
|
"consumable_price": 0
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
|
|
// 180/100 * 26300 = 47340
|
|
if settlement.ActualRentAmountCent != 47340 {
|
|
t.Fatalf("ActualRentAmountCent = %d, want 47340", settlement.ActualRentAmountCent)
|
|
}
|
|
if settlement.OwnerRentIncomeCent != 41940 { // 180/100 * 23300
|
|
t.Fatalf("OwnerRentIncomeCent = %d, want 41940", settlement.OwnerRentIncomeCent)
|
|
}
|
|
if settlement.OvershootAmountCent != 21040 { // 47340 - 26300
|
|
t.Fatalf("OvershootAmountCent = %d, want 21040", settlement.OvershootAmountCent)
|
|
}
|
|
if settlement.ShortfallCent != 0 {
|
|
t.Fatalf("ShortfallCent = %d, want 0", settlement.ShortfallCent)
|
|
}
|
|
if settlement.RentRefundCent != 0 {
|
|
t.Fatalf("RentRefundCent = %d, want 0", settlement.RentRefundCent)
|
|
}
|
|
// 押金 30000 - 打超 21040 = 8960
|
|
if settlement.DepositRefundCent != 8960 {
|
|
t.Fatalf("DepositRefundCent = %d, want 8960", settlement.DepositRefundCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementShortfallWhenDepositInsufficient(t *testing.T) {
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 26300,
|
|
OwnerRentAmountCent: 23300,
|
|
DepositAmountCent: 5000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_coin_base_price": 263,
|
|
"seller_coin_base_price": 233,
|
|
"consumable_price": 0
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
|
|
// overshoot 21040, deposit 5000 → shortfall 16040
|
|
if settlement.OvershootAmountCent != 21040 {
|
|
t.Fatalf("OvershootAmountCent = %d, want 21040", settlement.OvershootAmountCent)
|
|
}
|
|
if settlement.ShortfallCent != 16040 {
|
|
t.Fatalf("ShortfallCent = %d, want 16040", settlement.ShortfallCent)
|
|
}
|
|
if settlement.DepositRefundCent != 0 {
|
|
t.Fatalf("DepositRefundCent = %d, want 0", settlement.DepositRefundCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementUsesExplicitRatiosForActualConsume(t *testing.T) {
|
|
// buyer 1:40, seller 1:45;消耗 180M
|
|
// 租客: 180*100/40 = 450 元;号主: 180*100/45 = 400 元
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 25000, // 100M 预收
|
|
OwnerRentAmountCent: 22222,
|
|
DepositAmountCent: 50000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 100000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_ratio": 40,
|
|
"seller_ratio": 45,
|
|
"buyer_coin_base_price": 250,
|
|
"seller_coin_base_price": 222.22,
|
|
"consumable_price": 0
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
|
|
if settlement.ActualRentAmountCent != 45000 {
|
|
t.Fatalf("ActualRentAmountCent = %d, want 45000", settlement.ActualRentAmountCent)
|
|
}
|
|
if settlement.OwnerRentIncomeCent != 40000 {
|
|
t.Fatalf("OwnerRentIncomeCent = %d, want 40000", settlement.OwnerRentIncomeCent)
|
|
}
|
|
if settlement.PlatformFeeCent != 5000 {
|
|
t.Fatalf("PlatformFeeCent = %d, want 5000", settlement.PlatformFeeCent)
|
|
}
|
|
if settlement.OvershootAmountCent != 20000 {
|
|
t.Fatalf("OvershootAmountCent = %d, want 20000", settlement.OvershootAmountCent)
|
|
}
|
|
if settlement.ShortfallCent != 0 {
|
|
t.Fatalf("ShortfallCent = %d, want 0", settlement.ShortfallCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCheckoutSettlementCapsFullUseRatioRounding(t *testing.T) {
|
|
// 发布量刚好用完时,比例换算的四舍五入不能把线下待打款抬高。
|
|
order := model.RentalOrder{
|
|
RentAmountCent: 37210,
|
|
OwnerRentAmountCent: 34000,
|
|
PlatformFeeCent: 3210,
|
|
DepositAmountCent: 50000,
|
|
AccountSnapshot: datatypes.JSON([]byte(`{
|
|
"haf_coin_amount": 160000000,
|
|
"asset_summary": {
|
|
"price_breakdown": {
|
|
"buyer_ratio": 43,
|
|
"seller_ratio": 47,
|
|
"buyer_coin_base_price": 372.1,
|
|
"seller_coin_base_price": 340,
|
|
"consumable_price": 0
|
|
}
|
|
}
|
|
}`)),
|
|
}
|
|
|
|
settlement := calculateCheckoutSettlement(order, 0, 160, 0)
|
|
if settlement.ActualRentAmountCent != order.RentAmountCent {
|
|
t.Fatalf("ActualRentAmountCent = %d, want %d", settlement.ActualRentAmountCent, order.RentAmountCent)
|
|
}
|
|
if settlement.OwnerRentIncomeCent != order.OwnerRentAmountCent {
|
|
t.Fatalf("OwnerRentIncomeCent = %d, want %d", settlement.OwnerRentIncomeCent, order.OwnerRentAmountCent)
|
|
}
|
|
if settlement.PlatformFeeCent != order.PlatformFeeCent {
|
|
t.Fatalf("PlatformFeeCent = %d, want %d", settlement.PlatformFeeCent, order.PlatformFeeCent)
|
|
}
|
|
if settlement.OvershootAmountCent != 0 {
|
|
t.Fatalf("OvershootAmountCent = %d, want 0", settlement.OvershootAmountCent)
|
|
}
|
|
}
|
|
|
|
func TestCalculateDepositWaiverUsesSharedRemainingQuota(t *testing.T) {
|
|
paid, waived := calculateDepositWaiver(500, 300, 0)
|
|
if paid != 200 || waived != 300 {
|
|
t.Fatalf("paid = %d, waived = %d, want 200, 300", paid, waived)
|
|
}
|
|
|
|
paid, waived = calculateDepositWaiver(500, 300, 200)
|
|
if paid != 400 || waived != 100 {
|
|
t.Fatalf("paid = %d, waived = %d, want 400, 100", paid, waived)
|
|
}
|
|
|
|
paid, waived = calculateDepositWaiver(500, 300, 300)
|
|
if paid != 500 || waived != 0 {
|
|
t.Fatalf("paid = %d, waived = %d, want 500, 0", paid, waived)
|
|
}
|
|
}
|
|
|
|
func TestArchiveAssetsMovesListingOffline(t *testing.T) {
|
|
now := time.Now()
|
|
listing := model.RentalListing{
|
|
Status: listingStatusRented,
|
|
InTransaction: true,
|
|
PublishedAt: &now,
|
|
}
|
|
account := model.GameAccount{Status: accountStatusRented}
|
|
|
|
if err := archiveAssets(nil, &listing, &account); err != nil {
|
|
t.Fatalf("archiveAssets: %v", err)
|
|
}
|
|
|
|
if listing.Status != listingStatusOffline {
|
|
t.Fatalf("listing.Status = %q, want %q", listing.Status, listingStatusOffline)
|
|
}
|
|
if listing.InTransaction {
|
|
t.Fatal("listing.InTransaction = true, want false")
|
|
}
|
|
if listing.PublishedAt != nil {
|
|
t.Fatal("listing.PublishedAt is not nil")
|
|
}
|
|
if account.Status != accountStatusOffline {
|
|
t.Fatalf("account.Status = %q, want %q", account.Status, accountStatusOffline)
|
|
}
|
|
}
|
|
|
|
func TestCompleteAssetsMovesListingCompleted(t *testing.T) {
|
|
now := time.Now()
|
|
listing := model.RentalListing{
|
|
Status: listingStatusRented,
|
|
InTransaction: true,
|
|
PublishedAt: &now,
|
|
}
|
|
account := model.GameAccount{Status: accountStatusRented}
|
|
|
|
if err := completeAssets(nil, &listing, &account); err != nil {
|
|
t.Fatalf("completeAssets: %v", err)
|
|
}
|
|
|
|
if listing.Status != listingStatusCompleted {
|
|
t.Fatalf("listing.Status = %q, want %q", listing.Status, listingStatusCompleted)
|
|
}
|
|
if listing.InTransaction {
|
|
t.Fatal("listing.InTransaction = true, want false")
|
|
}
|
|
if listing.PublishedAt != nil {
|
|
t.Fatal("listing.PublishedAt is not nil")
|
|
}
|
|
if account.Status != accountStatusOffline {
|
|
t.Fatalf("account.Status = %q, want %q", account.Status, accountStatusOffline)
|
|
}
|
|
}
|
|
|
|
func TestNewOrderNoUsesShanghaiTimeWhenLocalIsUTC(t *testing.T) {
|
|
oldLocal := time.Local
|
|
time.Local = time.UTC
|
|
defer func() {
|
|
time.Local = oldLocal
|
|
}()
|
|
|
|
loc := timeutil.ShanghaiLocation()
|
|
before := time.Now().In(loc)
|
|
orderNo, err := newOrderNo()
|
|
after := time.Now().In(loc)
|
|
if err != nil {
|
|
t.Fatalf("newOrderNo() error = %v", err)
|
|
}
|
|
if len(orderNo) != len("RO20260603123456789") {
|
|
t.Fatalf("orderNo length = %d, want %d: %s", len(orderNo), len("RO20260603123456789"), orderNo)
|
|
}
|
|
parsed, err := time.ParseInLocation("20060102150405", orderNo[2:16], loc)
|
|
if err != nil {
|
|
t.Fatalf("parse order no time: %v", err)
|
|
}
|
|
if parsed.Before(before.Add(-2*time.Second)) || parsed.After(after.Add(2*time.Second)) {
|
|
t.Fatalf("order no time = %s, want between %s and %s", parsed, before, after)
|
|
}
|
|
}
|
|
|
|
func TestApplyAdminOrderFiltersSmartRouting(t *testing.T) {
|
|
db := database.NewDryRunDB()
|
|
|
|
tests := []struct {
|
|
name string
|
|
query AdminOrderQuery
|
|
wantSQLFrag string
|
|
}{
|
|
{
|
|
name: "11位手机号路由至users(phone)",
|
|
query: AdminOrderQuery{Keyword: "13800138000"},
|
|
wantSQLFrag: "owner.phone = ? OR renter.phone = ?",
|
|
},
|
|
{
|
|
name: "RO前缀订单号路由至rental_orders(order_no)",
|
|
query: AdminOrderQuery{Keyword: "RO202609051000"},
|
|
wantSQLFrag: "o.order_no = ?",
|
|
},
|
|
{
|
|
name: "12位时间计算的纯数字商品编号路由至rental_listings(listing_no)",
|
|
query: AdminOrderQuery{Keyword: "202609040184"},
|
|
wantSQLFrag: "l.listing_no = ?",
|
|
},
|
|
{
|
|
name: "L前缀商品编号路由至rental_listings(listing_no)",
|
|
query: AdminOrderQuery{Keyword: "L20260905100"},
|
|
wantSQLFrag: "l.listing_no = ?",
|
|
},
|
|
{
|
|
name: "短数字ID路由至主键及关联ID",
|
|
query: AdminOrderQuery{Keyword: "12345"},
|
|
wantSQLFrag: "o.id = ? OR o.listing_id = ? OR o.owner_id = ? OR o.renter_id = ?",
|
|
},
|
|
{
|
|
name: "普通文字退回多列LIKE模糊匹配",
|
|
query: AdminOrderQuery{Keyword: "测试账号"},
|
|
wantSQLFrag: "o.order_no LIKE ? OR l.listing_no LIKE ? OR a.title LIKE ? OR owner.phone LIKE ? OR renter.phone LIKE ?",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
stmt := applyAdminOrderFilters(db.Table("rental_orders AS o"), tt.query).Find(&[]struct{}{}).Statement
|
|
sql := stmt.SQL.String()
|
|
if !strings.Contains(sql, tt.wantSQLFrag) {
|
|
t.Fatalf("期望 SQL 包含 %q, 实际生成: %s", tt.wantSQLFrag, sql)
|
|
}
|
|
})
|
|
}
|
|
}
|