package devianter import ( "net/http" "strings" "testing" ) // Regression: request() used to call try(e) and then dereference resp (nil on a // transport error), panicking. From UpdateCSRF's goroutine that panic was // unrecovered and killed the whole process, so the container crash-looped. func TestRequestTransportFailureDoesNotPanic(t *testing.T) { // Port 1 on loopback: nothing listening, so the dial fails fast. r := request("http://127.0.0.1:1/nope") if r.Err == nil { t.Fatal("expected Err to be set on a transport failure") } if r.Status != 0 { t.Fatalf("expected Status 0 on a failed request, got %d", r.Status) } if r.Body != "" { t.Fatalf("expected empty Body on a failed request, got %q", r.Body) } } func TestLooksLikeJSON(t *testing.T) { jsonResp := reqrt{Body: `{"ok":true}`, Headers: http.Header{}} jsonResp.Headers.Set("Content-Type", "application/json; charset=utf-8") if !looksLikeJSON(jsonResp) { t.Error("a JSON body with a JSON content-type should look like JSON") } htmlResp := reqrt{Body: "nope", Headers: http.Header{}} htmlResp.Headers.Set("Content-Type", "text/html") if looksLikeJSON(htmlResp) { t.Error("an HTML error page must never be treated as JSON") } } // A CloudFront block is the exact failure that produced `invalid character '<'`; // it should now be reported in plain language. func TestDescribeDetectsCloudFrontBlock(t *testing.T) { r := reqrt{ Status: 403, Body: "

403 ERROR

Request blocked.\nGenerated by cloudfront (CloudFront)", Headers: http.Header{}, } r.Headers.Set("Content-Type", "text/html") msg := describe(r) if !strings.Contains(msg, "CloudFront/WAF") { t.Errorf("want a CloudFront/WAF hint, got %q", msg) } if !strings.Contains(msg, "403") { t.Errorf("want the HTTP status in the message, got %q", msg) } } // DA's own errors are JSON and must pass through intact for callers to unmarshal. func TestDescribePassesThroughAPIJSON(t *testing.T) { body := `{"error":"invalid_request","errorDescription":"Invalid or expired form submission"}` r := reqrt{Status: 400, Body: body, Headers: http.Header{}} r.Headers.Set("Content-Type", "application/json") if got := describe(r); got != body { t.Errorf("JSON API errors should pass through unchanged:\n got %q\nwant %q", got, body) } } // APIError must not emit a JSON parse error for a non-JSON (e.g. CDN block) body. func TestAPIErrorHandlesNonJSON(t *testing.T) { e := APIError(&stringErr{"devianter: HTTP 403 non-JSON response — blocked"}) if e.Reason != "request_failed" { t.Errorf("want Reason=request_failed for non-JSON errors, got %q", e.Reason) } if !strings.Contains(e.Error, "blocked") { t.Errorf("want the underlying message preserved, got %q", e.Error) } } type stringErr struct{ s string } func (e *stringErr) Error() string { return e.s }