8d631f8fab
Optimized README for better discoverability with keywords: api, habits, self-hosted. Consolidated content to reduce redundancy while maintaining clarity. Added comprehensive test coverage across multiple layers: - Infrastructure: bcrypt hashing, JWT tokens, configuration validation - Application commands: user deletion, password reset, token revocation, email verification - Application queries: login, token refresh - Domain entities: refresh tokens, password reset tokens Coverage increased from 42.5% to 50.8% with meaningful business logic tests. Fixed integration test handler initialization with correct parameters.
218 lines
4.6 KiB
Go
218 lines
4.6 KiB
Go
package crypto
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func TestNewBcryptHasher(t *testing.T) {
|
|
hasher := NewBcryptHasher()
|
|
if hasher == nil {
|
|
t.Fatal("NewBcryptHasher() returned nil")
|
|
}
|
|
|
|
_, ok := hasher.(*BcryptHasher)
|
|
if !ok {
|
|
t.Fatal("NewBcryptHasher() did not return *BcryptHasher")
|
|
}
|
|
}
|
|
|
|
func TestBcryptHasher_Hash(t *testing.T) {
|
|
hasher := NewBcryptHasher()
|
|
|
|
tests := []struct {
|
|
name string
|
|
password string
|
|
}{
|
|
{
|
|
name: "simple password",
|
|
password: "password123",
|
|
},
|
|
{
|
|
name: "complex password",
|
|
password: "P@ssw0rd!123$%^&*()",
|
|
},
|
|
{
|
|
name: "empty password",
|
|
password: "",
|
|
},
|
|
{
|
|
name: "unicode password",
|
|
password: "pässwörd123",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hashed, err := hasher.Hash(tt.password)
|
|
if err != nil {
|
|
t.Fatalf("Hash() error = %v", err)
|
|
}
|
|
|
|
if hashed == "" {
|
|
t.Fatal("Hash() returned empty string")
|
|
}
|
|
|
|
if hashed == tt.password {
|
|
t.Fatal("Hash() returned the same as input password")
|
|
}
|
|
|
|
err = bcrypt.CompareHashAndPassword([]byte(hashed), []byte(tt.password))
|
|
if err != nil {
|
|
t.Fatalf("Generated hash does not match original password: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBcryptHasher_Compare(t *testing.T) {
|
|
hasher := NewBcryptHasher()
|
|
|
|
tests := []struct {
|
|
name string
|
|
password string
|
|
compareWith string
|
|
expectError bool
|
|
errorAssertion func(error) bool
|
|
}{
|
|
{
|
|
name: "matching passwords",
|
|
password: "password123",
|
|
compareWith: "password123",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "non-matching passwords",
|
|
password: "password123",
|
|
compareWith: "wrongpassword",
|
|
expectError: true,
|
|
errorAssertion: func(err error) bool {
|
|
return err == bcrypt.ErrMismatchedHashAndPassword
|
|
},
|
|
},
|
|
{
|
|
name: "empty password comparison",
|
|
password: "",
|
|
compareWith: "",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "unicode password match",
|
|
password: "pässwörd123",
|
|
compareWith: "pässwörd123",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "unicode password mismatch",
|
|
password: "pässwörd123",
|
|
compareWith: "password123",
|
|
expectError: true,
|
|
errorAssertion: func(err error) bool {
|
|
return err == bcrypt.ErrMismatchedHashAndPassword
|
|
},
|
|
},
|
|
{
|
|
name: "case sensitive",
|
|
password: "Password123",
|
|
compareWith: "password123",
|
|
expectError: true,
|
|
errorAssertion: func(err error) bool {
|
|
return err == bcrypt.ErrMismatchedHashAndPassword
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hashed, err := hasher.Hash(tt.password)
|
|
if err != nil {
|
|
t.Fatalf("Hash() error = %v", err)
|
|
}
|
|
|
|
err = hasher.Compare(hashed, tt.compareWith)
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Fatal("Compare() expected error but got nil")
|
|
}
|
|
if tt.errorAssertion != nil && !tt.errorAssertion(err) {
|
|
t.Fatalf("Compare() error = %v, but assertion failed", err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Compare() unexpected error = %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBcryptHasher_CompareWithInvalidHash(t *testing.T) {
|
|
hasher := NewBcryptHasher()
|
|
|
|
tests := []struct {
|
|
name string
|
|
invalidHash string
|
|
password string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "invalid hash format",
|
|
invalidHash: "not-a-valid-hash",
|
|
password: "password123",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "empty hash",
|
|
invalidHash: "",
|
|
password: "password123",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "corrupted hash",
|
|
invalidHash: "$2a$10$invalidhashdata",
|
|
password: "password123",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := hasher.Compare(tt.invalidHash, tt.password)
|
|
if !tt.expectError && err != nil {
|
|
t.Fatalf("Compare() unexpected error = %v", err)
|
|
}
|
|
if tt.expectError && err == nil {
|
|
t.Fatal("Compare() expected error but got nil")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBcryptHasher_HashGeneratesDifferentHashes(t *testing.T) {
|
|
hasher := NewBcryptHasher()
|
|
password := "samePassword123"
|
|
|
|
hash1, err := hasher.Hash(password)
|
|
if err != nil {
|
|
t.Fatalf("Hash() error = %v", err)
|
|
}
|
|
|
|
hash2, err := hasher.Hash(password)
|
|
if err != nil {
|
|
t.Fatalf("Hash() error = %v", err)
|
|
}
|
|
|
|
if hash1 == hash2 {
|
|
t.Fatal("Hash() generated identical hashes for same password (should use salt)")
|
|
}
|
|
|
|
if err := hasher.Compare(hash1, password); err != nil {
|
|
t.Fatalf("First hash doesn't match password: %v", err)
|
|
}
|
|
|
|
if err := hasher.Compare(hash2, password); err != nil {
|
|
t.Fatalf("Second hash doesn't match password: %v", err)
|
|
}
|
|
}
|