Sign in or sign up before continuing. Don't have an account yet? Register now to get started.
Register now
WebMock adapter: body.present? returns false even when body has content after v1.7.1
After upgrading from httpx 1.7.0 to 1.7.1, the `ResponseBodyMethods` override in the WebMock adapter causes `body.present?` to return `false` even when the response body contains data. This breaks code that checks for body presence before parsing. ## Environment - **httpx version:** 1.7.1 - **Ruby version:** 3.4.8 - **WebMock:** Enabled (using httpx's built-in adapter) - **Test framework:** Minitest with VCR ## Steps to Reproduce ```ruby require 'webmock' require 'httpx' require 'httpx/adapters/webmock' WebMock.enable! WebMock.disable_net_connect! # Stub a request with JSON error response WebMock.stub_request(:post, "https://example.com/api") .to_return_json( status: 429, body: { error: { message: "Too many requests." } } ) # Make the request response = HTTPX.post("https://example.com/api") # Check body state puts "response.status: #{response.status}" # => 429 puts "response.body.class: #{response.body.class}" # => HTTPX::Response::Body(plugin)/WebMock::HttpLibAdapters::Plugin puts "response.body.present?: #{response.body.present?}" # => false ❌ (UNEXPECTED) puts "response.body.empty?: #{response.body.empty?}" # => true ❌ (UNEXPECTED) puts "response.body.to_s: '#{response.body.to_s}'" # => '{"error":{"message":"Too many requests."}}' ✓ (CORRECT) puts "response.body.to_s.empty?: #{response.body.to_s.empty?}" # => false ✓ (CORRECT) ``` ### Expected Behavior When a mocked response has body content, body.present? should return true. ### Actual Behavior body.present? returns false even though body.to_s returns the correct content. ## Root Cause In httpx 1.7.1, the `@length` tracking was moved from write() into decode_chunk(): Before (v1.7.0) ``` def write(chunk) chunk = decode_chunk(chunk) size = chunk.bytesize @length += size # ← @length updated in write() transition(:open) @buffer.write(chunk) ... end def decode_chunk(chunk) @inflaters.reverse_each { |inflater| chunk = inflater.call(chunk) } chunk end ``` After (v1.7.1) [ref](https://gitlab.com/os85/httpx/-/blob/v1.7.1/lib/httpx/response/body.rb#L204-211) ``` def write(chunk) chunk = decode_chunk(chunk) # ← @length now updated inside here transition(:open) @buffer.write(chunk) ... end def decode_chunk(chunk) @inflaters.reverse_each { |inflater| chunk = inflater.call(chunk) } @length += chunk.bytesize # ← MOVED HERE chunk end ``` ## The Problem The WebMock adapter overrides decode_chunk to skip inflation for mocked responses [ref](https://gitlab.com/os85/httpx/-/blob/v1.7.1/lib/httpx/adapters/webmock.rb#L92-98): ``` module ResponseBodyMethods def decode_chunk(chunk) return chunk if @response.mocked? # ← Returns early! super # ← Never reaches parent's @length increment end end ``` ### The Chain Reaction 1. WebMock's `decode_chunk` returns early for mocked responses 2. Parent's `@length += chunk.bytesize` never executes 3. `@length` stays at 0 4. `empty?` returns true (checks `@length.zero?`) 5. `present?` returns `false` (calls `!blank?` → `empty?`) 6. Code that checks `body.present?` fails to parse the body Meanwhile: The actual content is still written to `@buffer` (line 139 of webmock.rb), so `body.to_s` works fine. ### Workaround As a workaround, I do `body.to_s.present?` instead of `body.present?` ## Additional Context The issue specifically affects error responses (4xx, 5xx) in tests that use WebMock, where the response body contains error details that need to be parsed. The body content is present and accessible via to_s, but standard presence checks fail. This creates an inconsistency where the same code behaves differently in tests (with WebMock) versus production (real HTTP responses).
issue