package distribution import ( "testing" "hfb_sys/backend/internal/model" "gorm.io/gorm" ) // TestEnsureListingShelvedTxAutoShelf 覆盖「所有站点共用同一套已审核货源」的自动铺货语义: // 开启自动展示的站点自动上架、人工下架不被覆盖、系统下架可恢复、重复调用幂等, // 并且铺货后的商品能在分站前台公开列表里被搜到。 func TestEnsureListingShelvedTxAutoShelf(t *testing.T) { db := distributionTestDB(t) repo := NewRepository(db) autoSite, err := repo.CreateSite(t.Context(), CreateSiteRequest{ SiteCode: "auto_shelf_on", SiteName: "自动铺货站", Domain: "auto-on.hfb.local", }) if err != nil { t.Fatal(err) } if !autoSite.DisplayAccountEnabled { t.Fatalf("expected display_account_enabled true, got %+v", autoSite) } // 关闭自动展示:建站时显式传 false 必须真正落库,否则自动铺货会铺到站长明确排除的站点。 displayOff := false manualSite, err := repo.CreateSite(t.Context(), CreateSiteRequest{ SiteCode: "auto_shelf_off", SiteName: "手动铺货站", Domain: "auto-off.hfb.local", DisplayAccountEnabled: &displayOff, }) if err != nil { t.Fatal(err) } if manualSite.DisplayAccountEnabled { t.Fatalf("display_account_enabled=false 应在建站时生效, got %+v", manualSite) } account := model.GameAccount{ Title: "自动铺货商品", GameName: "delta_force", ServerRegion: "QQ", LoginPlatform: "PC", RankLevel: "王者", HafCoinAmount: 10_000_000, Status: "published", } if err := db.Create(&account).Error; err != nil { t.Fatal(err) } listing := model.RentalListing{ ListingNo: "L-AUTO-SHELF-1", AccountID: account.ID, OwnerID: 1, PriceCent: 5000, DepositAmountCent: 10000, Status: "published", ReviewStatus: "approved", } if err := db.Create(&listing).Error; err != nil { t.Fatal(err) } // 1. 只铺到开启自动展示的站点,加价保持 0(不改变主站原价) if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil { t.Fatalf("EnsureListingShelvedTx failed: %v", err) } rows := loadShelfRows(t, db, listing.ID) if len(rows) != 1 { t.Fatalf("expected 1 shelf row for auto display site, got %+v", rows) } if rows[0].SiteID != autoSite.ID || rows[0].DisplayStatus != "enabled" || rows[0].ShelfRatioDelta != 0 { t.Fatalf("unexpected shelf row: %+v", rows[0]) } // 2. 幂等:重复铺货不产生重复行 if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil { t.Fatalf("EnsureListingShelvedTx repeat failed: %v", err) } if rows = loadShelfRows(t, db, listing.ID); len(rows) != 1 { t.Fatalf("auto shelf must be idempotent, got %+v", rows) } // 3. 站长人工下架是选品决策,自动铺货不得覆盖 if _, err := repo.UpdateAccountDisplay(t.Context(), 0, rows[0].ID, "disabled"); err != nil { t.Fatalf("UpdateAccountDisplay failed: %v", err) } if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil { t.Fatalf("EnsureListingShelvedTx after operator disable failed: %v", err) } rows = loadShelfRows(t, db, listing.ID) if rows[0].DisplayStatus != "disabled" || rows[0].DisplayDisabledBy != displayDisabledByOperator { t.Fatalf("operator disable must be preserved: %+v", rows[0]) } // 4. 系统下架(含历史无来源标记)可被自动铺货恢复 if err := db.Model(&model.DistributionSiteAccount{}).Where("id = ?", rows[0].ID). Updates(map[string]any{"display_status": "disabled", "display_disabled_by": displayDisabledBySystem}).Error; err != nil { t.Fatal(err) } if err := repo.EnsureListingShelvedTx(db, listing.ID, account.ID); err != nil { t.Fatalf("EnsureListingShelvedTx after system disable failed: %v", err) } rows = loadShelfRows(t, db, listing.ID) if rows[0].DisplayStatus != "enabled" || rows[0].DisplayDisabledBy != "" { t.Fatalf("system disable must be recovered: %+v", rows[0]) } // 5. 铺货结果能在分站前台公开列表看到;未开启自动展示的站点看不到 page, err := repo.ListPublicSiteListings(t.Context(), autoSite.SiteCode, 1, 20) if err != nil { t.Fatalf("ListPublicSiteListings failed: %v", err) } if page.Total != 1 || len(page.Items) != 1 || page.Items[0].ListingID != listing.ID { t.Fatalf("auto display site should expose the listing, got %+v", page) } pageOff, err := repo.ListPublicSiteListings(t.Context(), manualSite.SiteCode, 1, 20) if err != nil { t.Fatalf("ListPublicSiteListings manual failed: %v", err) } if pageOff.Total != 0 || len(pageOff.Items) != 0 { t.Fatalf("display_account_enabled=false site must not expose listings, got %+v", pageOff) } } func loadShelfRows(t *testing.T, db *gorm.DB, listingID uint64) []model.DistributionSiteAccount { t.Helper() var rows []model.DistributionSiteAccount if err := db.Where("listing_id = ?", listingID).Find(&rows).Error; err != nil { t.Fatal(err) } return rows } // TestCreateSitePersistsExplicitZeroFlags 回归:模型上带 default 标签的字段在显式传 // false/0 时不能被 GORM 忽略并回落数据库默认值,否则「不允许自动展示」会静默变成开启, // 从而让自动铺货铺到站长明确排除的站点。 func TestCreateSitePersistsExplicitZeroFlags(t *testing.T) { db := distributionTestDB(t) repo := NewRepository(db) displayOff := false zeroHoldDays := 0 dto, err := repo.CreateSite(t.Context(), CreateSiteRequest{ SiteCode: "explicit_zero_site", SiteName: "显式零值站点", Domain: "explicit-zero.hfb.local", DisplayAccountEnabled: &displayOff, SettlementHoldDays: &zeroHoldDays, }) if err != nil { t.Fatalf("CreateSite failed: %v", err) } if dto.DisplayAccountEnabled || dto.SettlementHoldDays != 0 { t.Fatalf("DTO 未反映显式零值: enabled=%v hold=%d", dto.DisplayAccountEnabled, dto.SettlementHoldDays) } var site model.DistributionSite if err := db.Where("site_code = ?", "explicit_zero_site").First(&site).Error; err != nil { t.Fatal(err) } if site.DisplayAccountEnabled { t.Fatalf("display_account_enabled 显式 false 未落库") } if site.SettlementHoldDays != 0 { t.Fatalf("settlement_hold_days 显式 0 未落库, got %d", site.SettlementHoldDays) } }