feat(13-01): add the prohibited rule and dated-download and notification masks

- lagoon.ValidateRequest supports Laravel 9 prohibited (!required, not
  implicit); with no catalog line its message is validation.prohibited
- tide compares Content-Disposition with real calendar dates masked on both
  sides; a different name, an invalid date or a one-sided date still diffs
- tide.NormalizePublications masks a Carbon +00:00 $.data.payload.created_at
  and an uncaptured positive integer $.data.payload.id as {{id}}
- the album-date test's outside-album sibling moves off payload.created_at,
  which now has its own mask
- READMEs and docs describe the rule and both masks
This commit is contained in:
Jakub Zych
2026-10-03 06:32:46 +02:00
parent 55a4092019
commit 2b94dfd2d2
10 changed files with 243 additions and 9 deletions

View File

@@ -664,3 +664,52 @@ func TestValidateRequestExistsRule(t *testing.T) {
t.Error("a missing table must be an error for arrays too")
}
}
// TestValidateRequestProhibited ports Laravel 9's prohibited rule: it is
// !validateRequired and not implicit, so an absent key and a blank string
// are never validated, null and an empty array pass, and any value required
// would accept fails, 0 and false included. Neither catalog has a
// validation.prohibited line, so the message is the key itself.
func TestValidateRequestProhibited(t *testing.T) {
rules := []RequestRule{{Field: "condition", Rules: ParseRules("prohibited")}}
pass := map[string]map[string]any{
"absent": {},
"null": {"condition": nil},
"empty string": {"condition": ""},
"blank string": {"condition": " "},
"empty array": {"condition": []any{}},
"empty map": {"condition": map[string]any{}},
}
for name, input := range pass {
if got := mustValidate(t, inLocale("pl"), input, rules); len(got) != 0 {
t.Errorf("%s: errors = %v, want none", name, got)
}
}
fail := map[string]any{
"string": "VG",
"zero": json.Number("0"),
"float zero": float64(0),
"false": false,
"true": true,
"non-empty array": []any{"x"},
"non-empty map": map[string]any{"a": 1},
}
for name, value := range fail {
for _, loc := range []string{"pl", "en"} {
got := mustValidate(t, inLocale(loc), map[string]any{"condition": value}, rules)
want := map[string][]string{"condition": {"validation.prohibited"}}
if !reflect.DeepEqual(got, want) {
t.Errorf("%s (%s): errors = %#v, want %#v", name, loc, got, want)
}
}
}
// Next to other rules, prohibited fails like any non-implicit rule.
mixed := []RequestRule{
{Field: "name", Rules: ParseRules("required|string")},
{Field: "shelf", Rules: ParseRules("prohibited")},
}
got := mustValidate(t, inLocale("en"), map[string]any{"name": "Kind of Blue", "shelf": "A1"}, mixed)
if want := map[string][]string{"shelf": {"validation.prohibited"}}; !reflect.DeepEqual(got, want) {
t.Errorf("mixed = %#v, want %#v", got, want)
}
}