diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index be63ae674..a78a590d8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -14,25 +14,27 @@ jobs: matrix: ruby: - head + - '3.2' - '3.1' - '3.0' - '2.7' - '2.6' - - '2.5' - jruby continue-on-error: ${{ matrix.ruby == 'head' }} name: Ruby ${{ matrix.ruby }} env: JRUBY_OPTS: "--debug" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Apt Packages run: | sudo apt-get install libcurl4-openssl-dev -y - uses: ruby/setup-ruby@v1 - continue-on-error: true + continue-on-error: false with: ruby-version: ${{ matrix.ruby }} bundler-cache: true + rubygems: 'latest' + bundler: 'latest' - run: | bundle exec rake diff --git a/README.md b/README.md index 9f1d2a221..17a212325 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ WebMock [![Build Status](https://github.com/bblimke/webmock/workflows/CI/badge.svg?branch=master)](https://github.com/bblimke/webmock/actions) [![Code Climate](https://codeclimate.com/github/bblimke/webmock/badges/gpa.svg)](https://codeclimate.com/github/bblimke/webmock) [![Mentioned in Awesome Ruby](https://awesome.re/mentioned-badge.svg)](https://github.com/markets/awesome-ruby) -[![Inline docs](http://inch-ci.org/github/bblimke/webmock.svg?branch=master)](http://inch-ci.org/github/bblimke/webmock) -[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=webmock&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=webmock&package-manager=bundler&version-scheme=semver) Library for stubbing and setting expectations on HTTP requests in Ruby. @@ -40,12 +38,12 @@ Supported HTTP libraries Supported Ruby Interpreters --------------------------- - -* MRI 2.5 * MRI 2.6 * MRI 2.7 +* MRI 3.0 +* MRI 3.1 +* MRI 3.2 * JRuby -* Rubinius ## Installation @@ -540,7 +538,7 @@ RestClient.get('www.example.org:8080', '/') # ===> Allowed With a `Regexp` matching the URI: ```ruby -WebMock.disable_net_connect!(allow: %r{ample.org/foo}) +WebMock.disable_net_connect!(allow: %r{ample\.org/foo}) RestClient.get('www.example.org', '/foo/bar') # ===> Allowed RestClient.get('sample.org', '/foo') # ===> Allowed diff --git a/lib/webmock/http_lib_adapters/em_http_request_adapter.rb b/lib/webmock/http_lib_adapters/em_http_request_adapter.rb index fc410e09f..b13d4ce81 100644 --- a/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +++ b/lib/webmock/http_lib_adapters/em_http_request_adapter.rb @@ -101,7 +101,7 @@ def setup(response, uri, error = nil) def connection_completed @state = :response_header - send_request(request_signature.headers, request_signature.body) + send_request(*headers_and_body_processed_by_middleware) end def send_request(head, body) @@ -168,12 +168,18 @@ def build_webmock_response webmock_response end - def build_request_signature - headers, body = build_request, @req.body - - @conn.middleware.select {|m| m.respond_to?(:request) }.each do |m| - headers, body = m.request(self, headers, body) + def headers_and_body_processed_by_middleware + @headers_and_body_processed_by_middleware ||= begin + head, body = build_request, @req.body + @conn.middleware.each do |m| + head, body = m.request(self, head, body) if m.respond_to?(:request) + end + [head, body] end + end + + def build_request_signature + headers, body = headers_and_body_processed_by_middleware method = @req.method uri = @req.uri.clone diff --git a/lib/webmock/request_pattern.rb b/lib/webmock/request_pattern.rb index 8fb84e832..7f9437d42 100644 --- a/lib/webmock/request_pattern.rb +++ b/lib/webmock/request_pattern.rb @@ -283,7 +283,7 @@ def matches?(body, content_type = "") matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type) elsif (@pattern).is_a?(Array) matching_body_array?(body_as_hash(body, content_type), @pattern, content_type) - elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher) + elsif (@pattern).is_a?(WebMock::Matchers::HashArgumentMatcher) @pattern == body_as_hash(body, content_type) else empty_string?(@pattern) && empty_string?(body) || diff --git a/lib/webmock/util/version_checker.rb b/lib/webmock/util/version_checker.rb index e8fa4f9b5..a99da32f2 100644 --- a/lib/webmock/util/version_checker.rb +++ b/lib/webmock/util/version_checker.rb @@ -86,7 +86,6 @@ def compare_version when @major < @min_major then :too_low when @major == @min_major && @minor < @min_minor then :too_low when @major == @min_major && @minor == @min_minor && @patch < @min_patch then :too_low - when @max_major && @major > @max_major then :too_high when @max_major && @major == @max_major && @max_minor && @minor > @max_minor then :too_high diff --git a/minitest/test_helper.rb b/minitest/test_helper.rb index 5ea5bd8e0..e5765cef1 100644 --- a/minitest/test_helper.rb +++ b/minitest/test_helper.rb @@ -10,7 +10,7 @@ require 'minitest/autorun' require 'webmock/minitest' -test_class = defined?(MiniTest::Test) ? MiniTest::Test : MiniTest::Unit::TestCase +test_class = defined?(Minitest::Test) ? Minitest::Test : MiniTest::Unit::TestCase test_class.class_eval do @@ -28,7 +28,7 @@ def assert_raise_with_message(e, message, &block) end def assert_fail(message, &block) - assert_raise_with_message(MiniTest::Assertion, message, &block) + assert_raise_with_message(Minitest::Assertion, message, &block) end end diff --git a/minitest/test_webmock.rb b/minitest/test_webmock.rb index 6fd0b0b6f..ca7b8c174 100644 --- a/minitest/test_webmock.rb +++ b/minitest/test_webmock.rb @@ -1,7 +1,7 @@ require File.expand_path(File.dirname(__FILE__) + '/test_helper') require File.expand_path(File.dirname(__FILE__) + '/../test/shared_test') -test_class = defined?(MiniTest::Test) ? MiniTest::Test : MiniTest::Unit::TestCase +test_class = defined?(Minitest::Test) ? Minitest::Test : MiniTest::Unit::TestCase class MiniTestWebMock < test_class 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 07389cc79..6196657bf 100644 --- a/spec/acceptance/async_http_client/async_http_client_spec.rb +++ b/spec/acceptance/async_http_client/async_http_client_spec.rb @@ -3,11 +3,11 @@ require 'acceptance/webmock_shared' require_relative './async_http_client_spec_helper' -require 'protocol/http/body/file' +unless RUBY_PLATFORM =~ /java/ + require 'protocol/http/body/file' -Async.logger.debug! if ENV['ASYNC_LOGGER_DEBUG'] + Async.logger.debug! if ENV['ASYNC_LOGGER_DEBUG'] -unless RUBY_PLATFORM =~ /java/ describe 'Async::HTTP::Client' do include AsyncHttpClientSpecHelper 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 47e57539d..4f6b42c54 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 @@ -44,6 +44,10 @@ def connection_refused_exception_class Errno::ECONNREFUSED end + def connection_error_class + HTTP::ConnectionError + end + def http_library :async_http_client end diff --git a/spec/acceptance/curb/curb_spec_helper.rb b/spec/acceptance/curb/curb_spec_helper.rb index 9191df6af..cea503ac1 100644 --- a/spec/acceptance/curb/curb_spec_helper.rb +++ b/spec/acceptance/curb/curb_spec_helper.rb @@ -37,7 +37,7 @@ def setup_request(uri, curl, options={}) curl.password = options[:basic_auth][1] end curl.timeout = 30 - curl.connect_timeout = 30 + curl.connect_timeout = 5 if headers = options[:headers] headers.each {|k,v| curl.headers[k] = v } @@ -54,6 +54,9 @@ def connection_refused_exception_class Curl::Err::ConnectionFailedError end + def connection_error_class + end + def http_library :curb end diff --git a/spec/acceptance/em_http_request/em_http_request_spec.rb b/spec/acceptance/em_http_request/em_http_request_spec.rb index a1574d948..04b4320aa 100644 --- a/spec/acceptance/em_http_request/em_http_request_spec.rb +++ b/spec/acceptance/em_http_request/em_http_request_spec.rb @@ -149,6 +149,18 @@ def response(resp) before { WebMock.allow_net_connect! } include_examples "em-http-request middleware/after_request hook integration" + it "doesn't modify headers" do + EM.run do + conn = EventMachine::HttpRequest.new(webmock_server_url) + http = conn.post(head: { 'content-length' => '4' }, body: 'test') + expect(conn).to receive(:send_data).with(/POST \/ HTTP\/1.1\r\nContent-Length: 4\r\nConnection: close\r\nHost: localhost:\d+\r\nUser-Agent: EventMachine HttpClient\r\nAccept-Encoding: gzip, compressed\r\n\r\n/).and_call_original + expect(conn).to receive(:send_data).with('test') + http.callback do + EM.stop + end + end + end + it "only calls request middleware once" do middleware = Class.new do def self.called! diff --git a/spec/acceptance/em_http_request/em_http_request_spec_helper.rb b/spec/acceptance/em_http_request/em_http_request_spec_helper.rb index d7d4b4997..87ecdcf84 100644 --- a/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +++ b/spec/acceptance/em_http_request/em_http_request_spec_helper.rb @@ -50,13 +50,16 @@ def http_request(method, uri, options = {}, &block) end def client_timeout_exception_class - 'Errno::ETIMEDOUT' + RuntimeError # 'Errno::ETIMEDOUT' end def connection_refused_exception_class RuntimeError end + def connection_error_class + end + def http_library :em_http_request end diff --git a/spec/acceptance/excon/excon_spec_helper.rb b/spec/acceptance/excon/excon_spec_helper.rb index 445c30f3e..793fa97e6 100644 --- a/spec/acceptance/excon/excon_spec_helper.rb +++ b/spec/acceptance/excon/excon_spec_helper.rb @@ -45,6 +45,9 @@ def connection_refused_exception_class Excon::Errors::SocketError end + def connection_error_class + end + def http_library :excon end diff --git a/spec/acceptance/http_rb/http_rb_spec_helper.rb b/spec/acceptance/http_rb/http_rb_spec_helper.rb index 89db48498..4924f12f0 100644 --- a/spec/acceptance/http_rb/http_rb_spec_helper.rb +++ b/spec/acceptance/http_rb/http_rb_spec_helper.rb @@ -31,6 +31,9 @@ def connection_refused_exception_class HTTP::ConnectionError end + def connection_error_class + end + def http_library :http_rb end diff --git a/spec/acceptance/httpclient/httpclient_spec_helper.rb b/spec/acceptance/httpclient/httpclient_spec_helper.rb index cf78baca6..e3816bf35 100644 --- a/spec/acceptance/httpclient/httpclient_spec_helper.rb +++ b/spec/acceptance/httpclient/httpclient_spec_helper.rb @@ -38,6 +38,9 @@ def connection_refused_exception_class Errno::ECONNREFUSED end + def connection_error_class + end + def http_library :httpclient end diff --git a/spec/acceptance/manticore/manticore_spec_helper.rb b/spec/acceptance/manticore/manticore_spec_helper.rb index 0c549fcd5..f053c8189 100644 --- a/spec/acceptance/manticore/manticore_spec_helper.rb +++ b/spec/acceptance/manticore/manticore_spec_helper.rb @@ -29,6 +29,9 @@ def connection_refused_exception_class Manticore::SocketException end + def connection_error_class + end + def http_library :manticore end diff --git a/spec/acceptance/net_http/net_http_spec_helper.rb b/spec/acceptance/net_http/net_http_spec_helper.rb index 6796ed314..13c61c69f 100644 --- a/spec/acceptance/net_http/net_http_spec_helper.rb +++ b/spec/acceptance/net_http/net_http_spec_helper.rb @@ -58,6 +58,9 @@ def connection_refused_exception_class Errno::ECONNREFUSED end + def connection_error_class + end + def http_library :net_http end diff --git a/spec/acceptance/patron/patron_spec_helper.rb b/spec/acceptance/patron/patron_spec_helper.rb index a9a05c91c..ff8494b6c 100644 --- a/spec/acceptance/patron/patron_spec_helper.rb +++ b/spec/acceptance/patron/patron_spec_helper.rb @@ -47,6 +47,9 @@ def connection_refused_exception_class Patron::ConnectionFailed end + def connection_error_class + end + def http_library :patron end diff --git a/spec/acceptance/shared/stubbing_requests.rb b/spec/acceptance/shared/stubbing_requests.rb index df77335a7..f9204e957 100644 --- a/spec/acceptance/shared/stubbing_requests.rb +++ b/spec/acceptance/shared/stubbing_requests.rb @@ -178,12 +178,44 @@ body: "foo=1").status).to eq("200") end - it "should match if stubbed request body is hash_included" do - WebMock.reset! - stub_request(:post, "www.example.com").with(body: {"foo" => hash_including("bar" => '1')}) - expect(http_request( - :post, "http://www.example.com/", headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, - body: "foo[bar]=1").status).to eq("200") + context "when using hash_including to match the request body" do + it "should match if stubbed request body is including hash" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => hash_including("bar" => '1')}) + expect(http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, + body: "foo[bar]=1").status).to eq("200") + end + + it "should not match if stubbed request body is excluding hash" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => hash_including("bar" => '1')}) + expect { + http_request(:post, "http://www.example.com/", + headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, + body: "foo[xyz]=1") + }.to raise_error(WebMock::NetConnectNotAllowedError) + end + end + + context "when using hash_excluding to match the request body" do + it "should match if stubbed request body is excluding hash" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => hash_excluding("bar" => '1')}) + expect(http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, + body: "foo[xyz]=1").status).to eq("200") + end + + it "should not match if stubbed request body is including hash" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => hash_excluding("bar" => '1')}) + expect { + http_request(:post, "http://www.example.com/", + headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, + body: "foo[bar]=1") + }.to raise_error(WebMock::NetConnectNotAllowedError) + end end end end diff --git a/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb b/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb index 074d2e237..4d26cf180 100644 --- a/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +++ b/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb @@ -53,6 +53,9 @@ def connection_refused_exception_class FakeTyphoeusHydraConnectError end + def connection_error_class + end + def http_library :typhoeus end diff --git a/spec/acceptance/webmock_shared.rb b/spec/acceptance/webmock_shared.rb index 98f8bd578..4a97a0bbb 100644 --- a/spec/acceptance/webmock_shared.rb +++ b/spec/acceptance/webmock_shared.rb @@ -22,6 +22,14 @@ WebMock.reset! end + around(:each, net_connect: true) do |ex| + ex.run_with_retry retry: 2, exceptions_to_retry: [ + client_timeout_exception_class, + connection_refused_exception_class, + connection_error_class + ].compact + end + include_context "allowing and disabling net connect", *adapter_info include_context "stubbing requests", *adapter_info diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7a219fc6f..b32c9cc74 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'rspec' +require 'rspec/retry' require 'webmock/rspec' diff --git a/spec/unit/util/version_checker_spec.rb b/spec/unit/util/version_checker_spec.rb index ce819da94..65476341b 100644 --- a/spec/unit/util/version_checker_spec.rb +++ b/spec/unit/util/version_checker_spec.rb @@ -121,5 +121,11 @@ module WebMock expect(Kernel).to receive(:warn).with(%r{You are using foo 2.0.0. WebMock does not support this version. WebMock supports versions >= 1.0.0, < 3.1, except versions 2.0.0.}) checker.check_version! end + + it "does not raise an error or print a warning when the max_minor_version is 1.0 and the version is 0.9" do + checker = VersionChecker.new('foo', '0.9.0', '0.8.0', '1.0') + expect(Kernel).not_to receive(:warn)#.with(%r{You are using foo 0.9.0. WebMock does not support this version. WebMock supports versions >= 0.8.0, < 1.0.}) + checker.check_version! + end end end diff --git a/test/shared_test.rb b/test/shared_test.rb index f172ae1e7..70f6a00a5 100644 --- a/test/shared_test.rb +++ b/test/shared_test.rb @@ -78,10 +78,28 @@ def test_verification_that_expected_request_not_occured_with_query_params def test_verification_that_expected_request_occured_with_excluding_query_params stub_request(:any, 'http://www.example.com').with(query: hash_excluding('a' => ['b', 'c'])) - http_request(:get, 'http://www.example.com/?a[]=x&a[]=y&x=1') + http_request(:get, 'http://www.example.com/?a[]=b&a[]=y&x=1') assert_requested(:get, 'http://www.example.com', query: hash_excluding('a' => ['b', 'c'])) end + def test_verification_that_expected_request_occured_with_hash_excluding_body + stub_request(:post, 'http://www.example.com').with(body: hash_excluding('a' => ['b', 'c'])) + http_request(:post, 'http://www.example.com/', + body: 'a[]=b&a[]=y&x=1', + headers: {'Content-Type' => 'application/x-www-form-urlencoded'} + ) + assert_requested(:post, 'http://www.example.com', body: hash_excluding('a' => ['b', 'c'])) + end + + def test_verification_that_expected_request_not_occured_with_hash_excluding_body + stub_request(:post, 'http://www.example.com').with(body: hash_excluding('a' => ['b', 'c'])) + http_request(:post, 'http://www.example.com/', + body: 'a[]=b&a[]=c&x=1', + headers: {'Content-Type' => 'application/x-www-form-urlencoded'} + ) + assert_not_requested(:post, 'http://www.example.com', body: hash_excluding('a' => ['b', 'c'])) + end + def test_verification_that_non_expected_request_didnt_occur expected_message = %r(The request GET http://www.example.com/ was not expected to execute but it executed 1 time\n\nThe following requests were made:\n\nGET http://www.example.com/ with headers .+ was made 1 time\n\n============================================================) assert_fail(expected_message) do diff --git a/webmock.gemspec b/webmock.gemspec index 132fd6303..c4f3c6280 100644 --- a/webmock.gemspec +++ b/webmock.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| 'wiki_uri' => 'https://github.com/bblimke/webmock/wiki' } - s.required_ruby_version = '>= 2.3' + s.required_ruby_version = '>= 2.5' s.add_dependency 'addressable', '>= 2.8.0' s.add_dependency 'crack', '>= 0.3.2' @@ -33,22 +33,21 @@ Gem::Specification.new do |s| s.add_development_dependency 'typhoeus', '>= 0.5.0' s.add_development_dependency 'em-http-request', '>= 1.0.2' s.add_development_dependency 'em-synchrony', '>= 1.0.0' + s.add_development_dependency 'async-http', '>= 0.48.0' end s.add_development_dependency 'http', '>= 0.8.0' s.add_development_dependency 'manticore', '>= 0.5.1' if RUBY_PLATFORM =~ /java/ - s.add_development_dependency 'rack', ((RUBY_VERSION < '2.2.2') ? '1.6.0' : '> 1.6') + s.add_development_dependency 'rack', '> 1.6' s.add_development_dependency 'rspec', '>= 3.1.0' s.add_development_dependency 'httpclient', '>= 2.2.4' s.add_development_dependency 'excon', '>= 0.27.5' - s.add_development_dependency 'async-http', '>= 0.48.0' s.add_development_dependency 'minitest', '>= 5.0.0' s.add_development_dependency 'test-unit', '>= 3.0.0' s.add_development_dependency 'rdoc', '> 3.5.0' s.add_development_dependency 'webrick' + s.add_development_dependency 'rspec-retry' - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - s.require_paths = ['lib'] + s.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "lib/**/*"] + s.require_path = "lib" end