diff options
| author | Christian Cleberg <[email protected]> | 2026-07-14 18:32:39 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-14 18:32:39 -0500 |
| commit | 1044b66ee7180d28ade26c8174f3c5e2e1430b16 (patch) | |
| tree | 4df1259342f4ff5bc3b8d9be2f4e7a5178e94eea /util_test.go | |
| parent | b3c99749f133abf6c04506135c2e4e7567da5254 (diff) | |
| download | devianter-1044b66ee7180d28ade26c8174f3c5e2e1430b16.tar.gz devianter-1044b66ee7180d28ade26c8174f3c5e2e1430b16.tar.bz2 devianter-1044b66ee7180d28ade26c8174f3c5e2e1430b16.zip | |
fix: resolve nil-deref crash, CSRF panic, add timeouts + clear CDN-block errors; repoint module to zerolabsco
References: https://github.com/zerolabsco/skunky-art/issues/2
Diffstat (limited to 'util_test.go')
| -rw-r--r-- | util_test.go | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..5576bd4 --- /dev/null +++ b/util_test.go @@ -0,0 +1,84 @@ +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: "<!DOCTYPE HTML><html>nope</html>", 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: "<!DOCTYPE HTML><HTML><H1>403 ERROR</H1>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 } |
