fix(publish): 迁移03街拍体检同步 city+year+title 口径,补 04 体检与查重口径测试

This commit is contained in:
toom1996
2026-09-23 13:14:48 +08:00
parent ce4f8b786d
commit 12d05f75e9
4 changed files with 84 additions and 5 deletions

View File

@ -113,3 +113,64 @@ func TestStreetEntityKeyUnique(t *testing.T) {
db.Exec("DELETE FROM street_snaps WHERE id = ?", newID)
})
}
// TestStreetEntityStateMatchesIndexKey 覆盖 StreetSnapEntityState 的查重口径:
// 它必须与唯一索引 uq_ss_entity 的表达式 COALESCE(title, '') 完全一致,
// 否则查重会比索引更严(漏判 → 写入注定被索引拒绝的行)或更松(重复内容被当成新实体)。
func TestStreetEntityStateMatchesIndexKey(t *testing.T) {
db := testDB(t)
applyMigration(t, db, "2026-09-22-01-single-table-publish.sql")
applyMigration(t, db, "2026-09-22-03-entity-key-unique.sql")
applyMigration(t, db, "2026-09-22-04-street-entity-key-title.sql")
repo := NewIngestRepository(db)
ctx := context.Background()
const city = "EntityStateTestCity"
const year = 1996
// 种子行:title = Day 2(经 ORM 写入,title 为普通空串语义)。
snap := &model.StreetSnap{JobID: 1, Title: "EntityState Day 2", Year: year, City: city, Status: model.StatusPending}
id, err := repo.CreateStreetSnapWithImages(ctx, snap, nil)
if err != nil {
t.Fatalf("种子行应能插入: %v", err)
}
t.Cleanup(func() { db.Exec("DELETE FROM street_snaps WHERE id = ?", id) })
// ① 同城同年**不同 title** → 不命中(同城同年不同专题是两个实体)。
if _, _, found, err := repo.StreetSnapEntityState(ctx, city, year, "EntityState Day 3"); err != nil {
t.Fatalf("查重出错: %v", err)
} else if found {
t.Fatalf("不同 title 不应命中既有行,实际命中")
}
// ② 同城同年**同 title** → 命中,且带出正确 id / status。
hitID, status, found, err := repo.StreetSnapEntityState(ctx, city, year, "EntityState Day 2")
if err != nil {
t.Fatalf("查重出错: %v", err)
}
if !found || hitID != id || status != model.StatusPending {
t.Fatalf("同 title 应命中 id=%d status=pending,实际 found=%v id=%d status=%q", id, found, hitID, status)
}
// ③ title 为 NULL 的历史行 + 传入空串 title → 必须命中。
// 这是 COALESCE(title, '') 与裸 title = ? 唯一的分歧点:裸写法下 NULL 永不等于空串,
// 这里会误判「未命中」并插入一行与 NULL 行同键(COALESCE 后同为 '')的记录,被索引拒绝。
// 用原生 SQL 插入,因为 GORM 的 string 字段会写空串而非 NULL。
var nullID uint32
if err := db.Raw(
"INSERT INTO street_snaps (title, year, city, is_deleted, status, created_at, updated_at) "+
"VALUES (NULL, ?, ?, 0, ?, 0, 0) RETURNING id",
year, city, model.StatusPending,
).Scan(&nullID).Error; err != nil {
t.Fatalf("插入 NULL title 行失败: %v", err)
}
t.Cleanup(func() { db.Exec("DELETE FROM street_snaps WHERE id = ?", nullID) })
nid, _, nfound, err := repo.StreetSnapEntityState(ctx, city, year, "")
if err != nil {
t.Fatalf("查重出错: %v", err)
}
if !nfound || nid != nullID {
t.Fatalf("NULL title 行 + 空串应命中 id=%d,实际 found=%v id=%d", nullID, nfound, nid)
}
}