242 lines
5.3 KiB
Go
242 lines
5.3 KiB
Go
package password
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestHash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
password string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid password",
|
|
password: "testpassword123",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty password",
|
|
password: "",
|
|
wantErr: false, // Empty password is valid, just hashed
|
|
},
|
|
{
|
|
name: "long password",
|
|
password: "this is a very long password with many characters and symbols !@#$%^&*()",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hash, err := Hash(tt.password)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
assert.Empty(t, hash)
|
|
} else {
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, hash)
|
|
// Verify hash format: $argon2id$v=19$m=65536,t=3,p=4$salt$hash
|
|
assert.Contains(t, hash, "$argon2id$")
|
|
assert.Contains(t, hash, "v=19")
|
|
assert.Contains(t, hash, "m=65536")
|
|
assert.Contains(t, hash, "t=3")
|
|
assert.Contains(t, hash, "p=4")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVerify(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
password string
|
|
hash string
|
|
want bool
|
|
wantErr bool
|
|
skip bool
|
|
}{
|
|
{
|
|
name: "correct password",
|
|
password: "testpassword123",
|
|
hash: "", // Will be generated
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "incorrect password",
|
|
password: "wrongpassword",
|
|
hash: "", // Will be generated from different password
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid hash format - too few parts",
|
|
password: "testpassword123",
|
|
hash: "$argon2id$v=19$m=65536",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid hash format - wrong algorithm",
|
|
password: "testpassword123",
|
|
hash: "$bcrypt$v=19$m=65536,t=3,p=4$salt$hash",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid hash format - malformed version",
|
|
password: "testpassword123",
|
|
hash: "$argon2id$v=invalid$m=65536,t=3,p=4$salt$hash",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid hash format - malformed parameters",
|
|
password: "testpassword123",
|
|
hash: "$argon2id$v=19$m=invalid,t=3,p=4$salt$hash",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid hash format - invalid base64 salt",
|
|
password: "testpassword123",
|
|
hash: "$argon2id$v=19$m=65536,t=3,p=4$invalid-base64$hash",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid hash format - invalid base64 hash",
|
|
password: "testpassword123",
|
|
hash: "", // Will be generated and then corrupted
|
|
want: false,
|
|
wantErr: true,
|
|
skip: true, // Skip this test - corrupting base64 is complex
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Skip test if marked
|
|
if tt.skip {
|
|
t.Skip("Skipping test")
|
|
}
|
|
|
|
var hash string
|
|
var err error
|
|
|
|
// Generate hash if needed
|
|
if tt.hash == "" {
|
|
if tt.name == "incorrect password" {
|
|
// Generate hash for a different password
|
|
hash, err = Hash("differentpassword")
|
|
require.NoError(t, err)
|
|
} else {
|
|
hash, err = Hash(tt.password)
|
|
require.NoError(t, err)
|
|
}
|
|
} else {
|
|
hash = tt.hash
|
|
}
|
|
|
|
// Verify
|
|
got, err := Verify(tt.password, hash)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHash_Verify_RoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
passwords := []string{
|
|
"testpassword123",
|
|
"",
|
|
"!@#$%^&*()",
|
|
"very long password with spaces and special characters !@#$%^&*()_+-=[]{}|;:,.<>?",
|
|
"unicode-测试-пароль",
|
|
}
|
|
|
|
for _, password := range passwords {
|
|
t.Run(password, func(t *testing.T) {
|
|
hash, err := Hash(password)
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, hash)
|
|
|
|
// Verify the same password
|
|
valid, err := Verify(password, hash)
|
|
require.NoError(t, err)
|
|
assert.True(t, valid, "Password should verify correctly")
|
|
|
|
// Verify different password
|
|
invalid, err := Verify("wrongpassword", hash)
|
|
require.NoError(t, err)
|
|
assert.False(t, invalid, "Wrong password should not verify")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHash_Uniqueness(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
password := "testpassword123"
|
|
hashes := make(map[string]bool)
|
|
|
|
// Generate multiple hashes for the same password
|
|
// They should be different due to random salt
|
|
for i := 0; i < 10; i++ {
|
|
hash, err := Hash(password)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, hashes, hash, "Each hash should be unique due to random salt")
|
|
hashes[hash] = true
|
|
|
|
// But all should verify correctly
|
|
valid, err := Verify(password, hash)
|
|
require.NoError(t, err)
|
|
assert.True(t, valid, "All hashes should verify correctly")
|
|
}
|
|
}
|
|
|
|
// Helper functions for test
|
|
func splitHash(hash string) []string {
|
|
parts := make([]string, 0, 6)
|
|
current := ""
|
|
for _, char := range hash {
|
|
if char == '$' {
|
|
if current != "" {
|
|
parts = append(parts, current)
|
|
current = ""
|
|
}
|
|
} else {
|
|
current += string(char)
|
|
}
|
|
}
|
|
if current != "" {
|
|
parts = append(parts, current)
|
|
}
|
|
return parts
|
|
}
|
|
|
|
func joinHash(parts []string) string {
|
|
result := ""
|
|
for i, part := range parts {
|
|
if i > 0 {
|
|
result += "$"
|
|
}
|
|
result += part
|
|
}
|
|
return result
|
|
}
|