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) } }