fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
367
consent-service/internal/services/auth_service_test.go
Normal file
367
consent-service/internal/services/auth_service_test.go
Normal file
@@ -0,0 +1,367 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user