// 用户显示名批量只读仓储单测(D4:academic 端点 53 的 `realName` 数据源)。 // // 为什么自带 sqlite 库而不走 `setup(t)`:本包的 `setup` 依赖 `UNIFIED_TEST_DSN`(MySQL 集成测试), // 未设置时整包 Skip;本用例要证明的是**三条纯逻辑口径**(NULL → 不返回、空白 → 不返回、批量去重), // 与方言无关,用 sqlite 才能在默认 CI 环境真跑起来。 package repository import ( "context" "path/filepath" "testing" "time" gormsqlite "github.com/glebarez/sqlite" "gorm.io/gorm" "yuansiwei/unified-backend/internal/identity/model" ) // newUserNameTestDB 建只含 `t_user` 的 sqlite 库。 func newUserNameTestDB(t *testing.T) *gorm.DB { t.Helper() dsn := filepath.Join(t.TempDir(), "user_name_repo_test.db") db, err := gorm.Open(gormsqlite.Open(dsn), &gorm.Config{}) if err != nil { t.Fatalf("gorm.Open(显示名测试库) 失败: %v", err) } sqlDB, err := db.DB() if err != nil { t.Fatalf("取 sql.DB 失败: %v", err) } sqlDB.SetMaxOpenConns(1) t.Cleanup(func() { _ = sqlDB.Close() }) if err := db.AutoMigrate(&model.User{}); err != nil { t.Fatalf("AutoMigrate(t_user) 失败: %v", err) } return db } // insertUser 落一个用户(display_name 可 NULL)。 func insertUser(t *testing.T, db *gorm.DB, uid string, displayName *string) { t.Helper() row := &model.User{CanonicalUserID: uid, DisplayName: displayName} if err := db.Create(row).Error; err != nil { t.Fatalf("插入用户 %s 失败: %v", uid, err) } _ = time.Now() } // TestDisplayNamesContracts 三条口径 + 批量去重(**任一条写错都会让端点 53 的姓名列出现脏值**)。 // // A 有名字 → 返回 "张三" // B display_name 为 NULL → **不进 map**(调用方下发 null) // C display_name 为空白串 → **不进 map**(不能把 " " 当姓名) // D 有名字但重复传两次 → 只查一次、结果一次 // E 库里不存在 → 不进 map func TestDisplayNamesContracts(t *testing.T) { db := newUserNameTestDB(t) const ( userA = "01HUSERA0000000000000000A" userB = "01HUSERB0000000000000000B" userC = "01HUSERC0000000000000000C" userD = "01HUSERD0000000000000000D" userE = "01HUSERE0000000000000000E" ) insertUser(t, db, userA, strPtr("张三")) insertUser(t, db, userB, nil) insertUser(t, db, userC, strPtr(" ")) insertUser(t, db, userD, strPtr("李四")) repo := NewUserNameRepo(db) names, err := repo.DisplayNames(context.Background(), []string{userA, userB, userC, userD, userD, userE, " "}) if err != nil { t.Fatalf("DisplayNames 失败: %v", err) } if got, ok := names[userA]; !ok || got != "张三" { t.Fatalf("A = %q(在 map 内=%v),期望 张三", got, ok) } if _, ok := names[userB]; ok { t.Fatalf("B 在 map 内:display_name 为 NULL 时**不得**返回(调用方要据此下发 null)") } if _, ok := names[userC]; ok { t.Fatalf("C 在 map 内:display_name 为空白串时**不得**返回(空白不是姓名)") } if got, ok := names[userD]; !ok || got != "李四" { t.Fatalf("D = %q(在 map 内=%v),期望 李四", got, ok) } if _, ok := names[userE]; ok { t.Fatalf("E 在 map 内:库中不存在该用户,不得返回") } if len(names) != 2 { t.Fatalf("返回条数 = %d,期望 2(只有 A/D 有真名字):%v", len(names), names) } } // TestDisplayNamesEmptyInput 空入参不发 SQL、返回空 map(不是 nil,避免调用方 nil 解引用)。 func TestDisplayNamesEmptyInput(t *testing.T) { db := newUserNameTestDB(t) repo := NewUserNameRepo(db) for _, ids := range [][]string{nil, {}, {"", " "}} { names, err := repo.DisplayNames(context.Background(), ids) if err != nil { t.Fatalf("空入参不该报错: %v", err) } if names == nil { t.Fatalf("空入参应返回空 map 而非 nil(入参 %v)", ids) } if len(names) != 0 { t.Fatalf("空入参应返回 0 条,实际 %d", len(names)) } } } // TestDisplayNamesNilDB 未装配 DB 时返回错误(不得 panic)。 func TestDisplayNamesNilDB(t *testing.T) { repo := NewUserNameRepo(nil) if _, err := repo.DisplayNames(context.Background(), []string{"01HUSERA0000000000000000A"}); err == nil { t.Fatal("DB 为 nil 时应返回错误,而不是静默返回空 map(那会让\"没名字\"与\"库连不上\"混为一谈)") } }