This commit is contained in:
toom1996
2026-09-25 11:31:52 +08:00
parent 4f4c11d508
commit 42f6316125
51 changed files with 4489 additions and 998 deletions

View File

@ -0,0 +1,174 @@
//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))
}
}