auth-ui: rewrite ui
Change-Id: I6f00867015ec77aa7e336e89da4dc1b081e330c6
diff --git a/core/auth/ui/validation_test.go b/core/auth/ui/validation_test.go
new file mode 100644
index 0000000..437376b
--- /dev/null
+++ b/core/auth/ui/validation_test.go
@@ -0,0 +1,75 @@
+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)
+ }
+}