user_repo_test.go 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. // UserRepo 仓储集成测试(t_user 主表查询 + 密码惰性升级写回)。
  2. //
  3. // 依赖 test_helpers_test.go 的 setup/insertFixture;未设置 UNIFIED_TEST_DSN 时跳过。
  4. package repository
  5. import (
  6. "context"
  7. stderrors "errors"
  8. "testing"
  9. apperrors "yuansiwei/unified-backend/internal/infra/errors"
  10. "yuansiwei/unified-backend/internal/identity/id"
  11. identitymodel "yuansiwei/unified-backend/internal/identity/model"
  12. )
  13. // TestUserRepo_GetByCanonicalUserID 命中 / 未命中。
  14. func TestUserRepo_GetByCanonicalUserID(t *testing.T) {
  15. gdb := setup(t)
  16. ctx := context.Background()
  17. fx := insertFixture(t, gdb, ctx)
  18. repo := NewUserRepo(gdb)
  19. t.Run("命中", func(t *testing.T) {
  20. u, err := repo.GetByCanonicalUserID(ctx, fx.canonical)
  21. if err != nil {
  22. t.Fatalf("GetByCanonicalUserID 命中: %v", err)
  23. }
  24. if u.CanonicalUserID != fx.canonical {
  25. t.Errorf("返回 canonical_user_id = %q,期望 %q", u.CanonicalUserID, fx.canonical)
  26. }
  27. if u.Status != identitymodel.UserStatusActive {
  28. t.Errorf("status = %q,期望 %q", u.Status, identitymodel.UserStatusActive)
  29. }
  30. })
  31. t.Run("未命中_返回ErrNotFound", func(t *testing.T) {
  32. _, err := repo.GetByCanonicalUserID(ctx, "00000000000000000000000000")
  33. if !stderrors.Is(err, apperrors.ErrNotFound) {
  34. t.Fatalf("期望 ErrNotFound,实际 %v", err)
  35. }
  36. })
  37. }
  38. // TestUserRepo_UpdatePasswordLazyUpgrade 惰性升级写回(argon2 + salt NULL + hash_version)/ 未命中用户。
  39. func TestUserRepo_UpdatePasswordLazyUpgrade(t *testing.T) {
  40. gdb := setup(t)
  41. ctx := context.Background()
  42. fx := insertFixture(t, gdb, ctx)
  43. repo := NewUserRepo(gdb)
  44. t.Run("命中_升级写回argon2_salt置NULL", func(t *testing.T) {
  45. // 升级前确认旧值
  46. u, err := repo.GetByCanonicalUserID(ctx, fx.canonical)
  47. if err != nil {
  48. t.Fatalf("升级前取用户: %v", err)
  49. }
  50. if u.PasswordHash == nil || *u.PasswordHash != fx.hashOld {
  51. t.Fatalf("升级前 password_hash = %v,期望 %q", u.PasswordHash, fx.hashOld)
  52. }
  53. // 升级写回
  54. if err := repo.UpdatePasswordLazyUpgrade(ctx, fx.canonical, fx.hashNew); err != nil {
  55. t.Fatalf("UpdatePasswordLazyUpgrade: %v", err)
  56. }
  57. // 断言升级后三列
  58. u2, err := repo.GetByCanonicalUserID(ctx, fx.canonical)
  59. if err != nil {
  60. t.Fatalf("升级后取用户: %v", err)
  61. }
  62. if u2.PasswordHash == nil || *u2.PasswordHash != fx.hashNew {
  63. t.Errorf("升级后 password_hash = %v,期望 %q", u2.PasswordHash, fx.hashNew)
  64. }
  65. if u2.PasswordSalt != nil {
  66. t.Errorf("升级后 password_salt 应为 NULL,实际 %v", *u2.PasswordSalt)
  67. }
  68. if u2.HashVersion == nil || *u2.HashVersion != identitymodel.HashVersionArgon2 {
  69. t.Errorf("升级后 hash_version = %v,期望 argon2", u2.HashVersion)
  70. }
  71. })
  72. t.Run("未命中用户_返回ErrNotFound", func(t *testing.T) {
  73. // 库里不存在该 ULID 的用户(RowsAffected=0 → ErrNotFound)
  74. ghost, err := id.NewCanonicalUserID()
  75. if err != nil {
  76. t.Fatalf("生成 ghost uid: %v", err)
  77. }
  78. if err := repo.UpdatePasswordLazyUpgrade(ctx, ghost, fx.hashNew); !stderrors.Is(err, apperrors.ErrNotFound) {
  79. t.Fatalf("期望 ErrNotFound,实际 %v", err)
  80. }
  81. })
  82. }