package store import ( "context" "errors" "testing" "time" ) // newUserRepo 返回一个可测试的 UserRepository 实现。 // 如需测试 Pg 实现,可在此替换为连接真实 DB 的版本。 func newUserRepo() UserRepository { return NewMemUserRepository() } func TestUserRepository_Create(t *testing.T) { repo := newUserRepo() ctx := context.Background() id, err := repo.Create(ctx, "alice", "hash123") if err != nil { t.Fatalf("Create failed: %v", err) } if id == "" { t.Fatal("expected non-empty ID") } // 重复用户名应返回 ErrUsernameTaken _, err = repo.Create(ctx, "alice", "hash456") if !errors.Is(err, ErrUsernameTaken) { t.Fatalf("expected ErrUsernameTaken, got %v", err) } } func TestUserRepository_FindByUsername(t *testing.T) { repo := newUserRepo() ctx := context.Background() _, err := repo.Create(ctx, "bob", "hash_bob") if err != nil { t.Fatalf("Create failed: %v", err) } user, err := repo.FindByUsername(ctx, "bob") if err != nil { t.Fatalf("FindByUsername failed: %v", err) } if user.Username != "bob" { t.Fatalf("expected username bob, got %s", user.Username) } if user.PasswordHash != "hash_bob" { t.Fatalf("expected password hash hash_bob, got %s", user.PasswordHash) } // 不存在的用户 _, err = repo.FindByUsername(ctx, "nobody") if !errors.Is(err, ErrUserNotFound) { t.Fatalf("expected ErrUserNotFound, got %v", err) } } func TestUserRepository_FindByID(t *testing.T) { repo := newUserRepo() ctx := context.Background() id, err := repo.Create(ctx, "charlie", "hash_charlie") if err != nil { t.Fatalf("Create failed: %v", err) } user, err := repo.FindByID(ctx, id) if err != nil { t.Fatalf("FindByID failed: %v", err) } if user.ID != id { t.Fatalf("expected ID %s, got %s", id, user.ID) } if user.Username != "charlie" { t.Fatalf("expected username charlie, got %s", user.Username) } // 不存在的 ID _, err = repo.FindByID(ctx, "nonexistent-uuid") if !errors.Is(err, ErrUserNotFound) { t.Fatalf("expected ErrUserNotFound, got %v", err) } } func TestUserRepository_RefreshToken(t *testing.T) { repo := newUserRepo() ctx := context.Background() userID, err := repo.Create(ctx, "dave", "hash_dave") if err != nil { t.Fatalf("Create failed: %v", err) } tokenHash := "abc123hash" expiresAt := time.Now().Add(7 * 24 * time.Hour) // 保存 token if err := repo.SaveRefreshToken(ctx, userID, tokenHash, expiresAt); err != nil { t.Fatalf("SaveRefreshToken failed: %v", err) } // 查找 token foundUserID, err := repo.FindRefreshToken(ctx, tokenHash) if err != nil { t.Fatalf("FindRefreshToken failed: %v", err) } if foundUserID != userID { t.Fatalf("expected userID %s, got %s", userID, foundUserID) } // 不存在的 token _, err = repo.FindRefreshToken(ctx, "nonexistent") if !errors.Is(err, ErrRefreshTokenNotFound) { t.Fatalf("expected ErrRefreshTokenNotFound, got %v", err) } // 删除 token if err := repo.DeleteRefreshToken(ctx, tokenHash); err != nil { t.Fatalf("DeleteRefreshToken failed: %v", err) } _, err = repo.FindRefreshToken(ctx, tokenHash) if !errors.Is(err, ErrRefreshTokenNotFound) { t.Fatalf("expected ErrRefreshTokenNotFound after delete, got %v", err) } } func TestUserRepository_DeleteUserRefreshTokens(t *testing.T) { repo := newUserRepo() ctx := context.Background() userID, err := repo.Create(ctx, "eve", "hash_eve") if err != nil { t.Fatalf("Create failed: %v", err) } // 保存多个 token for i := 0; i < 3; i++ { tokenHash := "token_" + string(rune('a'+i)) expiresAt := time.Now().Add(7 * 24 * time.Hour) if err := repo.SaveRefreshToken(ctx, userID, tokenHash, expiresAt); err != nil { t.Fatalf("SaveRefreshToken failed: %v", err) } } // 删除用户所有 token if err := repo.DeleteUserRefreshTokens(ctx, userID); err != nil { t.Fatalf("DeleteUserRefreshTokens failed: %v", err) } // 验证全部删除 for i := 0; i < 3; i++ { tokenHash := "token_" + string(rune('a'+i)) _, err := repo.FindRefreshToken(ctx, tokenHash) if !errors.Is(err, ErrRefreshTokenNotFound) { t.Fatalf("expected ErrRefreshTokenNotFound for token_%c, got %v", 'a'+i, err) } } } func TestUserRepository_ExpiredRefreshToken(t *testing.T) { repo := newUserRepo() ctx := context.Background() userID, err := repo.Create(ctx, "frank", "hash_frank") if err != nil { t.Fatalf("Create failed: %v", err) } tokenHash := "expired_token" expiresAt := time.Now().Add(-1 * time.Hour) // 已过期 if err := repo.SaveRefreshToken(ctx, userID, tokenHash, expiresAt); err != nil { t.Fatalf("SaveRefreshToken failed: %v", err) } // 过期 token 应返回 ErrRefreshTokenNotFound _, err = repo.FindRefreshToken(ctx, tokenHash) if !errors.Is(err, ErrRefreshTokenNotFound) { t.Fatalf("expected ErrRefreshTokenNotFound for expired token, got %v", err) } }