diff --git a/gems/instruments-ruby4.gemfile b/gems/instruments-ruby4.gemfile index 6831864e..c616132b 100644 --- a/gems/instruments-ruby4.gemfile +++ b/gems/instruments-ruby4.gemfile @@ -10,4 +10,5 @@ gem 'moped' gem 'actionpack' gem 'actionview' gem 'httpx' +gem 'webmock' gem 'grape' diff --git a/gems/instruments.gemfile b/gems/instruments.gemfile index 7e0124de..2c25febd 100644 --- a/gems/instruments.gemfile +++ b/gems/instruments.gemfile @@ -7,6 +7,7 @@ gem 'moped' gem 'actionpack' gem 'actionview' gem 'httpx' +gem 'webmock' gem 'grape' # json 3.0 (released 2026-09) dropped the `quirks_mode` keyword that Grape 3's # ActiveSupport JSON formatter still passes, so any Grape 3 JSON request raises diff --git a/gems/typhoeus.gemfile b/gems/typhoeus.gemfile index 12dcfb9a..790a1e75 100644 --- a/gems/typhoeus.gemfile +++ b/gems/typhoeus.gemfile @@ -1,3 +1,4 @@ eval_gemfile("../Gemfile") gem 'typhoeus' +gem 'webmock' diff --git a/test/unit/instruments/httpx_test.rb b/test/unit/instruments/httpx_test.rb index ac48a023..dab38f93 100644 --- a/test/unit/instruments/httpx_test.rb +++ b/test/unit/instruments/httpx_test.rb @@ -4,17 +4,34 @@ require 'scout_apm/instruments/httpx' require 'httpx' + require 'webmock' class HTTPXTest < Minitest::Test + include WebMock::API + def setup + super # clears Thread.current[:scout_request] so a stale request from a + # prior test can't swallow this test's layers + WebMock.enable! + WebMock.disable_net_connect! + @context = ScoutApm::AgentContext.new @recorder = FakeRecorder.new ScoutApm::Agent.instance.context.recorder = @recorder ScoutApm::Instruments::HTTPX.new(@context).install(prepend: false) end + def teardown + WebMock.reset! + WebMock.allow_net_connect! + WebMock.disable! + super + end def test_httpx + stub_request(:get, /news\.ycombinator\.com/).to_return(status: 200, body: "") + stub_request(:get, /google\.com/).to_return(status: 200, body: "") + responses = HTTPX.get( "https://news.ycombinator.com/news", "https://news.ycombinator.com/news?p=2", @@ -27,11 +44,18 @@ def test_httpx end def test_httpx_post_request + stub_request(:post, /httpbin\.org\/post/).to_return(status: 200, body: "") + HTTPX.post("https://httpbin.org/post", json: { test: "data" }) assert_recorded(@recorder, "HTTP", "POST", "httpbin.org/post") end def test_instruments_httpx_error_handling + # An error response still flows through Session#request, so the layer is + # started and stopped, and the request is recorded exactly once. + stub_request(:get, /thisshouldnotexistatall12345\.com/) + .to_return(status: 500, body: "") + begin HTTPX.get("https://thisshouldnotexistatall12345.com") rescue @@ -41,6 +65,10 @@ def test_instruments_httpx_error_handling end def test_httpx_request_retry + # A timed-out request is still dispatched through Session#request, so the + # layer is recorded even though the response errors. + stub_request(:get, /httpbin\.org\/delay/).to_timeout + begin HTTPX.with(timeout: { connect_timeout: 0.25, request_timeout: 0.25 }) .get("https://httpbin.org/delay/5") @@ -50,14 +78,17 @@ def test_httpx_request_retry end def test_multiple_plugins + stub_request(:get, /news\.ycombinator\.com/).to_return(status: 200, body: "") + stub_request(:get, "http://httpbin.org/redirect/2") + .to_return(status: 302, headers: { "Location" => "http://httpbin.org/redirect/1" }) + stub_request(:get, "http://httpbin.org/redirect/1") + .to_return(status: 302, headers: { "Location" => "http://httpbin.org/get" }) + stub_request(:get, "http://httpbin.org/get").to_return(status: 200, body: "") + session = HTTPX.plugin(:persistent).plugin(:follow_redirects) session.get("https://news.ycombinator.com/news") - begin - session.get("http://httpbin.org/redirect/2") - rescue - skip "httpbin.org not available" - end + session.get("http://httpbin.org/redirect/2") assert_equal 2, @recorder.requests.length, "Expected 2 requests to be recorded" end diff --git a/test/unit/instruments/typhoeus_test.rb b/test/unit/instruments/typhoeus_test.rb index bf59e05f..f5630c14 100644 --- a/test/unit/instruments/typhoeus_test.rb +++ b/test/unit/instruments/typhoeus_test.rb @@ -4,15 +4,31 @@ require 'scout_apm/instruments/typhoeus' require 'typhoeus' + require 'webmock' class TyphoeusTest < Minitest::Test + include WebMock::API + def setup + super # clears Thread.current[:scout_request] so a stale request from a + # prior test can't swallow this test's layers + WebMock.enable! + WebMock.disable_net_connect! + stub_request(:any, /example\.com/).to_return(status: 200, body: "") + @context = ScoutApm::AgentContext.new @recorder = FakeRecorder.new ScoutApm::Agent.instance.context.recorder = @recorder ScoutApm::Instruments::Typhoeus.new(@context).install(prepend: false) end + def teardown + WebMock.reset! + WebMock.allow_net_connect! + WebMock.disable! + super + end + def test_instruments_typhoeus_hydra hydra = Typhoeus::Hydra.new 2.times.map{ hydra.queue(Typhoeus::Request.new("example.com", followlocation: true)) } @@ -39,4 +55,4 @@ def assert_recorded(recorder, type, name, desc = nil) assert_equal desc, req.root_layer.desc if !desc.nil? end end -end \ No newline at end of file +end