Files
mygo/internal/repository/credential_test.go
Huxley 3eeb9f6d26 Implement JWT authentication and app passkey support
- Add JWT token generation and validation
- Implement bcrypt password hashing
- Create auth service with register/login/refresh/logout
- Add app passkey generation and management
- Implement protected routes and auth middleware
- Add comprehensive tests for new functionality
2026-04-29 11:50:09 +08:00

195 lines
5.2 KiB
Go

package repository
import (
"context"
"testing"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/dhao2001/mygo/internal/model"
)
func setupCredentialRepo(t *testing.T) CredentialRepository {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("open db: %v", err)
}
if err := db.AutoMigrate(&model.Credential{}); err != nil {
t.Fatalf("migrate: %v", err)
}
return NewCredentialRepository(db)
}
func TestCredentialRepository_Create(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
cred := &model.Credential{
ID: "cred-1",
UserID: "user-1",
Type: "app_passkey",
Label: "My Phone",
SecretHash: "hash-abc",
}
if err := repo.Create(ctx, cred); err != nil {
t.Fatalf("Create = %v", err)
}
}
func TestCredentialRepository_CreateDuplicateHash(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
c1 := &model.Credential{ID: "cred-1", UserID: "user-1", Type: "app_passkey", Label: "A", SecretHash: "hash-abc"}
c2 := &model.Credential{ID: "cred-2", UserID: "user-1", Type: "app_passkey", Label: "B", SecretHash: "hash-abc"}
if err := repo.Create(ctx, c1); err != nil {
t.Fatalf("Create = %v", err)
}
err := repo.Create(ctx, c2)
if err != model.ErrDuplicate {
t.Fatalf("expected ErrDuplicate, got %v", err)
}
}
func TestCredentialRepository_FindByID(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
cred := &model.Credential{ID: "cred-1", UserID: "user-1", Type: "app_passkey", Label: "Phone", SecretHash: "h1"}
if err := repo.Create(ctx, cred); err != nil {
t.Fatalf("Create = %v", err)
}
found, err := repo.FindByID(ctx, "cred-1")
if err != nil {
t.Fatalf("FindByID = %v", err)
}
if found.Label != "Phone" {
t.Errorf("Label = %q, want %q", found.Label, "Phone")
}
}
func TestCredentialRepository_FindByIDNotFound(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
_, err := repo.FindByID(ctx, "nonexistent")
if err != model.ErrNotFound {
t.Fatalf("expected ErrNotFound, got %v", err)
}
}
func TestCredentialRepository_FindByUserID(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
c1 := &model.Credential{ID: "c-1", UserID: "user-1", Type: "app_passkey", Label: "A", SecretHash: "h1"}
c2 := &model.Credential{ID: "c-2", UserID: "user-1", Type: "app_passkey", Label: "B", SecretHash: "h2"}
c3 := &model.Credential{ID: "c-3", UserID: "user-2", Type: "app_passkey", Label: "C", SecretHash: "h3"}
for _, c := range []*model.Credential{c1, c2, c3} {
if err := repo.Create(ctx, c); err != nil {
t.Fatalf("Create = %v", err)
}
}
creds, err := repo.FindByUserID(ctx, "user-1")
if err != nil {
t.Fatalf("FindByUserID = %v", err)
}
if len(creds) != 2 {
t.Errorf("len(creds) = %d, want 2", len(creds))
}
}
func TestCredentialRepository_FindByUserIDAndType(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
c1 := &model.Credential{ID: "c-1", UserID: "user-1", Type: "app_passkey", Label: "A", SecretHash: "h1"}
c2 := &model.Credential{ID: "c-2", UserID: "user-1", Type: "oauth", Label: "Github", SecretHash: "h2"}
for _, c := range []*model.Credential{c1, c2} {
if err := repo.Create(ctx, c); err != nil {
t.Fatalf("Create = %v", err)
}
}
passkeys, err := repo.FindByUserIDAndType(ctx, "user-1", "app_passkey")
if err != nil {
t.Fatalf("FindByUserIDAndType = %v", err)
}
if len(passkeys) != 1 {
t.Errorf("len(passkeys) = %d, want 1", len(passkeys))
}
if passkeys[0].Type != "app_passkey" {
t.Errorf("type = %q, want %q", passkeys[0].Type, "app_passkey")
}
}
func TestCredentialRepository_FindByHash(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
cred := &model.Credential{ID: "c-1", UserID: "user-1", Type: "app_passkey", Label: "Phone", SecretHash: "hash-find"}
if err := repo.Create(ctx, cred); err != nil {
t.Fatalf("Create = %v", err)
}
found, err := repo.FindByHash(ctx, "hash-find")
if err != nil {
t.Fatalf("FindByHash = %v", err)
}
if found.UserID != "user-1" {
t.Errorf("UserID = %q, want %q", found.UserID, "user-1")
}
}
func TestCredentialRepository_UpdateLastUsed(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
cred := &model.Credential{ID: "c-1", UserID: "user-1", Type: "app_passkey", Label: "Phone", SecretHash: "h1"}
if err := repo.Create(ctx, cred); err != nil {
t.Fatalf("Create = %v", err)
}
if err := repo.UpdateLastUsed(ctx, "c-1"); err != nil {
t.Fatalf("UpdateLastUsed = %v", err)
}
found, err := repo.FindByID(ctx, "c-1")
if err != nil {
t.Fatalf("FindByID = %v", err)
}
if found.LastUsedAt == nil {
t.Fatal("LastUsedAt should not be nil after update")
}
}
func TestCredentialRepository_Delete(t *testing.T) {
repo := setupCredentialRepo(t)
ctx := context.Background()
cred := &model.Credential{ID: "c-1", UserID: "user-1", Type: "app_passkey", Label: "Phone", SecretHash: "h1"}
if err := repo.Create(ctx, cred); err != nil {
t.Fatalf("Create = %v", err)
}
if err := repo.Delete(ctx, "c-1"); err != nil {
t.Fatalf("Delete = %v", err)
}
_, err := repo.FindByID(ctx, "c-1")
if err != model.ErrNotFound {
t.Fatalf("expected ErrNotFound after delete, got %v", err)
}
}