mirror of
https://github.com/sonr-io/crypto.git
synced 2026-01-12 04:09:13 +00:00
208 lines
4.2 KiB
Go
208 lines
4.2 KiB
Go
|
|
package password
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestValidator_Validate(t *testing.T) {
|
||
|
|
validator := NewValidator(DefaultPasswordConfig())
|
||
|
|
|
||
|
|
testCases := []struct {
|
||
|
|
name string
|
||
|
|
password string
|
||
|
|
wantErr bool
|
||
|
|
errMsg string
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "too short",
|
||
|
|
password: "Short1!",
|
||
|
|
wantErr: true,
|
||
|
|
errMsg: "at least 12 characters",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "too long",
|
||
|
|
password: string(make([]byte, 129)),
|
||
|
|
wantErr: true,
|
||
|
|
errMsg: "not exceed 128 characters",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "missing uppercase",
|
||
|
|
password: "longenoughpassword123!",
|
||
|
|
wantErr: true,
|
||
|
|
errMsg: "uppercase letter",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "missing lowercase",
|
||
|
|
password: "LONGENOUGHPASSWORD123!",
|
||
|
|
wantErr: true,
|
||
|
|
errMsg: "lowercase letter",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "missing digit",
|
||
|
|
password: "LongEnoughPassword!",
|
||
|
|
wantErr: true,
|
||
|
|
errMsg: "one digit",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "missing special",
|
||
|
|
password: "LongEnoughPassword123",
|
||
|
|
wantErr: true,
|
||
|
|
errMsg: "special character",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "valid password",
|
||
|
|
password: "ValidPassword123!",
|
||
|
|
wantErr: false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "complex valid password",
|
||
|
|
password: "MyS3cur3P@ssw0rd!2024",
|
||
|
|
wantErr: false,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tc := range testCases {
|
||
|
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
|
err := validator.Validate([]byte(tc.password))
|
||
|
|
if tc.wantErr {
|
||
|
|
require.Error(t, err)
|
||
|
|
assert.Contains(t, err.Error(), tc.errMsg)
|
||
|
|
} else {
|
||
|
|
assert.NoError(t, err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_CustomConfig(t *testing.T) {
|
||
|
|
config := &PasswordConfig{
|
||
|
|
MinLength: 8,
|
||
|
|
MaxLength: 64,
|
||
|
|
RequireUppercase: false,
|
||
|
|
RequireLowercase: true,
|
||
|
|
RequireDigits: true,
|
||
|
|
RequireSpecial: false,
|
||
|
|
MinEntropy: 30.0,
|
||
|
|
}
|
||
|
|
|
||
|
|
validator := NewValidator(config)
|
||
|
|
|
||
|
|
// Should pass with custom config
|
||
|
|
err := validator.Validate([]byte("simple123"))
|
||
|
|
assert.NoError(t, err)
|
||
|
|
|
||
|
|
// Should fail - too short
|
||
|
|
err = validator.Validate([]byte("short1"))
|
||
|
|
assert.Error(t, err)
|
||
|
|
|
||
|
|
// Should fail - no digits
|
||
|
|
err = validator.Validate([]byte("simplepass"))
|
||
|
|
assert.Error(t, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGenerateSalt(t *testing.T) {
|
||
|
|
// Test valid salt generation
|
||
|
|
salt, err := GenerateSalt(32)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Len(t, salt, 32)
|
||
|
|
|
||
|
|
// Test different salt each time
|
||
|
|
salt2, err := GenerateSalt(32)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.NotEqual(t, salt, salt2)
|
||
|
|
|
||
|
|
// Test minimum size enforcement
|
||
|
|
_, err = GenerateSalt(8)
|
||
|
|
assert.Error(t, err)
|
||
|
|
assert.Contains(t, err.Error(), "at least 16 bytes")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSecureCompare(t *testing.T) {
|
||
|
|
// Test equal slices
|
||
|
|
a := []byte("password")
|
||
|
|
b := []byte("password")
|
||
|
|
assert.True(t, SecureCompare(a, b))
|
||
|
|
|
||
|
|
// Test different slices
|
||
|
|
c := []byte("different")
|
||
|
|
assert.False(t, SecureCompare(a, c))
|
||
|
|
|
||
|
|
// Test different lengths
|
||
|
|
d := []byte("pass")
|
||
|
|
assert.False(t, SecureCompare(a, d))
|
||
|
|
|
||
|
|
// Test empty slices
|
||
|
|
assert.True(t, SecureCompare([]byte{}, []byte{}))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestZeroBytes(t *testing.T) {
|
||
|
|
password := []byte("sensitive")
|
||
|
|
ZeroBytes(password)
|
||
|
|
|
||
|
|
for _, b := range password {
|
||
|
|
assert.Equal(t, byte(0), b)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCalculateEntropy(t *testing.T) {
|
||
|
|
validator := NewValidator(nil)
|
||
|
|
|
||
|
|
testCases := []struct {
|
||
|
|
name string
|
||
|
|
password string
|
||
|
|
minEntropy float64
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "lowercase only",
|
||
|
|
password: "abcdefghij",
|
||
|
|
minEntropy: 40,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "alphanumeric",
|
||
|
|
password: "Abc123",
|
||
|
|
minEntropy: 30,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "complex",
|
||
|
|
password: "MyP@ssw0rd!",
|
||
|
|
minEntropy: 50,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tc := range testCases {
|
||
|
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
|
entropy := validator.calculateEntropy([]byte(tc.password))
|
||
|
|
assert.GreaterOrEqual(t, entropy, tc.minEntropy)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkValidate(b *testing.B) {
|
||
|
|
validator := NewValidator(DefaultPasswordConfig())
|
||
|
|
password := []byte("ValidPassword123!")
|
||
|
|
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
_ = validator.Validate(password)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkGenerateSalt(b *testing.B) {
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
_, _ = GenerateSalt(32)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkSecureCompare(b *testing.B) {
|
||
|
|
a := []byte("password123")
|
||
|
|
c := []byte("password123")
|
||
|
|
|
||
|
|
b.ResetTimer()
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
_ = SecureCompare(a, c)
|
||
|
|
}
|
||
|
|
}
|