Files
summercms/modules/tide/normalize_phase13_test.go
Jakub Zych 4ed45c1b03 test(13-06): cover the Phase 13 framework edges in surf, conga, lagoon and tide
- surf: a transitive three-route family, HEAD and sorted Allow on family
  paths, a family across two plugins with per-member middleware, refused
  trailing-slash and multi-segment shapes
- conga: refusal messages name kind and queue, a configured queue counts
  as served, a delayed unregistered dispatch waits scheduled
- lagoon: prohibited under a wildcard, on a dotted path and after bail
- tide: quoted, RFC 5987 and multi-date download names; a captured id
  beside a masked notification id
2026-10-03 11:01:58 +02:00

172 lines
8.1 KiB
Go

package tide
import (
"encoding/json"
"strings"
"testing"
)
// TestNormalizeContentDispositionDate: a download name built from the day
// of the request replays on a later day, while a different stem, an invalid
// date, a date on one side only or a missing header still diff.
func TestNormalizeContentDispositionDate(t *testing.T) {
const recorded = "attachment; filename=export-2026-09-17.csv"
cases := []struct {
name string
got map[string]string
diff bool
}{
{"same stem on a later day", map[string]string{"Content-Disposition": "attachment; filename=export-2026-10-03.csv"}, false},
{"same day", map[string]string{"Content-Disposition": recorded}, false},
{"header name case", map[string]string{"content-disposition": "attachment; filename=export-2027-01-01.csv"}, false},
{"different stem", map[string]string{"Content-Disposition": "attachment; filename=report-2026-10-03.csv"}, true},
{"invalid date", map[string]string{"Content-Disposition": "attachment; filename=export-2026-13-45.csv"}, true},
{"date on one side only", map[string]string{"Content-Disposition": "attachment; filename=export.csv"}, true},
{"inline instead of attachment", map[string]string{"Content-Disposition": "inline; filename=export-2026-10-03.csv"}, true},
{"header absent", map[string]string{}, true},
}
for _, c := range cases {
diffs := compareHeaders(map[string]string{"Content-Disposition": recorded}, c.got, nil)
if (len(diffs) > 0) != c.diff {
t.Errorf("%s: diffs = %+v, want diff=%v", c.name, diffs, c.diff)
}
}
// A recorded value that holds an invalid date keeps the byte comparison.
odd := "attachment; filename=export-2026-02-30.csv"
if diffs := compareHeaders(map[string]string{"Content-Disposition": odd}, map[string]string{"Content-Disposition": "attachment; filename=export-2026-02-28.csv"}, nil); len(diffs) != 1 {
t.Errorf("invalid recorded date: diffs = %+v, want one", diffs)
}
// Two dates must both be real and both present.
two := "attachment; filename=export-2026-09-01-2026-09-17.csv"
if diffs := compareHeaders(map[string]string{"Content-Disposition": two}, map[string]string{"Content-Disposition": "attachment; filename=export-2026-10-01-2026-10-03.csv"}, nil); len(diffs) != 0 {
t.Errorf("two dates: diffs = %+v, want none", diffs)
}
// The mask applies to Content-Disposition only.
if diffs := compareHeaders(map[string]string{"Location": "/files/2026-09-17"}, map[string]string{"Location": "/files/2026-10-03"}, nil); len(diffs) != 1 {
t.Errorf("Location: diffs = %+v, want one", diffs)
}
if _, n, ok := maskDispositionDates("x12026-09-170"); n != 0 || !ok {
t.Errorf("a date inside a longer digit run is not a date token")
}
}
// TestNormalizeNotificationPublication: a notification:new publication's id
// and created_at under $.data.payload normalize equal across two runs, while
// a Z date, a string id and every other path stay visible.
func TestNormalizeNotificationPublication(t *testing.T) {
store := mustMemoryStore()
pub := func(id, created string) []Publication {
return []Publication{{Method: "POST", Path: "/api/publish", Body: json.RawMessage(
`{"channel":"acme#5","data":{"event":"notification:new","payload":{"id":` + id +
`,"type":"item_added","payload":{"album_id":3,"created_at":"2026-01-01T00:00:00+00:00"},"read_at":null,"created_at":"` + created + `"}}}`)}}
}
a, err := NormalizePublications(pub("10000001", "2026-09-30T11:21:55+00:00"), store)
if err != nil {
t.Fatal(err)
}
b, err := NormalizePublications(pub("42", "2026-10-03T08:00:00+00:00"), store)
if err != nil {
t.Fatal(err)
}
if diffs := DiffPublications(a, b); len(diffs) != 0 {
t.Fatalf("diffs = %+v", diffs)
}
body := string(a[0].Body)
if !strings.Contains(body, `"payload":{"id":{{id}},`) || !strings.HasSuffix(body, `"read_at":null,"created_at":"{{datetime}}"}}}`) {
t.Fatalf("notification not masked: %s", body)
}
// Only $.data.payload.created_at is masked, not a nested payload date.
if !strings.Contains(body, `"album_id":3,"created_at":"2026-01-01T00:00:00+00:00"`) {
t.Fatalf("over-normalised: %s", body)
}
// A Z date, a zero or negative id and a string id stay visible.
for _, c := range []struct{ id, created, want string }{
{"42", "2026-10-03T08:00:00Z", `"created_at":"2026-10-03T08:00:00Z"`},
{`"42"`, "2026-10-03T08:00:00+00:00", `"payload":{"id":"42",`},
{"0", "2026-10-03T08:00:00+00:00", `"payload":{"id":0,`},
{"-3", "2026-10-03T08:00:00+00:00", `"payload":{"id":-3,`},
{"4.5", "2026-10-03T08:00:00+00:00", `"payload":{"id":4.5,`},
} {
got, err := NormalizePublications(pub(c.id, c.created), store)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got[0].Body), c.want) {
t.Errorf("id %s created %s: %s lacks %s", c.id, c.created, got[0].Body, c.want)
}
if diffs := DiffPublications(a, got); len(diffs) == 0 {
t.Errorf("id %s created %s must diff against the masked publication", c.id, c.created)
}
}
// A captured id keeps its own placeholder.
store.Set("id:note", "77")
got, err := NormalizePublications(pub("77", "2026-10-03T08:00:00+00:00"), store)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got[0].Body), `"payload":{"id":{{id:note}},`) {
t.Fatalf("captured id: %s", got[0].Body)
}
// An album publication's existing masks are unchanged.
album := []Publication{{Method: "POST", Path: "/api/publish", Body: json.RawMessage(
`{"channel":"c","data":{"payload":{"album":{"created_at":"2026-09-30T11:21:55+00:00"},"actor":{"user_id":1,"name":null},"timestamp":"2026-09-30T11:21:55+00:00"}}}`)}}
got, err = NormalizePublications(album, store)
if err != nil {
t.Fatal(err)
}
want := `{"channel":"c","data":{"payload":{"album":{"created_at":"{{datetime}}"},"actor":"{{actor}}","timestamp":"{{timestamp}}"}}}`
if string(got[0].Body) != want {
t.Fatalf("album publication\n got %s\nwant %s", got[0].Body, want)
}
}
// TestNormalizePhase13Edges: the download-date mask also holds for a
// quoted filename, an RFC 5987 filename* and three dates in one name,
// while a changed count of dates diffs; a notification publication keeps
// a captured id beside its masked own id.
func TestNormalizePhase13Edges(t *testing.T) {
for _, c := range []struct {
name, recorded, got string
diff bool
}{
{"quoted", `attachment; filename="export-2026-09-17.csv"`, `attachment; filename="export-2026-10-03.csv"`, false},
{"quoted stem change", `attachment; filename="export-2026-09-17.csv"`, `attachment; filename="backup-2026-10-03.csv"`, true},
{"rfc5987", `attachment; filename*=UTF-8''p%C5%82yty-2026-09-17.csv`, `attachment; filename*=UTF-8''p%C5%82yty-2026-10-03.csv`, false},
{"three dates", "attachment; filename=a-2026-01-01-2026-02-01-2026-03-01.csv", "attachment; filename=a-2027-01-01-2027-02-01-2027-03-01.csv", false},
{"one date fewer", "attachment; filename=a-2026-01-01-2026-02-01.csv", "attachment; filename=a-2026-01-01.csv", true},
{"leap day", "attachment; filename=a-2028-02-29.csv", "attachment; filename=a-2026-10-03.csv", false},
{"not a leap year", "attachment; filename=a-2026-02-29.csv", "attachment; filename=a-2026-10-03.csv", true},
} {
diffs := compareHeaders(map[string]string{"Content-Disposition": c.recorded}, map[string]string{"Content-Disposition": c.got}, nil)
if (len(diffs) > 0) != c.diff {
t.Errorf("%s: diffs = %+v, want diff=%v", c.name, diffs, c.diff)
}
}
store := mustMemoryStore()
store.Set("id:album", "5")
pub := func(id, albumID string) []Publication {
return []Publication{{Method: "POST", Path: "/api/publish", Body: json.RawMessage(
`{"channel":"acme#9","data":{"event":"notification:new","payload":{"id":` + id +
`,"type":"item_added","payload":{"album_id":` + albumID + `},"read_at":null,"created_at":"2026-10-03T08:00:00+00:00"}}}`)}}
}
got, err := NormalizePublications(pub("31", "5"), store)
if err != nil {
t.Fatal(err)
}
body := string(got[0].Body)
if !strings.Contains(body, `"payload":{"id":{{id}},`) || !strings.Contains(body, `"album_id":{{id:album}}`) {
t.Fatalf("masks beside a captured id: %s", body)
}
other, err := NormalizePublications(pub("32", "6"), store)
if err != nil {
t.Fatal(err)
}
if diffs := DiffPublications(got, other); len(diffs) == 0 {
t.Fatal("a different album id must still diff")
}
}