| package main |
| |
| import ( |
| "reflect" |
| "strings" |
| "testing" |
| ) |
| |
| const ( |
| testUsernameLengthMessage = "Username must be at least 3 characters long." |
| testPasswordLengthMessage = "Password must be at least 20 characters long." |
| testPasswordCompositionMessage = "Password must contain at least one digit, lower&upper case and special characters" |
| ) |
| |
| func TestValidateUsernameBoundaries(t *testing.T) { |
| tests := []struct { |
| name string |
| username string |
| want []ValidationError |
| }{ |
| {"empty", "", []ValidationError{{Field: "username", Message: testUsernameLengthMessage}}}, |
| {"two ASCII bytes", "ab", []ValidationError{{Field: "username", Message: testUsernameLengthMessage}}}, |
| {"three ASCII bytes", "abc", nil}, |
| {"existing byte semantics", "é", []ValidationError{{Field: "username", Message: testUsernameLengthMessage}}}, |
| {"four UTF-8 bytes", "éé", nil}, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| if got := validateUsername(tt.username); !reflect.DeepEqual(got, tt.want) { |
| t.Fatalf("validateUsername() = %#v, want %#v", got, tt.want) |
| } |
| }) |
| } |
| } |
| |
| func TestValidatePasswordLengthAndDeterministicOrdering(t *testing.T) { |
| if got := validatePassword("short"); !reflect.DeepEqual(got, []ValidationError{ |
| {Field: "password", Message: testPasswordLengthMessage}, |
| {Field: "password", Message: testPasswordCompositionMessage}, |
| }) { |
| t.Fatalf("short password errors = %#v", got) |
| } |
| if got := validatePassword("Aa1!" + strings.Repeat("x", 15)); len(got) != 1 || got[0].Message != testPasswordLengthMessage { |
| t.Fatalf("19-byte password errors = %#v", got) |
| } |
| if got := validatePassword("Aa1!" + strings.Repeat("x", 16)); len(got) != 0 { |
| t.Fatalf("20-byte password errors = %#v", got) |
| } |
| } |
| |
| func TestValidatePasswordUnicodeCategories(t *testing.T) { |
| valid := "Éé١ " + strings.Repeat("x", 16) |
| if got := validatePassword(valid); len(got) != 0 { |
| t.Fatalf("Unicode category password errors = %#v", got) |
| } |
| } |
| |
| func TestValidatePasswordAcceptsEveryConfiguredASCIISpecial(t *testing.T) { |
| for _, special := range " !\"#$%&'()*+,-./:;<=>?@[\\]^_{|}~" { |
| t.Run(string(special), func(t *testing.T) { |
| password := "Aa1" + strings.Repeat("x", 16) + string(special) |
| if got := validatePassword(password); len(got) != 0 { |
| t.Fatalf("configured special %q rejected: %#v", special, got) |
| } |
| }) |
| } |
| } |
| |
| func TestValidatePasswordRejectsUnconfiguredPunctuation(t *testing.T) { |
| password := "Aa1" + strings.Repeat("x", 16) + "§" |
| got := validatePassword(password) |
| if !reflect.DeepEqual(got, []ValidationError{{Field: "password", Message: testPasswordCompositionMessage}}) { |
| t.Fatalf("unconfigured punctuation errors = %#v", got) |
| } |
| } |