feat: 编写 UserRepository 单元测试
This commit is contained in:
185
backend/internal/store/user_test.go
Normal file
185
backend/internal/store/user_test.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user