| @@ -9,6 +9,7 @@ import ( |
| 9 | 9 | "strings" |
| 10 | 10 | "testing" |
| 11 | 11 | "time" |
| 12 | "unicode/utf8" |
| 12 | 13 | |
| 13 | 14 | "gitbay.org/gitbay/internal/config" |
| 14 | 15 | ) |
| @@ -34,11 +35,12 @@ func fakeAPNs(t *testing.T, h http.HandlerFunc) (*Client, *httptest.Server) { |
| 34 | 35 | } |
| 35 | 36 | |
| 36 | 37 | func TestSendShapesTheRequest(t *testing.T) { |
| 37 | | var gotPath, gotTopic, gotType, gotAuth string |
| 38 | var gotPath, gotTopic, gotType, gotAuth, gotCollapse string |
| 38 | 39 | var payload map[string]any |
| 39 | 40 | c, _ := fakeAPNs(t, func(w http.ResponseWriter, r *http.Request) { |
| 40 | 41 | gotPath, gotTopic = r.URL.Path, r.Header.Get("apns-topic") |
| 41 | 42 | gotType, gotAuth = r.Header.Get("apns-push-type"), r.Header.Get("authorization") |
| 43 | gotCollapse = r.Header.Get("apns-collapse-id") |
| 42 | 44 | raw, _ := io.ReadAll(r.Body) |
| 43 | 45 | json.Unmarshal(raw, &payload) |
| 44 | 46 | w.WriteHeader(200) |
| @@ -67,9 +69,11 @@ func TestSendShapesTheRequest(t *testing.T) { |
| 67 | 69 | if payload["path"] != "krz/gitbay/issues/12" { |
| 68 | 70 | t.Fatalf("path = %v", payload["path"]) |
| 69 | 71 | } |
| 70 | | // Collapsing is wrong here: two comments are two notices. |
| 71 | | if _, ok := payload["apns-collapse-id"]; ok { |
| 72 | | t.Fatal("collapse id set") |
| 72 | // Collapsing is wrong here: two comments are two notices. This is an |
| 73 | // APNs HTTP header, not a body field, so it must be checked on the |
| 74 | // request the handler received, not on the decoded JSON payload. |
| 75 | if gotCollapse != "" { |
| 76 | t.Fatalf("apns-collapse-id = %q, want unset", gotCollapse) |
| 73 | 77 | } |
| 74 | 78 | } |
| 75 | 79 | |
| @@ -118,3 +122,29 @@ func TestSendMapsResponses(t *testing.T) { |
| 118 | 122 | }) |
| 119 | 123 | } |
| 120 | 124 | } |
| 125 | |
| 126 | func TestSendTruncatesBodyOnRuneBoundary(t *testing.T) { |
| 127 | var payload map[string]any |
| 128 | c, _ := fakeAPNs(t, func(w http.ResponseWriter, r *http.Request) { |
| 129 | raw, _ := io.ReadAll(r.Body) |
| 130 | json.Unmarshal(raw, &payload) |
| 131 | w.WriteHeader(200) |
| 132 | }) |
| 133 | // A leading ASCII byte shifts every following two-byte rune off an |
| 134 | // even offset, so a raw cut at maxBodyBytes is guaranteed to land on |
| 135 | // the second byte of one of them rather than a rune boundary. |
| 136 | long := "x" + strings.Repeat("é", 2000) |
| 137 | res, _, err := c.Send(context.Background(), "T", "t", long, "p") |
| 138 | if err != nil || res != resultSent { |
| 139 | t.Fatalf("res = %v, err = %v", res, err) |
| 140 | } |
| 141 | aps := payload["aps"].(map[string]any) |
| 142 | alert := aps["alert"].(map[string]any) |
| 143 | body := alert["body"].(string) |
| 144 | if !utf8.ValidString(body) { |
| 145 | t.Fatalf("body is not valid UTF-8: %q", body) |
| 146 | } |
| 147 | if len(body) > maxBodyBytes { |
| 148 | t.Fatalf("body is %d bytes, want <= %d", len(body), maxBodyBytes) |
| 149 | } |
| 150 | } |