package httpx import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestWriteProblem(t *testing.T) { req := httptest.NewRequest("GET", "/api/items/1", nil) req = req.WithContext(WithRequestID(req.Context(), "req-123")) rec := httptest.NewRecorder() WriteProblem(rec, req, http.StatusNotFound, "no such item") if rec.Code != http.StatusNotFound { t.Errorf("status = %d; want 404", rec.Code) } if ct := rec.Header().Get("Content-Type"); ct != "application/problem+json" { t.Errorf("Content-Type = %q", ct) } nosniff := rec.Header().Get("X-Content-Type-Options") if nosniff != "nosniff" { t.Errorf("X-Content-Type-Options = %q", nosniff) } var p Problem if err := json.Unmarshal(rec.Body.Bytes(), &p); err != nil { t.Fatalf("body %q: %v", rec.Body, err) } want := Problem{ Type: "about:blank", Status: http.StatusNotFound, Title: "Not Found", Detail: "no such item", RequestID: "req-123", } if p != want { t.Errorf("problem = %+v; want %+v", p, want) } } // Without a request (or a request ID) the problem has no requestId, and an // empty detail is left out. func TestWriteProblemMinimal(t *testing.T) { for _, req := range []*http.Request{ nil, httptest.NewRequest("GET", "/", nil), } { rec := httptest.NewRecorder() WriteProblem(rec, req, http.StatusTooManyRequests, "") var body map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { t.Fatalf("body %q: %v", rec.Body, err) } want := map[string]any{ "type": "about:blank", "status": float64(http.StatusTooManyRequests), "title": "Too Many Requests", } if len(body) != len(want) { t.Errorf("request %v: body = %v; want %v", req != nil, body, want) } for k, v := range want { if body[k] != v { t.Errorf("request %v: %s = %v; want %v", req != nil, k, body[k], v) } } } } func TestRequestID(t *testing.T) { ctx := context.Background() if id := RequestID(ctx); id != "" { t.Errorf("RequestID(empty) = %q; want empty", id) } ctx = WithRequestID(ctx, "abc") if id := RequestID(ctx); id != "abc" { t.Errorf("RequestID = %q; want abc", id) } // The latest ID wins; a value of another type under the key is ignored. if id := RequestID(WithRequestID(ctx, "def")); id != "def" { t.Errorf("RequestID after replacing = %q; want def", id) } if id := RequestID(context.WithValue(ctx, requestIDKey, 42)); id != "" { t.Errorf("RequestID(int) = %q; want empty", id) } }