//go:build integration // 集成测试:公开视图必须屏蔽未发布内容 —— 这是「单表发布模型」的核心保证。 // // 运行(库需先就绪,见 internal/repository/dedup_integration_test.go 顶部说明): // // go test -tags integration ./internal/repository/ -run TestPublicViews -v package repository import ( "context" "fmt" "os" "path/filepath" "testing" "fashionapi/internal/model" "gorm.io/gorm" ) // applyMigration 读取并执行 db/migrations 下的迁移文件。 // 迁移 SQL 因此每次测试都被真实跑一遍:语法错、视图列集漂移会立刻暴露。 // 迁移文件必须幂等,重复调用安全。 func applyMigration(t *testing.T, db *gorm.DB, name string) { t.Helper() path := filepath.Join("..", "..", "db", "migrations", name) sqlBytes, err := os.ReadFile(path) if err != nil { t.Fatalf("读取迁移失败 %s: %v", path, err) } if err := db.Exec(string(sqlBytes)).Error; err != nil { t.Fatalf("执行迁移失败 %s: %v", name, err) } } // seedRunway 插入一条指定状态的走秀记录 + 一张主图,返回记录 id。 // year 用测试专用值(入参),便于列表按年份隔离断言;created_at 取未来时间戳, // 保证不会被迁移里「存量行置已发布」的时间戳护栏误刷。 func seedRunway(t *testing.T, db *gorm.DB, status string, year uint16) uint32 { t.Helper() now := uint32(1_800_000_000) // 实体键必须非空且按 year 派生:2026-09-22-03 的唯一索引落库后,brand_id=0 且 // season_code / collection_type 均为空会让同一次运行里的多条种子行(乃至上次运行 // 未清理的遗留行)被当成「同一实体」而互相冲突,失败点还会伪装成迁移体检。 rw := model.BrandRunway{ TitleEn: "view-test", Year: year, Status: status, CreatedAt: now, UpdatedAt: now, SeasonCode: fmt.Sprintf("T%04d", year), CollectionType: "t", } if err := db.Create(&rw).Error; err != nil { t.Fatalf("插入走秀失败: %v", err) } img := model.BrandRunwayImage{ RunwayID: rw.ID, BrandID: 1, Image: "view-test.jpg", Name: "look", SortOrder: 1, CreatedAt: now, UpdatedAt: now, } if err := db.Create(&img).Error; err != nil { t.Fatalf("插入走秀图片失败: %v", err) } t.Cleanup(func() { db.Exec("DELETE FROM brand_runway_images WHERE runway_id = ?", rw.ID) db.Exec("DELETE FROM brand_runways WHERE id = ?", rw.ID) }) return rw.ID } // seedStreetSnap 插入一条指定状态的街拍记录 + 一张图,返回记录 id。 func seedStreetSnap(t *testing.T, db *gorm.DB, status string, year uint16) uint32 { t.Helper() now := uint32(1_800_000_000) snap := model.StreetSnap{Title: "view-test", City: "ViewTestCity", Year: year, Status: status, CreatedAt: now, UpdatedAt: now} if err := db.Create(&snap).Error; err != nil { t.Fatalf("插入街拍失败: %v", err) } img := model.StreetSnapImage{SnapID: snap.ID, Image: "view-test.jpg", Name: "look", SortOrder: 1, CreatedAt: now, UpdatedAt: now} if err := db.Create(&img).Error; err != nil { t.Fatalf("插入街拍图片失败: %v", err) } t.Cleanup(func() { db.Exec("DELETE FROM street_snap_images WHERE snap_id = ?", snap.ID) db.Exec("DELETE FROM street_snaps WHERE id = ?", snap.ID) }) return snap.ID } // TestPublicViewsHideUnpublished 走秀:pending / rejected 的记录与图片都不得出现在公开视图,published 才可见。 func TestPublicViewsHideUnpublished(t *testing.T) { db := testDB(t) applyMigration(t, db, "2026-09-22-01-single-table-publish.sql") ctx := context.Background() id := seedRunway(t, db, model.StatusPending, 1999) count := func() (records, images int64) { db.WithContext(ctx).Table("public_brand_runways").Where("id = ?", id).Count(&records) db.WithContext(ctx).Table("public_brand_runway_images").Where("runway_id = ?", id).Count(&images) return } if r, i := count(); r != 0 || i != 0 { t.Fatalf("pending 不应出现在公开视图,实际 records=%d images=%d", r, i) } if err := db.Model(&model.BrandRunway{}).Where("id = ?", id).Update("status", model.StatusPublished).Error; err != nil { t.Fatalf("置为已发布失败: %v", err) } if r, i := count(); r != 1 || i != 1 { t.Fatalf("published 应出现在公开视图,实际 records=%d images=%d", r, i) } if err := db.Model(&model.BrandRunway{}).Where("id = ?", id).Update("status", model.StatusRejected).Error; err != nil { t.Fatalf("置为已驳回失败: %v", err) } if r, i := count(); r != 0 || i != 0 { t.Fatalf("rejected 不应出现在公开视图,实际 records=%d images=%d", r, i) } } // TestPublicViewsHideUnpublishedStreet 街拍:同上,覆盖另外两个视图。 func TestPublicViewsHideUnpublishedStreet(t *testing.T) { db := testDB(t) applyMigration(t, db, "2026-09-22-01-single-table-publish.sql") ctx := context.Background() id := seedStreetSnap(t, db, model.StatusPending, 1998) count := func() (records, images int64) { db.WithContext(ctx).Table("public_street_snaps").Where("id = ?", id).Count(&records) db.WithContext(ctx).Table("public_street_snap_images").Where("snap_id = ?", id).Count(&images) return } if r, i := count(); r != 0 || i != 0 { t.Fatalf("pending 不应出现在公开视图,实际 records=%d images=%d", r, i) } if err := db.Model(&model.StreetSnap{}).Where("id = ?", id).Update("status", model.StatusPublished).Error; err != nil { t.Fatalf("置为已发布失败: %v", err) } if r, i := count(); r != 1 || i != 1 { t.Fatalf("published 应出现在公开视图,实际 records=%d images=%d", r, i) } }