veza/veza-backend-api/internal/services/password_service_test.go
senke ae586f6134 Phase 2 stabilisation: code mort, Modal→Dialog, feature flags, tests, router split, Rust legacy
Bloc A - Code mort:
- Suppression Studio (components, views, features)
- Suppression gamification + services mock (projectService, storageService, gamificationService)
- Mise à jour Sidebar, Navbar, locales

Bloc B - Frontend:
- Suppression modal.tsx deprecated, Modal.stories (doublon Dialog)
- Feature flags: PLAYLIST_SEARCH, PLAYLIST_RECOMMENDATIONS, ROLE_MANAGEMENT = true
- Suppression 19 tests orphelins, retrait exclusions vitest.config

Bloc C - Backend:
- Extraction routes_auth.go depuis router.go

Bloc D - Rust:
- Suppression security_legacy.rs (code mort, patterns déjà dans security/)
2026-02-14 17:23:32 +01:00

289 lines
7 KiB
Go

package services
import (
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
// createTestPasswordService creates a minimal PasswordService for testing Hash and Compare
func createTestPasswordService() *PasswordService {
logger, _ := zap.NewDevelopment()
return &PasswordService{
logger: logger,
}
}
func TestPasswordService_Hash(t *testing.T) {
service := createTestPasswordService()
tests := []struct {
name string
password string
wantErr bool
}{
{
name: "hash simple password",
password: "testpassword123",
wantErr: false,
},
{
name: "hash complex password",
password: "SecurePass123!@#",
wantErr: false,
},
{
name: "hash password with special chars",
password: "Test@123#Pass$",
wantErr: false,
},
{
name: "hash empty password",
password: "",
wantErr: false,
},
{
name: "hash long password",
password: "VeryLongPassword123456789!@#$%^&*()",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash, err := service.Hash(tt.password)
if tt.wantErr {
assert.Error(t, err)
assert.Empty(t, hash)
} else {
assert.NoError(t, err)
assert.NotEmpty(t, hash)
// Verify it's a valid bcrypt hash (starts with $2a$ or $2b$)
assert.Contains(t, []string{"$2a$", "$2b$"}, hash[:4])
}
})
}
}
func TestPasswordService_Hash_DifferentResults(t *testing.T) {
service := createTestPasswordService()
password := "testpassword123"
// Hash the same password twice - should produce different hashes (due to salt)
hash1, err1 := service.Hash(password)
hash2, err2 := service.Hash(password)
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.NotEqual(t, hash1, hash2, "Two hashes of the same password should be different (due to salt)")
}
func TestPasswordService_Hash_ValidBcryptFormat(t *testing.T) {
service := createTestPasswordService()
password := "testpassword123"
hash, err := service.Hash(password)
assert.NoError(t, err)
// Verify the hash is valid by trying to parse it
cost, err := bcrypt.Cost([]byte(hash))
assert.NoError(t, err)
assert.Equal(t, bcryptCost, cost, "Hash should have bcrypt cost 12")
}
func TestPasswordService_Compare_ValidPassword(t *testing.T) {
service := createTestPasswordService()
tests := []struct {
name string
password string
}{
{
name: "compare valid password",
password: "testpassword123",
},
{
name: "compare valid password with special chars",
password: "SecurePass123!@#",
},
{
name: "compare empty password",
password: "",
},
{
name: "compare long password",
password: "VeryLongPassword123456789!@#$%^&*()",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash, err := service.Hash(tt.password)
assert.NoError(t, err)
result := service.Compare(hash, tt.password)
assert.True(t, result, "Password should match the hash")
})
}
}
func TestPasswordService_Compare_InvalidPassword(t *testing.T) {
service := createTestPasswordService()
password := "testpassword123"
wrongPassword := "wrongpassword123"
hash, err := service.Hash(password)
assert.NoError(t, err)
result := service.Compare(hash, wrongPassword)
assert.False(t, result, "Wrong password should not match the hash")
}
func TestPasswordService_Compare_EmptyHash(t *testing.T) {
service := createTestPasswordService()
result := service.Compare("", "testpassword123")
assert.False(t, result, "Empty hash should not match any password")
}
func TestPasswordService_Compare_EmptyPassword(t *testing.T) {
service := createTestPasswordService()
hash, err := service.Hash("testpassword123")
assert.NoError(t, err)
result := service.Compare(hash, "")
assert.False(t, result, "Empty password should not match the hash")
}
func TestPasswordService_Compare_InvalidHash(t *testing.T) {
service := createTestPasswordService()
tests := []struct {
name string
hash string
password string
expectedResult bool
}{
{
name: "invalid hash format",
hash: "invalidhash",
password: "testpassword123",
expectedResult: false,
},
{
name: "malformed bcrypt hash",
hash: "$2a$12$invalid",
password: "testpassword123",
expectedResult: false,
},
{
name: "hash with wrong cost",
hash: "$2a$10$invalidhashformat",
password: "testpassword123",
expectedResult: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := service.Compare(tt.hash, tt.password)
assert.Equal(t, tt.expectedResult, result)
})
}
}
func TestPasswordService_HashAndCompare_Integration(t *testing.T) {
service := createTestPasswordService()
testCases := []struct {
name string
password string
}{
{
name: "simple password",
password: "password123",
},
{
name: "password with uppercase",
password: "Password123",
},
{
name: "password with special chars",
password: "Pass@123!",
},
{
name: "password with spaces",
password: "Pass 123!",
},
{
name: "password with unicode",
password: "Passé123!",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Hash the password
hash, err := service.Hash(tc.password)
assert.NoError(t, err)
assert.NotEmpty(t, hash)
// Compare with correct password - should match
result := service.Compare(hash, tc.password)
assert.True(t, result, "Password should match its hash")
// Compare with wrong password - should not match
wrongResult := service.Compare(hash, "wrongpassword")
assert.False(t, wrongResult, "Wrong password should not match")
})
}
}
func TestPasswordService_Hash_ConsistentCost(t *testing.T) {
service := createTestPasswordService()
password := "testpassword123"
hash, err := service.Hash(password)
assert.NoError(t, err)
// Verify the cost is 12
cost, err := bcrypt.Cost([]byte(hash))
assert.NoError(t, err)
assert.Equal(t, bcryptCost, cost)
}
func TestPasswordService_Hash_ErrorHandling(t *testing.T) {
service := createTestPasswordService()
// Test with extremely long password (bcrypt has a limit of 72 bytes)
// We reject passwords > 72 bytes instead of truncating silently for security
veryLongPassword := make([]byte, 1000)
for i := range veryLongPassword {
veryLongPassword[i] = 'a'
}
hash, err := service.Hash(string(veryLongPassword))
assert.Error(t, err)
assert.Empty(t, hash)
assert.Contains(t, err.Error(), "exceeds maximum length")
}
func TestPasswordService_Compare_CaseSensitive(t *testing.T) {
service := createTestPasswordService()
password := "TestPassword123"
upperPassword := "TESTPASSWORD123"
lowerPassword := "testpassword123"
hash, err := service.Hash(password)
assert.NoError(t, err)
// Exact match should work
assert.True(t, service.Compare(hash, password))
// Case variations should not match
assert.False(t, service.Compare(hash, upperPassword))
assert.False(t, service.Compare(hash, lowerPassword))
}