feat(lagoon): check min and max as length on every non-numeric field (quick-261104-fx1)
- min and max compare the characters of a string or a number's decimal form and the element count of a slice or map on any field without numeric or integer (Laravel's getSize), with the min.array and max.array messages for lists - blank values, booleans, dates and byte slices are not measured; a malformed bound is an error on every non-numeric field - reverse the 261104-f4x pin, prove it through the cabana settings save sequence, and update the README, docs and the storePassword comment
This commit is contained in:
@@ -415,9 +415,9 @@ func TestValidateStringURLTranslated(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateStringLength: on a string or url field min and max compare
|
||||
// the length in characters (runes, Laravel's mb_strlen) with the string
|
||||
// size messages; rule strings without string or url keep their old results.
|
||||
// TestValidateStringLength: min and max compare the length in characters
|
||||
// (runes, Laravel's mb_strlen) with the string size messages on every field
|
||||
// that is not numeric or integer, with or without string or url.
|
||||
func TestValidateStringLength(t *testing.T) {
|
||||
const longURL = "https://example.com/a-very-long-path/feed.png"
|
||||
const longWords = "this is not a url and it is far too long"
|
||||
@@ -435,10 +435,8 @@ func TestValidateStringLength(t *testing.T) {
|
||||
{"min nil", "string|min:3", nil, nil},
|
||||
{"required min empty", "required|string|min:3", "", []string{"The title field is required."}},
|
||||
{"non-string with max", "string|max:3", 12345, []string{"The title must be a string."}},
|
||||
// Pinned: without string or url, min and max are not checked on a
|
||||
// non-numeric field (unchanged behaviour, see the follow-up).
|
||||
{"max without string", "max:5", "0123456789", nil},
|
||||
{"nullable max without string", "nullable|max:5", "0123456789", nil},
|
||||
{"max without string", "max:5", "0123456789", []string{"The title may not be greater than 5 characters."}},
|
||||
{"nullable max without string", "nullable|max:5", "0123456789", []string{"The title may not be greater than 5 characters."}},
|
||||
{"string between keeps its check", "string|between:1,5", "0123456789", []string{"The title must be between 1 and 5 characters."}},
|
||||
{"string between passes", "string|between:1,5", "abc", nil},
|
||||
})
|
||||
@@ -458,3 +456,88 @@ func TestValidateStringLength(t *testing.T) {
|
||||
t.Fatalf("malformed min: err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateLengthWithoutStringRule: on a field without numeric or integer,
|
||||
// min and max are Laravel's getSize: the characters of a string or of a
|
||||
// number's decimal form and the element count of a slice or map. Blank
|
||||
// values, booleans, dates and byte slices are not measured, and a bound that
|
||||
// is not a non-negative whole number is an error whatever the value.
|
||||
func TestValidateLengthWithoutStringRule(t *testing.T) {
|
||||
empty, long := "", "abcdef"
|
||||
tooLong := []string{"The title may not be greater than 5 characters."}
|
||||
runRuleCases(t, "title", []ruleCase{
|
||||
{"255 polish letters", "required|max:255", strings.Repeat("ż", 255), nil},
|
||||
{"256 polish letters", "required|max:255", strings.Repeat("ż", 256), []string{"The title may not be greater than 255 characters."}},
|
||||
{"nil", "nullable|max:5", nil, nil},
|
||||
{"empty", "nullable|max:5", "", nil},
|
||||
{"blank", "nullable|max:5", " ", nil},
|
||||
{"nil pointer", "nullable|max:5", (*string)(nil), nil},
|
||||
{"pointer to empty", "nullable|max:5", &empty, nil},
|
||||
{"at max", "nullable|max:5", "abcde", nil},
|
||||
{"over max", "nullable|max:5", "abcdef", tooLong},
|
||||
{"pointer over max", "nullable|max:5", &long, tooLong},
|
||||
{"below min", "min:4", "abc", []string{"The title must be at least 4 characters."}},
|
||||
{"min empty", "min:4", "", nil},
|
||||
{"min nil", "min:4", nil, nil},
|
||||
{"min blank", "min:4", " ", nil},
|
||||
{"required min empty", "required|min:4", "", []string{"The title field is required."}},
|
||||
{"email under max", "required|email|max:255", "a@example.com", nil},
|
||||
{"int digits over max", "max:4", 12345, []string{"The title may not be greater than 4 characters."}},
|
||||
{"int digits at max", "max:4", 2024, nil},
|
||||
{"float digits at max", "max:4", float64(2024), nil},
|
||||
{"float form over max", "max:2", float64(1.5), []string{"The title may not be greater than 2 characters."}},
|
||||
{"bool not measured", "max:0", true, nil},
|
||||
{"time not measured", "max:1", time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC), nil},
|
||||
{"date not measured", "max:1", NewDate(2026, 11, 4), nil},
|
||||
})
|
||||
runRuleCases(t, "tags", []ruleCase{
|
||||
{"slice over max", "max:2", []string{"a", "b", "c"}, []string{"The tags may not have more than 2 items."}},
|
||||
{"slice at max", "max:2", []any{"a", "b"}, nil},
|
||||
{"slice below min", "min:2", []string{"a"}, []string{"The tags must have at least 2 items."}},
|
||||
{"empty slice skips", "min:2", []string{}, nil},
|
||||
{"map over max", "max:1", map[string]any{"a": 1, "b": 2}, []string{"The tags may not have more than 1 items."}},
|
||||
{"bytes not measured", "max:3", []byte("abcdef"), nil},
|
||||
})
|
||||
runRuleCases(t, "views", []ruleCase{
|
||||
{"integer max", "integer|max:10", 11, []string{"The views may not be greater than 10."}},
|
||||
{"numeric range", "numeric|min:0|max:10", "5", nil},
|
||||
})
|
||||
malformed := []struct {
|
||||
rule string
|
||||
value any
|
||||
token string
|
||||
}{
|
||||
{"max:abc", "x", "max:abc"},
|
||||
{"max:abc", nil, "max:abc"},
|
||||
{"nullable|min:-1", "abc", "min:-1"},
|
||||
}
|
||||
for _, tc := range malformed {
|
||||
_, err := Validate(t.Context(), nil, nil, map[string]string{"title": tc.rule}, map[string]any{"title": tc.value}, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), tc.token) {
|
||||
t.Fatalf("%q on %#v: err = %v", tc.rule, tc.value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateLengthWithoutStringRuleTranslated: the character and item
|
||||
// messages are the lagoon::validation lines of the request locale.
|
||||
func TestValidateLengthWithoutStringRuleTranslated(t *testing.T) {
|
||||
tr := requestTranslator(t)
|
||||
cases := []struct {
|
||||
field, rule string
|
||||
value any
|
||||
want string
|
||||
}{
|
||||
{"title", "max:5", "abcdef", "title nie może być dłuższy niż 5 znaków."},
|
||||
{"tags", "max:2", []string{"a", "b", "c"}, "tags nie może mieć więcej niż 2 elementów."},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
errs, err := Validate(inLocale("pl"), nil, nil, map[string]string{tc.field: tc.rule}, map[string]any{tc.field: tc.value}, tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := errs[tc.field]; len(got) != 1 || got[0] != tc.want {
|
||||
t.Fatalf("%s %q = %q, want %q", tc.field, tc.rule, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user