package mw import ( "net/http" "net/http/httptest" "testing" "code.nochebuena.dev/einherjar/core/logz" ) // A resolver that continues the client's X-Request-ID lands in both the context // and the response header. func TestRequestIDFrom_HonoursInbound(t *testing.T) { const inbound = "client-supplied-123" var ctxID string h := RequestIDFrom(func(r *http.Request) string { return r.Header.Get("X-Request-ID") })(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ctxID = logz.GetRequestID(r.Context()) })) req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("X-Request-ID", inbound) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if ctxID != inbound { t.Errorf("context request id = %q, want %q", ctxID, inbound) } if got := rec.Header().Get("X-Request-ID"); got != inbound { t.Errorf("response header = %q, want %q", got, inbound) } } // Deliberate regression: RequestID(gen) always generates and never honours an // inbound X-Request-ID. Callers that want to continue a client id use RequestIDFrom. func TestRequestID_AlwaysGenerates_IgnoresInbound(t *testing.T) { const inbound = "client-supplied-123" const generated = "generated-999" var ctxID string h := RequestID(func() string { return generated })( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ctxID = logz.GetRequestID(r.Context()) })) req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("X-Request-ID", inbound) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if ctxID != generated { t.Errorf("context request id = %q, want generated %q (inbound must be ignored)", ctxID, generated) } if got := rec.Header().Get("X-Request-ID"); got != generated { t.Errorf("response header = %q, want %q", got, generated) } } // The resolver runs exactly once per request. func TestRequestIDFrom_ResolverCalledOnce(t *testing.T) { calls := 0 h := RequestIDFrom(func(*http.Request) string { calls++ return "id" })(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) if calls != 1 { t.Errorf("resolver called %d times, want 1", calls) } } // An empty resolver result attaches nothing: no context id and no response header, // rather than a silently-empty value. func TestRequestIDFrom_EmptyResult_AttachesNothing(t *testing.T) { var ctxID string h := RequestIDFrom(func(*http.Request) string { return "" })( http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ctxID = logz.GetRequestID(r.Context()) })) req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("X-Request-ID", "should-be-ignored") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if ctxID != "" { t.Errorf("context request id = %q, want empty (nothing attached)", ctxID) } if vals := rec.Header().Values("X-Request-ID"); len(vals) != 0 { t.Errorf("X-Request-ID header = %v on empty resolve; want absent", vals) } }