175 lines
5.8 KiB
Go
175 lines
5.8 KiB
Go
//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))
|
||
}
|
||
}
|