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/)
289 lines
7 KiB
Go
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))
|
|
}
|