All checks were successful
check / check (push) Successful in 1m37s
Refactor Dockerfile to use a separate lint stage with a pinned golangci-lint v2.11.3 Docker image instead of installing golangci-lint via curl in the builder stage. This follows the pattern used by sneak/pixa. Changes: - Dockerfile: separate lint stage using golangci/golangci-lint:v2.11.3 (Debian-based, pinned by sha256) with COPY --from=lint dependency - Bump Go from 1.24 to 1.26.1 (golang:1.26.1-bookworm, pinned) - Bump golangci-lint from v1.64.8 to v2.11.3 - Migrate .golangci.yml from v1 to v2 format (same linters, format only) - All Docker images pinned by sha256 digest - Fix all lint issues from the v2 linter upgrade: - Add package comments to all packages - Add doc comments to all exported types, functions, and methods - Fix unchecked errors (errcheck) - Fix unused parameters (revive) - Fix gosec warnings (MaxBytesReader for form parsing) - Fix staticcheck suggestions (fmt.Fprintf instead of WriteString) - Rename DeliveryTask to Task to avoid stutter (delivery.Task) - Rename shadowed builtin 'max' parameter - Update README.md version requirements
194 lines
3.4 KiB
Go
194 lines
3.4 KiB
Go
package database_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
func TestGenerateRandomPassword(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
length int
|
|
}{
|
|
{"Short password", 8},
|
|
{"Medium password", 16},
|
|
{"Long password", 32},
|
|
{"Very short password", 3},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
password, err := database.GenerateRandomPassword(
|
|
tt.length,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf(
|
|
"GenerateRandomPassword() error = %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
if len(password) != tt.length {
|
|
t.Errorf(
|
|
"Password length = %v, want %v",
|
|
len(password), tt.length,
|
|
)
|
|
}
|
|
|
|
checkPasswordComplexity(
|
|
t, password, tt.length,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkPasswordComplexity(
|
|
t *testing.T,
|
|
password string,
|
|
length int,
|
|
) {
|
|
t.Helper()
|
|
|
|
// For passwords >= 4 chars, check complexity
|
|
if length < 4 {
|
|
return
|
|
}
|
|
|
|
flags := classifyChars(password)
|
|
|
|
if !flags[0] || !flags[1] || !flags[2] || !flags[3] {
|
|
t.Errorf(
|
|
"Password lacks required complexity: "+
|
|
"upper=%v, lower=%v, digit=%v, special=%v",
|
|
flags[0], flags[1], flags[2], flags[3],
|
|
)
|
|
}
|
|
}
|
|
|
|
func classifyChars(s string) [4]bool {
|
|
var flags [4]bool // upper, lower, digit, special
|
|
|
|
for _, char := range s {
|
|
switch {
|
|
case char >= 'A' && char <= 'Z':
|
|
flags[0] = true
|
|
case char >= 'a' && char <= 'z':
|
|
flags[1] = true
|
|
case char >= '0' && char <= '9':
|
|
flags[2] = true
|
|
case strings.ContainsRune(
|
|
"!@#$%^&*()_+-=[]{}|;:,.<>?",
|
|
char,
|
|
):
|
|
flags[3] = true
|
|
}
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
func TestGenerateRandomPasswordUniqueness(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Generate multiple passwords and ensure they're different
|
|
passwords := make(map[string]bool)
|
|
|
|
const numPasswords = 100
|
|
|
|
for range numPasswords {
|
|
password, err := database.GenerateRandomPassword(16)
|
|
if err != nil {
|
|
t.Fatalf(
|
|
"GenerateRandomPassword() error = %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
if passwords[password] {
|
|
t.Errorf(
|
|
"Duplicate password generated: %s",
|
|
password,
|
|
)
|
|
}
|
|
|
|
passwords[password] = true
|
|
}
|
|
}
|
|
|
|
func TestHashPassword(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
password := "testPassword123!"
|
|
|
|
hash, err := database.HashPassword(password)
|
|
if err != nil {
|
|
t.Fatalf("HashPassword() error = %v", err)
|
|
}
|
|
|
|
// Check that hash has correct format
|
|
if !strings.HasPrefix(hash, "$argon2id$") {
|
|
t.Errorf(
|
|
"Hash doesn't have correct prefix: %s",
|
|
hash,
|
|
)
|
|
}
|
|
|
|
// Verify password
|
|
valid, err := database.VerifyPassword(password, hash)
|
|
if err != nil {
|
|
t.Fatalf("VerifyPassword() error = %v", err)
|
|
}
|
|
|
|
if !valid {
|
|
t.Error(
|
|
"VerifyPassword() returned false " +
|
|
"for correct password",
|
|
)
|
|
}
|
|
|
|
// Verify wrong password fails
|
|
valid, err = database.VerifyPassword(
|
|
"wrongPassword", hash,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("VerifyPassword() error = %v", err)
|
|
}
|
|
|
|
if valid {
|
|
t.Error(
|
|
"VerifyPassword() returned true " +
|
|
"for wrong password",
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestHashPasswordUniqueness(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
password := "testPassword123!"
|
|
|
|
// Same password should produce different hashes
|
|
hash1, err := database.HashPassword(password)
|
|
if err != nil {
|
|
t.Fatalf("HashPassword() error = %v", err)
|
|
}
|
|
|
|
hash2, err := database.HashPassword(password)
|
|
if err != nil {
|
|
t.Fatalf("HashPassword() error = %v", err)
|
|
}
|
|
|
|
if hash1 == hash2 {
|
|
t.Error(
|
|
"Same password produced identical hashes " +
|
|
"(salt not working)",
|
|
)
|
|
}
|
|
}
|