//go:build integration // 集成测试:文章管理混合列表必须跨 brand_runways / street_snaps 统一排序与过滤。 // // 运行(库需先就绪,见 dedup_integration_test.go 顶部说明): // // go test -tags integration ./internal/repository/ -run TestAdminMixed -v package repository import ( "testing" "gorm.io/gorm" ) // seedMixedRecords 各插一条走秀(已发布)与街拍(待审),返回它们的内部 id。 // 用测试专用 city / season_code 隔离,避免与库里既有行撞唯一实体键。 func seedMixedRecords(t *testing.T, db *gorm.DB) (runwayID, streetID uint32) { t.Helper() if err := db.Raw(` INSERT INTO brand_runways (brand_id, title_en, image_count, status, is_deleted, year, season_code, collection_type, created_at) VALUES (999999, 'AdminRepoTestRunway', 3, ?, 0, 2099, 'ZZZ', 'test', 1800000000) RETURNING id`, "published").Scan(&runwayID).Error; err != nil { t.Fatalf("插入走秀种子失败: %v", err) } if err := db.Raw(` INSERT INTO street_snaps (city, title, image_count, status, is_deleted, year, created_at) VALUES ('TestCityZZZ', 'AdminRepoTestStreet', 2, ?, 0, 2099, 1800000000) RETURNING id`, "pending").Scan(&streetID).Error; err != nil { t.Fatalf("插入街拍种子失败: %v", err) } t.Cleanup(func() { _ = db.Exec("DELETE FROM brand_runways WHERE id = ?", runwayID).Error _ = db.Exec("DELETE FROM street_snaps WHERE id = ?", streetID).Error }) return runwayID, streetID } // TestAdminMixedListFilterAndOrder 验证: // 1. 全部视图同时返回走秀与街拍; // 2. 待审核视图只返回 pending 的街拍,不含已发布的走秀; // 3. 排序为「pending 优先、其次 id 倒序」—— 任一 pending 都排在所有 published 之前。 func TestAdminMixedListFilterAndOrder(t *testing.T) { db := testDB(t) runwayID, streetID := seedMixedRecords(t, db) repo := NewAdminRepository(db) all, total, err := repo.ListMixed(t.Context(), "", "", 1, 100) if err != nil { t.Fatalf("ListMixed 全部失败: %v", err) } if total < 2 { t.Fatalf("全部视图至少应含 2 条种子,实际 total=%d", total) } var sawRunway, sawStreet bool for _, c := range all { if c.Kind == "runway" && c.ID == runwayID { sawRunway = true } if c.Kind == "street" && c.ID == streetID { sawStreet = true } } if !sawRunway || !sawStreet { t.Fatalf("全部视图应同时含走秀与街拍: runway=%v street=%v", sawRunway, sawStreet) } // 待审核过滤:街拍(pending) 出现,走秀(published) 不出现。 pending, _, err := repo.ListMixed(t.Context(), "pending", "", 1, 100) if err != nil { t.Fatalf("ListMixed pending 失败: %v", err) } var pendingHasStreet, pendingHasRunway bool for _, c := range pending { if c.Kind == "street" && c.ID == streetID { pendingHasStreet = true } if c.Kind == "runway" && c.ID == runwayID { pendingHasRunway = true } } if !pendingHasStreet { t.Fatalf("待审核视图应含待审街拍") } if pendingHasRunway { t.Fatalf("待审核视图不应含已发布走秀") } // 排序:任一 pending 必须排在所有 published 之前。 var maxPendingIdx, minPublishedIdx = -1, 1 << 30 for i, c := range all { if c.Status == "pending" { if i > maxPendingIdx { maxPendingIdx = i } } else if c.Status == "published" { if i < minPublishedIdx { minPublishedIdx = i } } } if maxPendingIdx >= 0 && minPublishedIdx < 1<<30 && maxPendingIdx > minPublishedIdx { t.Fatalf("排序错误:存在 published 排在 pending 之前 (pending@%d, published@%d)", maxPendingIdx, minPublishedIdx) } // CountMixed 与列表口径一致。 if n, err := repo.CountMixed(t.Context(), "pending", ""); err != nil || n < 1 { t.Fatalf("CountMixed(pending) 应 >=1,实际 n=%d err=%v", n, err) } } // TestAdminMixedKindFilter 验证 kind 筛选:只取走秀 / 只取街拍 / 全部。 func TestAdminMixedKindFilter(t *testing.T) { db := testDB(t) applyMigration(t, db, "2026-09-22-01-single-table-publish.sql") repo := NewAdminRepository(db) ctx := t.Context() // 直接插入各一条,用独特 city / title 隔离唯一实体键。 var runwayID, streetID uint32 if err := db.Raw(` INSERT INTO brand_runways (brand_id, title_en, image_count, status, is_deleted, year, season_code, collection_type, created_at) VALUES (999999, 'KindFilterRunway', 1, 'published', 0, 2097, 'KFZ', 'test', 1800000000) RETURNING id`, ).Scan(&runwayID).Error; err != nil { t.Fatalf("插入走秀种子失败: %v", err) } if err := db.Raw(` INSERT INTO street_snaps (city, title, image_count, status, is_deleted, year, created_at) VALUES ('KindFilterCity', 'KindFilterStreet', 1, 'published', 0, 2097, 1800000000) RETURNING id`, ).Scan(&streetID).Error; err != nil { t.Fatalf("插入街拍种子失败: %v", err) } t.Cleanup(func() { _ = db.Exec("DELETE FROM brand_runways WHERE id = ?", runwayID).Error _ = db.Exec("DELETE FROM street_snaps WHERE id = ?", streetID).Error }) runwayOnly, _, err := repo.ListMixed(ctx, "", "runway", 1, 50) if err != nil { t.Fatalf("kind=runway 失败: %v", err) } for _, c := range runwayOnly { if c.Kind != "runway" { t.Fatalf("kind=runway 不应含 %s", c.Kind) } } if len(runwayOnly) == 0 { t.Fatalf("kind=runway 至少应含 1 条") } streetOnly, _, err := repo.ListMixed(ctx, "", "street", 1, 50) if err != nil { t.Fatalf("kind=street 失败: %v", err) } for _, c := range streetOnly { if c.Kind != "street" { t.Fatalf("kind=street 不应含 %s", c.Kind) } } if len(streetOnly) == 0 { t.Fatalf("kind=street 至少应含 1 条") } all, _, err := repo.ListMixed(ctx, "", "", 1, 50) if err != nil { t.Fatalf("kind='' 失败: %v", err) } if len(all) != len(runwayOnly)+len(streetOnly) { t.Fatalf("全部应等于两类型之和:all=%d runway=%d street=%d", len(all), len(runwayOnly), len(streetOnly)) } }