From a50557eb7f899a9008964d20ce653457527f0fa0 Mon Sep 17 00:00:00 2001 From: Leslie Hoare Date: Mon, 17 Aug 2026 14:59:11 +0100 Subject: [PATCH] Fix NameError in async-http adapter on protocol-http1 >= 0.40 protocol-http1 0.40.0 removed Protocol::HTTP1::Reason in favour of Protocol::HTTP::Status, but the async-http adapter still reads Protocol::HTTP1::Reason::DESCRIPTIONS when building a WebMock response. Any real response passing through the adapter raises: NameError: uninitialized constant Protocol::HTTP1::Reason async_http_client_adapter.rb:115 in `build_webmock_response' Stubbed responses never reach that method, so a suite stays green until something makes a real request. It surfaced for us via a library that recently moved its HTTP transport to async-http: every pass-through request raised, and the failure looked nothing like a WebMock problem. Resolves the description table on first use rather than at load, so it does not depend on which of the two libraries is required by then, and keeps working either side of protocol-http1 0.40.0. The spec helper's build_hash_response had the same reference and is routed through the same method. One existing example does reach the broken line, the cross-concern one that records a real response from WebMockServer and plays it back: on master it fails with the NameError, and in a full-file run the connection pool then never drains and the run hangs. The added regression test pins the status message itself, which that example does not assert. --- .../async_http_client_adapter.rb | 13 ++++++++++- .../async_http_client_spec.rb | 22 +++++++++++++++++++ .../async_http_client_spec_helper.rb | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/webmock/http_lib_adapters/async_http_client_adapter.rb b/lib/webmock/http_lib_adapters/async_http_client_adapter.rb index 0591bb78..dfedd0df 100644 --- a/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +++ b/lib/webmock/http_lib_adapters/async_http_client_adapter.rb @@ -16,6 +16,17 @@ class AsyncHttpClientAdapter < HttpLibAdapter OriginalAsyncHttpClient = Async::HTTP::Client unless const_defined?(:OriginalAsyncHttpClient) class << self + # protocol-http1 0.40.0 removed Protocol::HTTP1::Reason in favour of + # Protocol::HTTP::Status. Resolved on first use, rather than at load, + # so it does not depend on which of the two is required by then. + def status_descriptions + @status_descriptions ||= if defined?(::Protocol::HTTP1::Reason) + ::Protocol::HTTP1::Reason::DESCRIPTIONS + else + ::Protocol::HTTP::Status::DESCRIPTIONS + end + end + def enable! Async::HTTP.send(:remove_const, :Client) Async::HTTP.send(:const_set, :Client, Async::HTTP::WebMockClientWrapper) @@ -112,7 +123,7 @@ def build_webmock_response(response) webmock_response = WebMock::Response.new webmock_response.status = [ response.status, - ::Protocol::HTTP1::Reason::DESCRIPTIONS[response.status] + WebMock::HttpLibAdapters::AsyncHttpClientAdapter.status_descriptions[response.status] ] webmock_response.headers = build_webmock_response_headers(response) webmock_response.body = body diff --git a/spec/acceptance/async_http_client/async_http_client_spec.rb b/spec/acceptance/async_http_client/async_http_client_spec.rb index 038954a8..ede98c22 100644 --- a/spec/acceptance/async_http_client/async_http_client_spec.rb +++ b/spec/acceptance/async_http_client/async_http_client_spec.rb @@ -156,6 +156,28 @@ expect(callback_invoked).to eq(true) end + context 'with a real response' do + # build_webmock_response is the only place the adapter reads a status + # description, and only real responses reach it. + let(:server_url) { "http://#{WebMockServer.instance.host_with_port}/" } + + after do + WebMock.reset_callbacks + WebMock.disable_net_connect! + end + + it 'builds a webmock response with a status message' do + WebMock.allow_net_connect! + + recorded_response = nil + WebMock.after_request { |_request, response| recorded_response = response } + + make_request(:get, server_url) + + expect(recorded_response.status).to eq([200, 'OK']) + end + end + context 'scheme and protocol' do let(:default_response_headers) { {} } diff --git a/spec/acceptance/async_http_client/async_http_client_spec_helper.rb b/spec/acceptance/async_http_client/async_http_client_spec_helper.rb index bed7b457..fd5cf32c 100644 --- a/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +++ b/spec/acceptance/async_http_client/async_http_client_spec_helper.rb @@ -62,7 +62,7 @@ def build_hash_response(response) { status: response.status.to_s, - message: Protocol::HTTP1::Reason::DESCRIPTIONS[response.status], + message: WebMock::HttpLibAdapters::AsyncHttpClientAdapter.status_descriptions[response.status], headers: build_response_headers(response), body: response.read }