package services import ( "testing" "time" "github.com/breakpilot/consent-service/internal/models" "github.com/google/uuid" ) // TestHashPassword tests password hashing func TestHashPassword(t *testing.T) { // Create service without DB for unit tests s := &AuthService{} password := "testPassword123!" hash, err := s.HashPassword(password) if err != nil { t.Fatalf("HashPassword failed: %v", err) } if hash == "" { t.Error("Hash should not be empty") } if hash == password { t.Error("Hash should not equal the original password") } // Hash should be different each time (bcrypt uses random salt) hash2, _ := s.HashPassword(password) if hash == hash2 { t.Error("Same password should produce different hashes due to salt") } } // TestVerifyPassword tests password verification func TestVerifyPassword(t *testing.T) { s := &AuthService{} password := "testPassword123!" hash, _ := s.HashPassword(password) // Should verify correct password if !s.VerifyPassword(password, hash) { t.Error("VerifyPassword should return true for correct password") } // Should reject incorrect password if s.VerifyPassword("wrongPassword", hash) { t.Error("VerifyPassword should return false for incorrect password") } // Should reject empty password if s.VerifyPassword("", hash) { t.Error("VerifyPassword should return false for empty password") } } // TestGenerateSecureToken tests token generation func TestGenerateSecureToken(t *testing.T) { s := &AuthService{} tests := []struct { name string length int }{ {"short token", 16}, {"standard token", 32}, {"long token", 64}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { token, err := s.GenerateSecureToken(tt.length) if err != nil { t.Fatalf("GenerateSecureToken failed: %v", err) } if token == "" { t.Error("Token should not be empty") } // Tokens should be unique token2, _ := s.GenerateSecureToken(tt.length) if token == token2 { t.Error("Generated tokens should be unique") } }) } } // TestHashToken tests token hashing for storage func TestHashToken(t *testing.T) { s := &AuthService{} token := "test-token-123" hash := s.HashToken(token) if hash == "" { t.Error("Hash should not be empty") } if hash == token { t.Error("Hash should not equal the original token") } // Same token should produce same hash (deterministic) hash2 := s.HashToken(token) if hash != hash2 { t.Error("Same token should produce same hash") } // Different tokens should produce different hashes differentHash := s.HashToken("different-token") if hash == differentHash { t.Error("Different tokens should produce different hashes") } } // TestGenerateAccessToken tests JWT access token generation func TestGenerateAccessToken(t *testing.T) { s := &AuthService{ jwtSecret: "test-secret-key-for-testing-purposes", accessTokenExp: time.Hour, } user := &models.User{ ID: uuid.New(), Email: "test@example.com", Role: "user", AccountStatus: "active", } token, err := s.GenerateAccessToken(user) if err != nil { t.Fatalf("GenerateAccessToken failed: %v", err) } if token == "" { t.Error("Token should not be empty") } // Token should have three parts (header.payload.signature) parts := 0 for _, c := range token { if c == '.' { parts++ } } if parts != 2 { t.Errorf("JWT token should have 3 parts, got %d dots", parts) } } // TestValidateAccessToken tests JWT token validation func TestValidateAccessToken(t *testing.T) { secret := "test-secret-key-for-testing-purposes" s := &AuthService{ jwtSecret: secret, accessTokenExp: time.Hour, } user := &models.User{ ID: uuid.New(), Email: "test@example.com", Role: "admin", AccountStatus: "active", } token, _ := s.GenerateAccessToken(user) // Should validate valid token claims, err := s.ValidateAccessToken(token) if err != nil { t.Fatalf("ValidateAccessToken failed: %v", err) } if claims.UserID != user.ID.String() { t.Errorf("Expected UserID %s, got %s", user.ID.String(), claims.UserID) } if claims.Email != user.Email { t.Errorf("Expected Email %s, got %s", user.Email, claims.Email) } if claims.Role != user.Role { t.Errorf("Expected Role %s, got %s", user.Role, claims.Role) } } // TestValidateAccessToken_Invalid tests invalid token scenarios func TestValidateAccessToken_Invalid(t *testing.T) { s := &AuthService{ jwtSecret: "test-secret-key-for-testing-purposes", accessTokenExp: time.Hour, } tests := []struct { name string token string }{ {"empty token", ""}, {"invalid format", "not-a-jwt-token"}, {"invalid signature", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.invalidsignature"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := s.ValidateAccessToken(tt.token) if err == nil { t.Error("ValidateAccessToken should fail for invalid token") } }) } } // TestValidateAccessToken_WrongSecret tests token with wrong secret func TestValidateAccessToken_WrongSecret(t *testing.T) { s1 := &AuthService{ jwtSecret: "secret-one", accessTokenExp: time.Hour, } s2 := &AuthService{ jwtSecret: "secret-two", accessTokenExp: time.Hour, } user := &models.User{ ID: uuid.New(), Email: "test@example.com", Role: "user", AccountStatus: "active", } // Generate token with first secret token, _ := s1.GenerateAccessToken(user) // Try to validate with second secret (should fail) _, err := s2.ValidateAccessToken(token) if err == nil { t.Error("ValidateAccessToken should fail when using wrong secret") } } // TestGenerateRefreshToken tests refresh token generation func TestGenerateRefreshToken(t *testing.T) { s := &AuthService{} token, hash, err := s.GenerateRefreshToken() if err != nil { t.Fatalf("GenerateRefreshToken failed: %v", err) } if token == "" { t.Error("Token should not be empty") } if hash == "" { t.Error("Hash should not be empty") } // Verify hash matches token expectedHash := s.HashToken(token) if hash != expectedHash { t.Error("Returned hash should match hashed token") } // Tokens should be unique token2, hash2, _ := s.GenerateRefreshToken() if token == token2 { t.Error("Generated tokens should be unique") } if hash == hash2 { t.Error("Generated hashes should be unique") } } // TestPasswordStrength tests various password scenarios func TestPasswordStrength(t *testing.T) { s := &AuthService{} passwords := []struct { password string valid bool }{ {"short", true}, // bcrypt accepts any length {"12345678", true}, // numbers only {"password", true}, // letters only {"Pass123!", true}, // mixed {"", true}, // empty (bcrypt allows) {string(make([]byte, 72)), true}, // max bcrypt length } for _, p := range passwords { hash, err := s.HashPassword(p.password) if p.valid && err != nil { t.Errorf("HashPassword failed for valid password %q: %v", p.password, err) } if p.valid && !s.VerifyPassword(p.password, hash) { t.Errorf("VerifyPassword failed for password %q", p.password) } } } // BenchmarkHashPassword benchmarks password hashing func BenchmarkHashPassword(b *testing.B) { s := &AuthService{} password := "testPassword123!" for i := 0; i < b.N; i++ { s.HashPassword(password) } } // BenchmarkVerifyPassword benchmarks password verification func BenchmarkVerifyPassword(b *testing.B) { s := &AuthService{} password := "testPassword123!" hash, _ := s.HashPassword(password) for i := 0; i < b.N; i++ { s.VerifyPassword(password, hash) } } // BenchmarkGenerateAccessToken benchmarks JWT token generation func BenchmarkGenerateAccessToken(b *testing.B) { s := &AuthService{ jwtSecret: "test-secret-key-for-testing-purposes", accessTokenExp: time.Hour, } user := &models.User{ ID: uuid.New(), Email: "test@example.com", Role: "user", AccountStatus: "active", } for i := 0; i < b.N; i++ { s.GenerateAccessToken(user) } } // BenchmarkValidateAccessToken benchmarks JWT token validation func BenchmarkValidateAccessToken(b *testing.B) { s := &AuthService{ jwtSecret: "test-secret-key-for-testing-purposes", accessTokenExp: time.Hour, } user := &models.User{ ID: uuid.New(), Email: "test@example.com", Role: "user", AccountStatus: "active", } token, _ := s.GenerateAccessToken(user) for i := 0; i < b.N; i++ { s.ValidateAccessToken(token) } }