package handler import ( "baron-sso-backend/internal/domain" "crypto/rand" "encoding/binary" "testing" "unicode" ) // 정책을 받아 필수 요구사항을 모두 포함하는 비밀번호를 생성한다. func generatePasswordFromPolicy(policy *domain.PasswordPolicy) string { minLen := policy.MinLength if minLen < 8 { minLen = 12 // 안전한 기본값 } pwd := make([]rune, 0, minLen) if policy.Lowercase { pwd = append(pwd, 'a') } if policy.Uppercase { pwd = append(pwd, 'B') } if policy.Number { pwd = append(pwd, '3') } if policy.NonAlphanumeric { pwd = append(pwd, '!') } charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" for len(pwd) < minLen { pwd = append(pwd, rune(charset[randomInt(len(charset))])) } // 섞어서 예측 가능성을 낮춘다. for i := range pwd { j := randomInt(len(pwd)) pwd[i], pwd[j] = pwd[j], pwd[i] } return string(pwd) } func randomInt(n int) int { if n <= 0 { return 0 } var b [8]byte if _, err := rand.Read(b[:]); err != nil { return 0 } return int(binary.BigEndian.Uint64(b[:]) % uint64(n)) } func TestGeneratePasswordUsesNonAlphanumericRequirement(t *testing.T) { policy := &domain.PasswordPolicy{ MinLength: 8, Lowercase: true, Uppercase: true, Number: true, NonAlphanumeric: true, } pwd := generatePasswordFromPolicy(policy) if len(pwd) < policy.MinLength { t.Fatalf("비밀번호 길이가 정책 최소 길이 미만: got %d, want >= %d", len(pwd), policy.MinLength) } var hasLower, hasUpper, hasNumber, hasSymbol bool for _, r := range pwd { switch { case unicode.IsLower(r): hasLower = true case unicode.IsUpper(r): hasUpper = true case unicode.IsNumber(r): hasNumber = true case !unicode.IsLetter(r) && !unicode.IsNumber(r): hasSymbol = true } } if policy.Lowercase && !hasLower { t.Fatalf("소문자 요구사항 미충족: %q", pwd) } if policy.Uppercase && !hasUpper { t.Fatalf("대문자 요구사항 미충족: %q", pwd) } if policy.Number && !hasNumber { t.Fatalf("숫자 요구사항 미충족: %q", pwd) } if policy.NonAlphanumeric && !hasSymbol { t.Fatalf("비영문자 요구사항 미충족: %q", pwd) } }