├── .gemtest ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .rspec-tm ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── webmock.rb └── webmock │ ├── api.rb │ ├── assertion_failure.rb │ ├── callback_registry.rb │ ├── config.rb │ ├── cucumber.rb │ ├── deprecation.rb │ ├── errors.rb │ ├── http_lib_adapters │ ├── async_http_client_adapter.rb │ ├── curb_adapter.rb │ ├── em_http_request_adapter.rb │ ├── excon_adapter.rb │ ├── http_lib_adapter.rb │ ├── http_lib_adapter_registry.rb │ ├── http_rb │ │ ├── client.rb │ │ ├── request.rb │ │ ├── response.rb │ │ ├── streamer.rb │ │ └── webmock.rb │ ├── http_rb_adapter.rb │ ├── httpclient_adapter.rb │ ├── manticore_adapter.rb │ ├── net_http.rb │ ├── net_http_response.rb │ ├── patron_adapter.rb │ └── typhoeus_hydra_adapter.rb │ ├── matchers │ ├── any_arg_matcher.rb │ ├── hash_argument_matcher.rb │ ├── hash_excluding_matcher.rb │ └── hash_including_matcher.rb │ ├── minitest.rb │ ├── rack_response.rb │ ├── request_body_diff.rb │ ├── request_execution_verifier.rb │ ├── request_pattern.rb │ ├── request_registry.rb │ ├── request_signature.rb │ ├── request_signature_snippet.rb │ ├── request_stub.rb │ ├── response.rb │ ├── responses_sequence.rb │ ├── rspec.rb │ ├── rspec │ ├── matchers.rb │ └── matchers │ │ ├── request_pattern_matcher.rb │ │ └── webmock_matcher.rb │ ├── stub_registry.rb │ ├── stub_request_snippet.rb │ ├── test_unit.rb │ ├── util │ ├── hash_counter.rb │ ├── hash_keys_stringifier.rb │ ├── hash_validator.rb │ ├── headers.rb │ ├── parsers │ │ ├── json.rb │ │ ├── parse_error.rb │ │ └── xml.rb │ ├── query_mapper.rb │ ├── uri.rb │ ├── values_stringifier.rb │ └── version_checker.rb │ ├── version.rb │ └── webmock.rb ├── minitest ├── test_helper.rb ├── test_webmock.rb └── webmock_spec.rb ├── spec ├── acceptance │ ├── async_http_client │ │ ├── async_http_client_spec.rb │ │ └── async_http_client_spec_helper.rb │ ├── curb │ │ ├── curb_spec.rb │ │ └── curb_spec_helper.rb │ ├── em_http_request │ │ ├── em_http_request_spec.rb │ │ └── em_http_request_spec_helper.rb │ ├── excon │ │ ├── excon_spec.rb │ │ └── excon_spec_helper.rb │ ├── http_rb │ │ ├── http_rb_spec.rb │ │ └── http_rb_spec_helper.rb │ ├── httpclient │ │ ├── httpclient_spec.rb │ │ └── httpclient_spec_helper.rb │ ├── manticore │ │ ├── manticore_spec.rb │ │ └── manticore_spec_helper.rb │ ├── net_http │ │ ├── net_http_shared.rb │ │ ├── net_http_spec.rb │ │ ├── net_http_spec_helper.rb │ │ └── real_net_http_spec.rb │ ├── patron │ │ ├── patron_spec.rb │ │ └── patron_spec_helper.rb │ ├── shared │ │ ├── allowing_and_disabling_net_connect.rb │ │ ├── callbacks.rb │ │ ├── complex_cross_concern_behaviors.rb │ │ ├── enabling_and_disabling_webmock.rb │ │ ├── precedence_of_stubs.rb │ │ ├── request_expectations.rb │ │ ├── returning_declared_responses.rb │ │ └── stubbing_requests.rb │ ├── typhoeus │ │ ├── typhoeus_hydra_spec.rb │ │ └── typhoeus_hydra_spec_helper.rb │ └── webmock_shared.rb ├── fixtures │ └── test.txt ├── quality_spec.rb ├── spec_helper.rb ├── support │ ├── example_curl_output.txt │ ├── failures.rb │ ├── my_rack_app.rb │ ├── network_connection.rb │ └── webmock_server.rb └── unit │ ├── api_spec.rb │ ├── errors_spec.rb │ ├── http_lib_adapters │ ├── http_lib_adapter_registry_spec.rb │ └── http_lib_adapter_spec.rb │ ├── matchers │ ├── hash_excluding_matcher_spec.rb │ └── hash_including_matcher_spec.rb │ ├── rack_response_spec.rb │ ├── request_body_diff_spec.rb │ ├── request_execution_verifier_spec.rb │ ├── request_pattern_spec.rb │ ├── request_registry_spec.rb │ ├── request_signature_snippet_spec.rb │ ├── request_signature_spec.rb │ ├── request_stub_spec.rb │ ├── response_spec.rb │ ├── stub_registry_spec.rb │ ├── stub_request_snippet_spec.rb │ ├── util │ ├── hash_counter_spec.rb │ ├── hash_keys_stringifier_spec.rb │ ├── headers_spec.rb │ ├── parsers │ │ └── json_spec.rb │ ├── query_mapper_spec.rb │ ├── uri_spec.rb │ └── version_checker_spec.rb │ └── webmock_spec.rb ├── test ├── http_request.rb ├── shared_test.rb ├── test_helper.rb └── test_webmock.rb └── webmock.gemspec /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bblimke/webmock/50bb34991864a8155908c261954a3081df795f3e/.gemtest -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | ruby: 16 | - head 17 | - '3.4' 18 | - '3.3' 19 | - '3.2' 20 | - '3.1' 21 | - '3.0' 22 | - '2.7' 23 | - '2.6' 24 | # FIXME: When the JRuby issue https://github.com/jruby/jruby/issues/8606 is resolved, 25 | # please re-run it with the following: 26 | # - jruby-head 27 | continue-on-error: ${{ matrix.ruby == 'head' || matrix.ruby == 'jruby-head' }} 28 | name: Ruby ${{ matrix.ruby }} 29 | env: 30 | JRUBY_OPTS: "--debug" 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Install Apt Packages 34 | run: | 35 | sudo apt update 36 | sudo apt-get install libcurl4-openssl-dev -y 37 | - uses: ruby/setup-ruby@v1 38 | continue-on-error: false 39 | with: 40 | ruby-version: ${{ matrix.ruby }} 41 | bundler-cache: true 42 | rubygems: 'latest' 43 | bundler: 'latest' 44 | - run: | 45 | bundle exec rake 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | .*.sw[a-z] 15 | 16 | ## RubyMine and related 17 | .idea 18 | 19 | ## PROJECT::GENERAL 20 | coverage 21 | rdoc 22 | pkg 23 | 24 | ## PROJECT::SPECIFIC 25 | *.gem 26 | .bundle 27 | Gemfile.lock 28 | gemfiles/*.gemfile.lock 29 | pkg/* 30 | tmp/* 31 | *.rbc 32 | *.rbx 33 | .ruby-gemset 34 | .ruby-version 35 | .tool-versions 36 | -------------------------------------------------------------------------------- /.rspec-tm: -------------------------------------------------------------------------------- 1 | --rspec-version 2.0.0 2 | --bundler -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | gemspec 4 | 5 | # FIXME: This is a workaround to resolve the following error in Ruby 3.5: 6 | # 7 | # /home/runner/work/webmock/webmock/vendor/bundle/ruby/3.5.0+0/gems/ethon-0.16.0/lib/ethon.rb:2: 8 | # warning: logger was loaded from the standard library, but is not part of the default gems starting from Ruby 3.5.0. 9 | # 10 | # It can likely be removed once `ethon`, which is a dependency of `typhoeus`, manages its `logger` dependency. 11 | gem 'logger' 12 | gem 'ostruct' 13 | gem 'rake' 14 | 15 | platforms :jruby do 16 | gem 'jruby-openssl' 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2010 Bartosz Blimke 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require "rspec/core/rake_task" 5 | RSpec::Core::RakeTask.new(:spec) do |t| 6 | t.rspec_opts = %w[ 7 | --force-color 8 | --format progress 9 | --require ./spec/spec_helper.rb 10 | ] 11 | t.pattern = 'spec/**/*_spec.rb' 12 | end 13 | 14 | RSpec::Core::RakeTask.new(:spec_http_without_webmock) do |t| 15 | t.rspec_opts = %w[ 16 | --force-color 17 | --format progress 18 | --require ./spec/acceptance/net_http/real_net_http_spec.rb 19 | ] 20 | t.pattern = 'spec/acceptance/net_http/real_net_http_spec.rb' 21 | end 22 | 23 | require 'rake/testtask' 24 | Rake::TestTask.new(:test) do |test| 25 | test.test_files = FileList["test/**/*.rb"].exclude("test/test_helper.rb") 26 | test.options = "--use-color" 27 | test.verbose = false 28 | test.warning = false 29 | end 30 | 31 | Rake::TestTask.new(:minitest) do |test| 32 | test.test_files = FileList["minitest/**/*.rb"].exclude("test/test_helper.rb") 33 | test.options = "--pride" 34 | test.verbose = false 35 | test.warning = false 36 | end 37 | 38 | task default: [:spec, :spec_http_without_webmock, :test, :minitest] 39 | -------------------------------------------------------------------------------- /lib/webmock.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'singleton' 4 | 5 | require 'addressable/uri' 6 | require 'addressable/template' 7 | 8 | require_relative 'webmock/deprecation' 9 | require_relative 'webmock/version' 10 | 11 | require_relative 'webmock/errors' 12 | 13 | require_relative 'webmock/util/query_mapper' 14 | require_relative 'webmock/util/uri' 15 | require_relative 'webmock/util/headers' 16 | require_relative 'webmock/util/hash_counter' 17 | require_relative 'webmock/util/hash_keys_stringifier' 18 | require_relative 'webmock/util/values_stringifier' 19 | require_relative 'webmock/util/parsers/json' 20 | require_relative 'webmock/util/parsers/xml' 21 | require_relative 'webmock/util/version_checker' 22 | require_relative 'webmock/util/hash_validator' 23 | 24 | require_relative 'webmock/matchers/hash_argument_matcher' 25 | require_relative 'webmock/matchers/hash_excluding_matcher' 26 | require_relative 'webmock/matchers/hash_including_matcher' 27 | require_relative 'webmock/matchers/any_arg_matcher' 28 | 29 | require_relative 'webmock/request_pattern' 30 | require_relative 'webmock/request_signature' 31 | require_relative 'webmock/responses_sequence' 32 | require_relative 'webmock/request_stub' 33 | require_relative 'webmock/response' 34 | require_relative 'webmock/rack_response' 35 | 36 | require_relative 'webmock/stub_request_snippet' 37 | require_relative 'webmock/request_signature_snippet' 38 | require_relative 'webmock/request_body_diff' 39 | 40 | require_relative 'webmock/assertion_failure' 41 | require_relative 'webmock/request_execution_verifier' 42 | require_relative 'webmock/config' 43 | require_relative 'webmock/callback_registry' 44 | require_relative 'webmock/request_registry' 45 | require_relative 'webmock/stub_registry' 46 | require_relative 'webmock/api' 47 | 48 | require_relative 'webmock/http_lib_adapters/http_lib_adapter_registry' 49 | require_relative 'webmock/http_lib_adapters/http_lib_adapter' 50 | require_relative 'webmock/http_lib_adapters/net_http' 51 | require_relative 'webmock/http_lib_adapters/http_rb_adapter' 52 | require_relative 'webmock/http_lib_adapters/httpclient_adapter' 53 | require_relative 'webmock/http_lib_adapters/patron_adapter' 54 | require_relative 'webmock/http_lib_adapters/curb_adapter' 55 | require_relative 'webmock/http_lib_adapters/em_http_request_adapter' 56 | require_relative 'webmock/http_lib_adapters/typhoeus_hydra_adapter' 57 | require_relative 'webmock/http_lib_adapters/excon_adapter' 58 | require_relative 'webmock/http_lib_adapters/manticore_adapter' 59 | require_relative 'webmock/http_lib_adapters/async_http_client_adapter' 60 | 61 | require_relative 'webmock/webmock' 62 | -------------------------------------------------------------------------------- /lib/webmock/api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | module API 5 | extend self 6 | 7 | def stub_request(method, uri) 8 | WebMock::StubRegistry.instance. 9 | register_request_stub(WebMock::RequestStub.new(method, uri)) 10 | end 11 | 12 | alias_method :stub_http_request, :stub_request 13 | 14 | def a_request(method, uri) 15 | WebMock::RequestPattern.new(method, uri) 16 | end 17 | 18 | class << self 19 | alias :request :a_request 20 | end 21 | 22 | def assert_requested(*args, &block) 23 | if not args[0].is_a?(WebMock::RequestStub) 24 | args = convert_uri_method_and_options_to_request_and_options(args[0], args[1], args[2], &block) 25 | elsif block 26 | raise ArgumentError, "assert_requested with a stub object, doesn't accept blocks" 27 | end 28 | assert_request_requested(*args) 29 | end 30 | 31 | def assert_not_requested(*args, &block) 32 | if not args[0].is_a?(WebMock::RequestStub) 33 | args = convert_uri_method_and_options_to_request_and_options(args[0], args[1], args[2], &block) 34 | elsif block 35 | raise ArgumentError, "assert_not_requested with a stub object, doesn't accept blocks" 36 | end 37 | assert_request_not_requested(*args) 38 | end 39 | alias refute_requested assert_not_requested 40 | 41 | # Similar to RSpec::Mocks::ArgumentMatchers#hash_including() 42 | # 43 | # Matches a hash that includes the specified key(s) or key/value pairs. 44 | # Ignores any additional keys. 45 | # 46 | # @example 47 | # 48 | # object.should_receive(:message).with(hash_including(:key => val)) 49 | # object.should_receive(:message).with(hash_including(:key)) 50 | # object.should_receive(:message).with(hash_including(:key, :key2 => val2)) 51 | def hash_including(*args) 52 | if defined?(super) 53 | super 54 | else 55 | WebMock::Matchers::HashIncludingMatcher.new(anythingize_lonely_keys(*args)) 56 | end 57 | end 58 | 59 | def hash_excluding(*args) 60 | if defined?(super) 61 | super 62 | else 63 | WebMock::Matchers::HashExcludingMatcher.new(anythingize_lonely_keys(*args)) 64 | end 65 | end 66 | 67 | def remove_request_stub(stub) 68 | WebMock::StubRegistry.instance.remove_request_stub(stub) 69 | end 70 | 71 | def reset_executed_requests! 72 | WebMock::RequestRegistry.instance.reset! 73 | end 74 | 75 | private 76 | 77 | def convert_uri_method_and_options_to_request_and_options(method, uri, options, &block) 78 | options ||= {} 79 | options_for_pattern = options.dup 80 | [:times, :at_least_times, :at_most_times].each { |key| options_for_pattern.delete(key) } 81 | request = WebMock::RequestPattern.new(method, uri, options_for_pattern) 82 | request = request.with(&block) if block 83 | [request, options] 84 | end 85 | 86 | def assert_request_requested(request, options = {}) 87 | times = options.delete(:times) 88 | at_least_times = options.delete(:at_least_times) 89 | at_most_times = options.delete(:at_most_times) 90 | times = 1 if times.nil? && at_least_times.nil? && at_most_times.nil? 91 | verifier = WebMock::RequestExecutionVerifier.new(request, times, at_least_times, at_most_times) 92 | WebMock::AssertionFailure.failure(verifier.failure_message) unless verifier.matches? 93 | end 94 | 95 | def assert_request_not_requested(request, options = {}) 96 | times = options.delete(:times) 97 | at_least_times = options.delete(:at_least_times) 98 | at_most_times = options.delete(:at_most_times) 99 | verifier = WebMock::RequestExecutionVerifier.new(request, times, at_least_times, at_most_times) 100 | WebMock::AssertionFailure.failure(verifier.failure_message_when_negated) unless verifier.does_not_match? 101 | end 102 | 103 | #this is a based on RSpec::Mocks::ArgumentMatchers#anythingize_lonely_keys 104 | def anythingize_lonely_keys(*args) 105 | hash = args.last.class == Hash ? args.delete_at(-1) : {} 106 | args.each { | arg | hash[arg] = WebMock::Matchers::AnyArgMatcher.new(nil) } 107 | hash 108 | end 109 | 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/webmock/assertion_failure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class AssertionFailure 5 | @error_class = RuntimeError 6 | class << self 7 | attr_accessor :error_class 8 | def failure(message) 9 | raise @error_class.new(message) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/webmock/callback_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class CallbackRegistry 5 | @@callbacks = [] 6 | 7 | def self.add_callback(options, block) 8 | @@callbacks << {options: options, block: block} 9 | end 10 | 11 | def self.callbacks 12 | @@callbacks 13 | end 14 | 15 | def self.invoke_callbacks(options, request_signature, response) 16 | return if @@callbacks.empty? 17 | CallbackRegistry.callbacks.each do |callback| 18 | except = callback[:options][:except] 19 | real_only = callback[:options][:real_requests_only] 20 | unless except && except.include?(options[:lib]) 21 | if !real_only || options[:real_request] 22 | callback[:block].call(request_signature, response) 23 | end 24 | end 25 | end 26 | end 27 | 28 | def self.reset 29 | @@callbacks = [] 30 | end 31 | 32 | def self.any_callbacks? 33 | !@@callbacks.empty? 34 | end 35 | 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/webmock/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class Config 5 | include Singleton 6 | 7 | def initialize 8 | @show_stubbing_instructions = true 9 | @show_body_diff = true 10 | end 11 | 12 | attr_accessor :allow_net_connect 13 | attr_accessor :allow_localhost 14 | attr_accessor :allow 15 | attr_accessor :net_http_connect_on_start 16 | attr_accessor :show_stubbing_instructions 17 | attr_accessor :query_values_notation 18 | attr_accessor :show_body_diff 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/webmock/cucumber.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'webmock' 4 | require 'webmock/rspec/matchers' 5 | 6 | WebMock.enable! 7 | 8 | World(WebMock::API, WebMock::Matchers) 9 | 10 | After do 11 | WebMock.reset! 12 | end 13 | -------------------------------------------------------------------------------- /lib/webmock/deprecation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class Deprecation 5 | class << self 6 | def warning(message) 7 | warn "WebMock deprecation warning: #{message}" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/webmock/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | class NetConnectNotAllowedError < Exception 6 | def initialize(request_signature) 7 | request_signature_snippet = RequestSignatureSnippet.new(request_signature) 8 | text = [ 9 | "Real HTTP connections are disabled. Unregistered request: #{request_signature}", 10 | request_signature_snippet.stubbing_instructions, 11 | request_signature_snippet.request_stubs, 12 | "="*60 13 | ].compact.join("\n\n") 14 | super(text) 15 | end 16 | 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/excon_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'excon' 5 | rescue LoadError 6 | # excon not found 7 | end 8 | 9 | if defined?(Excon) 10 | WebMock::VersionChecker.new('Excon', Excon::VERSION, '0.27.5').check_version! 11 | 12 | module WebMock 13 | module HttpLibAdapters 14 | 15 | class ExconAdapter < HttpLibAdapter 16 | PARAMS_TO_DELETE = [:expects, :idempotent, 17 | :instrumentor_name, :instrumentor, 18 | :response_block, 19 | :__construction_args, :stack, 20 | :connection, :response] 21 | 22 | adapter_for :excon 23 | 24 | instance_exec do 25 | @original_excon_mock_default = nil 26 | @stub = nil 27 | end 28 | 29 | def self.enable! 30 | self.add_excon_stub 31 | end 32 | 33 | def self.disable! 34 | self.remove_excon_stub 35 | end 36 | 37 | def self.add_excon_stub 38 | if not @stub 39 | @original_excon_mock_default = ::Excon.defaults[:mock] 40 | ::Excon.defaults[:mock] = true 41 | @stub = ::Excon.stub({}) do |params| 42 | self.handle_request(params) 43 | end 44 | end 45 | end 46 | 47 | def self.remove_excon_stub 48 | ::Excon.defaults[:mock] = @original_excon_mock_default 49 | @original_excon_mock_default = nil 50 | Excon.stubs.delete(@stub) 51 | @stub = nil 52 | end 53 | 54 | def self.handle_request(params) 55 | mock_request = self.build_request params.dup 56 | WebMock::RequestRegistry.instance.requested_signatures.put(mock_request) 57 | 58 | if mock_response = WebMock::StubRegistry.instance.response_for_request(mock_request) 59 | self.perform_callbacks(mock_request, mock_response, real_request: false) 60 | response = self.real_response(mock_response) 61 | response 62 | elsif WebMock.net_connect_allowed?(mock_request.uri) 63 | conn = new_excon_connection(params) 64 | real_response = conn.request(request_params_from(params.merge(mock: false))) 65 | 66 | ExconAdapter.perform_callbacks(mock_request, ExconAdapter.mock_response(real_response), real_request: true) 67 | 68 | real_response.data 69 | else 70 | raise WebMock::NetConnectNotAllowedError.new(mock_request) 71 | end 72 | end 73 | 74 | def self.new_excon_connection(params) 75 | # Ensure the connection is constructed with the exact same args 76 | # that the orginal connection was constructed with. 77 | args = params.fetch(:__construction_args) 78 | ::Excon::Connection.new(connection_params_from args.merge(mock: false)) 79 | end 80 | 81 | def self.connection_params_from(hash) 82 | hash = hash.dup 83 | PARAMS_TO_DELETE.each { |key| hash.delete(key) } 84 | hash 85 | end 86 | 87 | def self.request_params_from(hash) 88 | hash = hash.dup 89 | if defined?(Excon::VALID_REQUEST_KEYS) 90 | hash.reject! {|key,_| !Excon::VALID_REQUEST_KEYS.include?(key) } 91 | end 92 | PARAMS_TO_DELETE.each { |key| hash.delete(key) } 93 | hash 94 | end 95 | 96 | def self.to_query(hash) 97 | string = "".dup 98 | for key, values in hash 99 | if values.nil? 100 | string << key.to_s << '&' 101 | else 102 | for value in [*values] 103 | string << key.to_s << '=' << CGI.escape(value.to_s) << '&' 104 | end 105 | end 106 | end 107 | string.chop! # remove trailing '&' 108 | end 109 | 110 | def self.build_request(params) 111 | params = params.dup 112 | params.delete(:user) 113 | params.delete(:password) 114 | method = (params.delete(:method) || :get).to_s.downcase.to_sym 115 | params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash) 116 | uri = Addressable::URI.new(params).to_s 117 | WebMock::RequestSignature.new method, uri, body: body_from(params), headers: params[:headers] 118 | end 119 | 120 | def self.body_from(params) 121 | body = params[:body] 122 | return body unless body.respond_to?(:read) 123 | 124 | contents = body.read 125 | body.rewind if body.respond_to?(:rewind) 126 | contents 127 | end 128 | 129 | def self.real_response(mock) 130 | raise Excon::Errors::Timeout if mock.should_timeout 131 | mock.raise_error_if_any 132 | { 133 | body: mock.body, 134 | status: mock.status[0].to_i, 135 | reason_phrase: mock.status[1], 136 | headers: mock.headers || {} 137 | } 138 | end 139 | 140 | def self.mock_response(real) 141 | mock = WebMock::Response.new 142 | mock.status = [real.status, real.reason_phrase] 143 | mock.headers = real.headers 144 | mock.body = real.body.dup 145 | mock 146 | end 147 | 148 | def self.perform_callbacks(request, response, options = {}) 149 | return unless WebMock::CallbackRegistry.any_callbacks? 150 | WebMock::CallbackRegistry.invoke_callbacks(options.merge(lib: :excon), request, response) 151 | end 152 | end 153 | end 154 | end 155 | 156 | Excon::Connection.class_eval do 157 | def self.new(args = {}) 158 | args.delete(:__construction_args) 159 | super(args).tap do |instance| 160 | instance.data[:__construction_args] = args 161 | end 162 | end 163 | end 164 | 165 | # Suppresses Excon connection argument validation warning 166 | Excon::VALID_CONNECTION_KEYS << :__construction_args 167 | end 168 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_lib_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class HttpLibAdapter 5 | def self.adapter_for(lib) 6 | WebMock::HttpLibAdapterRegistry.instance.register(lib, self) 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class HttpLibAdapterRegistry 5 | include Singleton 6 | 7 | attr_accessor :http_lib_adapters 8 | 9 | def initialize 10 | @http_lib_adapters = {} 11 | end 12 | 13 | def register(lib, adapter) 14 | @http_lib_adapters[lib] = adapter 15 | end 16 | 17 | def each_adapter(&block) 18 | @http_lib_adapters.each(&block) 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_rb/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HTTP 4 | class Client 5 | alias_method :__perform__, :perform 6 | 7 | def perform(request, options) 8 | return __perform__(request, options) unless webmock_enabled? 9 | 10 | WebMockPerform.new(request, options) { __perform__(request, options) }.exec 11 | end 12 | 13 | def webmock_enabled? 14 | ::WebMock::HttpLibAdapters::HttpRbAdapter.enabled? 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_rb/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HTTP 4 | class Request 5 | def webmock_signature 6 | request_body = nil 7 | 8 | if defined?(HTTP::Request::Body) 9 | request_body = String.new 10 | first_chunk_encoding = nil 11 | body.each do |part| 12 | request_body << part 13 | first_chunk_encoding ||= part.encoding 14 | end 15 | 16 | request_body.force_encoding(first_chunk_encoding) if first_chunk_encoding 17 | request_body 18 | else 19 | request_body = body 20 | end 21 | 22 | ::WebMock::RequestSignature.new(verb, uri.to_s, { 23 | headers: headers.to_h, 24 | body: request_body 25 | }) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_rb/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HTTP 4 | class Response 5 | def to_webmock 6 | webmock_response = ::WebMock::Response.new 7 | 8 | webmock_response.status = [status.to_i, reason] 9 | 10 | webmock_response.body = body.to_s 11 | # This call is used to reset the body of the response to enable it to be streamed if necessary. 12 | # The `body.to_s` call above reads the body, which allows WebMock to trigger any registered callbacks. 13 | # However, once the body is read to_s, it cannot be streamed again and attempting to do so 14 | # will raise a "HTTP::StateError: body has already been consumed" error. 15 | # To avoid this error, we replace the original body with a new one. 16 | # The new body has its @stream attribute set to new Streamer, instead of the original Connection. 17 | # Unfortunately, it's not possible to reset the original body to its initial streaming state. 18 | # Therefore, this replacement is the best workaround currently available. 19 | reset_body_to_allow_it_to_be_streamed!(webmock_response) 20 | 21 | webmock_response.headers = headers.to_h 22 | webmock_response 23 | end 24 | 25 | class << self 26 | def from_webmock(request, webmock_response, request_signature = nil) 27 | status = Status.new(webmock_response.status.first) 28 | headers = webmock_response.headers || {} 29 | uri = normalize_uri(request_signature && request_signature.uri) 30 | 31 | # HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x 32 | # and 2.x use a positional argument, and 0.x don't support supplying 33 | # the encoding. 34 | body = build_http_rb_response_body_from_webmock_response(webmock_response) 35 | 36 | return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0" 37 | 38 | # 5.0.0 had a breaking change to require request instead of uri. 39 | if HTTP::VERSION < '5.0.0' 40 | return new({ 41 | status: status, 42 | version: "1.1", 43 | headers: headers, 44 | body: body, 45 | uri: uri 46 | }) 47 | end 48 | 49 | new({ 50 | status: status, 51 | version: "1.1", 52 | headers: headers, 53 | body: body, 54 | request: request, 55 | }) 56 | end 57 | 58 | def build_http_rb_response_body_from_webmock_response(webmock_response) 59 | if HTTP::VERSION < "1.0.0" 60 | Body.new(Streamer.new(webmock_response.body)) 61 | elsif HTTP::VERSION < "3.0.0" 62 | Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding) 63 | else 64 | Body.new( 65 | Streamer.new(webmock_response.body, encoding: webmock_response.body.encoding), 66 | encoding: webmock_response.body.encoding 67 | ) 68 | end 69 | end 70 | 71 | def normalize_uri(uri) 72 | return unless uri 73 | 74 | uri = Addressable::URI.parse uri 75 | uri.port = nil if uri.default_port && uri.port == uri.default_port 76 | 77 | uri 78 | end 79 | end 80 | 81 | private 82 | 83 | def reset_body_to_allow_it_to_be_streamed!(webmock_response) 84 | @body = self.class.build_http_rb_response_body_from_webmock_response(webmock_response) 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_rb/streamer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HTTP 4 | class Response 5 | class Streamer 6 | def initialize(str, encoding: Encoding::BINARY) 7 | @io = StringIO.new str 8 | @encoding = encoding 9 | end 10 | 11 | def readpartial(size = nil, outbuf = nil) 12 | unless size 13 | if defined?(HTTP::Client::BUFFER_SIZE) 14 | size = HTTP::Client::BUFFER_SIZE 15 | elsif defined?(HTTP::Connection::BUFFER_SIZE) 16 | size = HTTP::Connection::BUFFER_SIZE 17 | end 18 | end 19 | 20 | chunk = @io.read size, outbuf 21 | chunk.force_encoding(@encoding) if chunk 22 | end 23 | 24 | def close 25 | @io.close 26 | end 27 | 28 | def finished_request? 29 | @io.eof? 30 | end 31 | 32 | def sequence_id 33 | -1 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_rb/webmock.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HTTP 4 | class WebMockPerform 5 | def initialize(request, options, &perform) 6 | @request = request 7 | @options = options 8 | @perform = perform 9 | @request_signature = nil 10 | end 11 | 12 | def exec 13 | replay || perform || halt 14 | end 15 | 16 | def request_signature 17 | unless @request_signature 18 | @request_signature = @request.webmock_signature 19 | register_request(@request_signature) 20 | end 21 | 22 | @request_signature 23 | end 24 | 25 | protected 26 | 27 | def response_for_request(signature) 28 | ::WebMock::StubRegistry.instance.response_for_request(signature) 29 | end 30 | 31 | def register_request(signature) 32 | ::WebMock::RequestRegistry.instance.requested_signatures.put(signature) 33 | end 34 | 35 | def replay 36 | webmock_response = response_for_request request_signature 37 | 38 | return unless webmock_response 39 | 40 | raise_timeout_error if webmock_response.should_timeout 41 | webmock_response.raise_error_if_any 42 | 43 | invoke_callbacks(webmock_response, real_request: false) 44 | response = ::HTTP::Response.from_webmock @request, webmock_response, request_signature 45 | 46 | @options.features.each { |_name, feature| response = feature.wrap_response(response) } 47 | response 48 | end 49 | 50 | def raise_timeout_error 51 | raise Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0" 52 | raise HTTP::TimeoutError, "connection error: #{Errno::ETIMEDOUT.new}" 53 | end 54 | 55 | def perform 56 | return unless ::WebMock.net_connect_allowed?(request_signature.uri) 57 | response = @perform.call 58 | invoke_callbacks(response.to_webmock, real_request: true) 59 | response 60 | end 61 | 62 | def halt 63 | raise ::WebMock::NetConnectNotAllowedError.new request_signature 64 | end 65 | 66 | def invoke_callbacks(webmock_response, options = {}) 67 | ::WebMock::CallbackRegistry.invoke_callbacks( 68 | options.merge({ lib: :http_rb }), 69 | request_signature, 70 | webmock_response 71 | ) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/http_rb_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require "http" 5 | rescue LoadError 6 | # HTTP gem not found 7 | end 8 | 9 | if defined?(HTTP) && defined?(HTTP::VERSION) 10 | WebMock::VersionChecker.new("HTTP Gem", HTTP::VERSION, "0.6.0").check_version! 11 | 12 | module WebMock 13 | module HttpLibAdapters 14 | class HttpRbAdapter < HttpLibAdapter 15 | adapter_for :http_rb 16 | 17 | class << self 18 | def enable! 19 | @enabled = true 20 | end 21 | 22 | def disable! 23 | @enabled = false 24 | end 25 | 26 | def enabled? 27 | @enabled 28 | end 29 | end 30 | end 31 | end 32 | end 33 | 34 | require_relative "http_rb/client" 35 | require_relative "http_rb/request" 36 | require_relative "http_rb/response" 37 | require_relative "http_rb/streamer" 38 | require_relative "http_rb/webmock" 39 | end 40 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/manticore_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'manticore' 5 | rescue LoadError 6 | # manticore not found 7 | end 8 | 9 | if defined?(Manticore) 10 | module WebMock 11 | module HttpLibAdapters 12 | class ManticoreAdapter < HttpLibAdapter 13 | adapter_for :manticore 14 | 15 | OriginalManticoreClient = Manticore::Client 16 | 17 | def self.enable! 18 | Manticore.send(:remove_const, :Client) 19 | Manticore.send(:const_set, :Client, WebMockManticoreClient) 20 | Manticore.instance_variable_set(:@manticore_facade, WebMockManticoreClient.new) 21 | end 22 | 23 | def self.disable! 24 | Manticore.send(:remove_const, :Client) 25 | Manticore.send(:const_set, :Client, OriginalManticoreClient) 26 | Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new) 27 | end 28 | 29 | class StubbedTimeoutResponse < Manticore::StubbedResponse 30 | def call 31 | @handlers[:failure].call(Manticore::ConnectTimeout.new("Too slow (mocked timeout)")) 32 | end 33 | end 34 | 35 | class WebMockManticoreClient < Manticore::Client 36 | def request(klass, url, options={}, &block) 37 | super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options)) 38 | end 39 | 40 | private 41 | 42 | def format_options(options) 43 | return options unless headers = options[:headers] 44 | 45 | options.merge(headers: join_array_values(headers)) 46 | end 47 | 48 | def join_array_values(headers) 49 | headers.reduce({}) do |h, (k,v)| 50 | v = v.join(', ') if v.is_a?(Array) 51 | h.merge(k => v) 52 | end 53 | end 54 | 55 | def response_object_for(request, context, &block) 56 | request_signature = generate_webmock_request_signature(request, context) 57 | WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) 58 | 59 | if webmock_response = registered_response_for(request_signature) 60 | webmock_response.raise_error_if_any 61 | manticore_response = generate_manticore_response(webmock_response) 62 | manticore_response.on_success do 63 | WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: false}, request_signature, webmock_response) 64 | end 65 | 66 | elsif real_request_allowed?(request_signature.uri) 67 | manticore_response = Manticore::Response.new(self, request, context, &block) 68 | manticore_response.on_complete do |completed_response| 69 | webmock_response = generate_webmock_response(completed_response) 70 | WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: true}, request_signature, webmock_response) 71 | end 72 | 73 | else 74 | raise WebMock::NetConnectNotAllowedError.new(request_signature) 75 | end 76 | 77 | manticore_response 78 | end 79 | 80 | def registered_response_for(request_signature) 81 | WebMock::StubRegistry.instance.response_for_request(request_signature) 82 | end 83 | 84 | def real_request_allowed?(uri) 85 | WebMock.net_connect_allowed?(uri) 86 | end 87 | 88 | def generate_webmock_request_signature(request, context) 89 | method = request.method.downcase 90 | uri = request.uri.to_s 91 | body = read_body(request) 92 | headers = split_array_values(request.headers) 93 | 94 | if context.get_credentials_provider && credentials = context.get_credentials_provider.get_credentials(AuthScope::ANY) 95 | headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(credentials.get_user_name,credentials.get_password) 96 | end 97 | 98 | WebMock::RequestSignature.new(method, uri, {body: body, headers: headers}) 99 | end 100 | 101 | def read_body(request) 102 | if request.respond_to?(:entity) && !request.entity.nil? 103 | Manticore::EntityConverter.new.read_entity(request.entity) 104 | end 105 | end 106 | 107 | def split_array_values(headers = []) 108 | headers.each_with_object({}) do |(k, v), h| 109 | h[k] = case v 110 | when /,/ then v.split(',').map(&:strip) 111 | else v 112 | end 113 | end 114 | end 115 | 116 | def generate_manticore_response(webmock_response) 117 | if webmock_response.should_timeout 118 | StubbedTimeoutResponse.new 119 | else 120 | Manticore::StubbedResponse.stub( 121 | code: webmock_response.status[0], 122 | body: webmock_response.body, 123 | headers: webmock_response.headers, 124 | cookies: {} 125 | ) 126 | end 127 | end 128 | 129 | def generate_webmock_response(manticore_response) 130 | webmock_response = WebMock::Response.new 131 | webmock_response.status = [manticore_response.code, manticore_response.message] 132 | webmock_response.headers = manticore_response.headers 133 | 134 | # The attempt to read the body could fail if manticore is used in a streaming mode 135 | webmock_response.body = begin 136 | manticore_response.body 137 | rescue ::Manticore::StreamClosedException 138 | nil 139 | end 140 | 141 | webmock_response 142 | end 143 | end 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/net_http_response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This code is entierly copied from VCR (http://github.com/myronmarston/vcr) by courtesy of Myron Marston 4 | 5 | # A Net::HTTP response that has already been read raises an IOError when #read_body 6 | # is called with a destination string or block. 7 | # 8 | # This causes a problem when VCR records a response--it reads the body before yielding 9 | # the response, and if the code that is consuming the HTTP requests uses #read_body, it 10 | # can cause an error. 11 | # 12 | # This is a bit of a hack, but it allows a Net::HTTP response to be "re-read" 13 | # after it has aleady been read. This attemps to preserve the behavior of 14 | # #read_body, acting just as if it had never been read. 15 | 16 | 17 | module Net 18 | module WebMockHTTPResponse 19 | def read_body(dest = nil, &block) 20 | if !(defined?(@__read_body_previously_called).nil?) && @__read_body_previously_called 21 | return super 22 | end 23 | return @body if dest.nil? && block.nil? 24 | raise ArgumentError.new("both arg and block given for HTTP method") if dest && block 25 | return nil if @body.nil? 26 | 27 | dest ||= ::Net::ReadAdapter.new(block) 28 | dest << @body.dup 29 | @body = dest 30 | ensure 31 | # allow subsequent calls to #read_body to proceed as normal, without our hack... 32 | @__read_body_previously_called = true 33 | end 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/webmock/http_lib_adapters/patron_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'patron' 5 | rescue LoadError 6 | # patron not found 7 | end 8 | 9 | if defined?(::Patron::Session) 10 | module WebMock 11 | module HttpLibAdapters 12 | class PatronAdapter < ::WebMock::HttpLibAdapter 13 | adapter_for :patron 14 | 15 | OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession) 16 | 17 | class WebMockPatronSession < ::Patron::Session 18 | def handle_request(req) 19 | request_signature = 20 | WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req) 21 | 22 | WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) 23 | 24 | if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature) 25 | WebMock::HttpLibAdapters::PatronAdapter. 26 | handle_file_name(req, webmock_response) 27 | res = WebMock::HttpLibAdapters::PatronAdapter. 28 | build_patron_response(webmock_response, default_response_charset) 29 | WebMock::CallbackRegistry.invoke_callbacks( 30 | {lib: :patron}, request_signature, webmock_response) 31 | res 32 | elsif WebMock.net_connect_allowed?(request_signature.uri) 33 | res = super 34 | if WebMock::CallbackRegistry.any_callbacks? 35 | webmock_response = WebMock::HttpLibAdapters::PatronAdapter. 36 | build_webmock_response(res) 37 | WebMock::CallbackRegistry.invoke_callbacks( 38 | {lib: :patron, real_request: true}, request_signature, 39 | webmock_response) 40 | end 41 | res 42 | else 43 | raise WebMock::NetConnectNotAllowedError.new(request_signature) 44 | end 45 | end 46 | end 47 | 48 | def self.enable! 49 | Patron.send(:remove_const, :Session) 50 | Patron.send(:const_set, :Session, WebMockPatronSession) 51 | end 52 | 53 | def self.disable! 54 | Patron.send(:remove_const, :Session) 55 | Patron.send(:const_set, :Session, OriginalPatronSession) 56 | end 57 | 58 | def self.handle_file_name(req, webmock_response) 59 | if req.action == :get && req.file_name 60 | begin 61 | File.open(req.file_name, "w") do |f| 62 | f.write webmock_response.body 63 | end 64 | rescue Errno::EACCES 65 | raise ArgumentError.new("Unable to open specified file.") 66 | end 67 | end 68 | end 69 | 70 | def self.build_request_signature(req) 71 | uri = WebMock::Util::URI.heuristic_parse(req.url) 72 | uri.path = uri.normalized_path.gsub("[^:]//","/") 73 | 74 | if [:put, :post, :patch].include?(req.action) 75 | if req.file_name 76 | if !File.exist?(req.file_name) || !File.readable?(req.file_name) 77 | raise ArgumentError.new("Unable to open specified file.") 78 | end 79 | request_body = File.read(req.file_name) 80 | elsif req.upload_data 81 | request_body = req.upload_data 82 | else 83 | raise ArgumentError.new("Must provide either data or a filename when doing a PUT or POST") 84 | end 85 | end 86 | 87 | headers = req.headers 88 | 89 | if req.credentials 90 | headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(req.credentials) 91 | end 92 | 93 | request_signature = WebMock::RequestSignature.new( 94 | req.action, 95 | uri.to_s, 96 | body: request_body, 97 | headers: headers 98 | ) 99 | request_signature 100 | end 101 | 102 | def self.build_patron_response(webmock_response, default_response_charset) 103 | raise ::Patron::TimeoutError if webmock_response.should_timeout 104 | webmock_response.raise_error_if_any 105 | 106 | header_fields = (webmock_response.headers || []).map { |(k, vs)| Array(vs).map { |v| "#{k}: #{v}" } }.flatten 107 | status_line = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}" 108 | header_data = ([status_line] + header_fields).join("\r\n") 109 | 110 | ::Patron::Response.new( 111 | "".dup, 112 | webmock_response.status[0], 113 | 0, 114 | header_data, 115 | webmock_response.body.dup, 116 | default_response_charset 117 | ) 118 | end 119 | 120 | def self.build_webmock_response(patron_response) 121 | webmock_response = WebMock::Response.new 122 | reason = patron_response.status_line. 123 | scan(%r(\AHTTP/(\d+(?:\.\d+)?)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2] 124 | webmock_response.status = [patron_response.status, reason] 125 | webmock_response.body = patron_response.body 126 | webmock_response.headers = patron_response.headers 127 | webmock_response 128 | end 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /lib/webmock/matchers/any_arg_matcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | module Matchers 5 | # this is a based on RSpec::Mocks::ArgumentMatchers::AnyArgMatcher 6 | class AnyArgMatcher 7 | def initialize(ignore) 8 | end 9 | 10 | def ==(other) 11 | true 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/webmock/matchers/hash_argument_matcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | module Matchers 5 | # Base class for Hash matchers 6 | # https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb 7 | class HashArgumentMatcher 8 | def initialize(expected) 9 | @expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, deep: true).sort] 10 | end 11 | 12 | def ==(_actual, &block) 13 | @expected.all?(&block) 14 | rescue NoMethodError 15 | false 16 | end 17 | 18 | def self.from_rspec_matcher(matcher) 19 | new(matcher.instance_variable_get(:@expected)) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/webmock/matchers/hash_excluding_matcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | module Matchers 5 | # this is a based on RSpec::Mocks::ArgumentMatchers::HashExcludingMatcher 6 | # https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb 7 | class HashExcludingMatcher < HashArgumentMatcher 8 | def ==(actual) 9 | super { |key, value| !actual.key?(key) || value != actual[key] } 10 | end 11 | 12 | def inspect 13 | "hash_excluding(#{@expected.inspect})" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/webmock/matchers/hash_including_matcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | module Matchers 5 | # this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher 6 | # https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb 7 | class HashIncludingMatcher < HashArgumentMatcher 8 | def ==(actual) 9 | super { |key, value| actual.key?(key) && value === actual[key] } 10 | rescue NoMethodError 11 | false 12 | end 13 | 14 | def inspect 15 | "hash_including(#{@expected.inspect})" 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/webmock/minitest.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'minitest/test' 5 | test_class = Minitest::Test 6 | assertions = "assertions" 7 | rescue LoadError 8 | require "minitest/unit" 9 | test_class = MiniTest::Unit::TestCase 10 | assertions = "_assertions" 11 | end 12 | 13 | require 'webmock' 14 | 15 | WebMock.enable! 16 | 17 | test_class.class_eval do 18 | include WebMock::API 19 | 20 | alias_method :teardown_without_webmock, :teardown 21 | def teardown_with_webmock 22 | teardown_without_webmock 23 | WebMock.reset! 24 | end 25 | alias_method :teardown, :teardown_with_webmock 26 | 27 | [:assert_request_requested, :assert_request_not_requested].each do |name| 28 | alias_method :"#{name}_without_assertions_count", name 29 | define_method :"#{name}_with_assertions_count" do |*args| 30 | self.send("#{assertions}=", self.send("#{assertions}") + 1) 31 | send :"#{name}_without_assertions_count", *args 32 | end 33 | alias_method name, :"#{name}_with_assertions_count" 34 | end 35 | end 36 | 37 | begin 38 | error_class = MiniTest::Assertion 39 | rescue NameError 40 | error_class = Minitest::Assertion 41 | end 42 | 43 | WebMock::AssertionFailure.error_class = error_class 44 | -------------------------------------------------------------------------------- /lib/webmock/rack_response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class RackResponse < Response 5 | def initialize(app) 6 | @app = app 7 | end 8 | 9 | def evaluate(request) 10 | env = build_rack_env(request) 11 | 12 | status, headers, response = @app.call(env) 13 | 14 | Response.new( 15 | body: body_from_rack_response(response), 16 | headers: headers, 17 | status: [status, Rack::Utils::HTTP_STATUS_CODES[status]] 18 | ) 19 | end 20 | 21 | def body_from_rack_response(response) 22 | body = "".dup 23 | response.each { |line| body << line } 24 | response.close if response.respond_to?(:close) 25 | return body 26 | end 27 | 28 | def build_rack_env(request) 29 | uri = request.uri 30 | headers = (request.headers || {}).dup 31 | body = request.body || '' 32 | 33 | env = { 34 | # CGI variables specified by Rack 35 | 'REQUEST_METHOD' => request.method.to_s.upcase, 36 | 'CONTENT_TYPE' => headers.delete('Content-Type'), 37 | 'CONTENT_LENGTH' => body.bytesize, 38 | 'PATH_INFO' => uri.path, 39 | 'QUERY_STRING' => uri.query || '', 40 | 'SERVER_NAME' => uri.host, 41 | 'SERVER_PORT' => uri.port, 42 | 'SCRIPT_NAME' => "" 43 | } 44 | 45 | env['HTTP_AUTHORIZATION'] = 'Basic ' + [uri.userinfo].pack('m').delete("\r\n") if uri.userinfo 46 | 47 | # Rack-specific variables 48 | env['rack.input'] = StringIO.new(body) 49 | env['rack.errors'] = $stderr 50 | if !Rack.const_defined?(:RELEASE) || Rack::RELEASE < "3" 51 | env['rack.version'] = Rack::VERSION 52 | end 53 | env['rack.url_scheme'] = uri.scheme 54 | env['rack.run_once'] = true 55 | env['rack.session'] = session 56 | env['rack.session.options'] = session_options 57 | 58 | headers.each do |k, v| 59 | env["HTTP_#{k.tr('-','_').upcase}"] = v 60 | end 61 | 62 | env 63 | end 64 | 65 | def session 66 | @session ||= {} 67 | end 68 | 69 | def session_options 70 | @session_options ||= {} 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/webmock/request_body_diff.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "hashdiff" 4 | require "json" 5 | 6 | module WebMock 7 | class RequestBodyDiff 8 | 9 | def initialize(request_signature, request_stub) 10 | @request_signature = request_signature 11 | @request_stub = request_stub 12 | end 13 | 14 | def body_diff 15 | return {} unless request_signature_diffable? && request_stub_diffable? 16 | 17 | Hashdiff.diff(request_signature_body_hash, request_stub_body_hash) 18 | end 19 | 20 | attr_reader :request_signature, :request_stub 21 | private :request_signature, :request_stub 22 | 23 | private 24 | 25 | def request_signature_diffable? 26 | request_signature.json_headers? && request_signature_parseable_json? 27 | end 28 | 29 | def request_stub_diffable? 30 | request_stub_body.is_a?(Hash) || request_stub_parseable_json? 31 | end 32 | 33 | def request_signature_body_hash 34 | JSON.parse(request_signature.body) 35 | end 36 | 37 | def request_stub_body_hash 38 | return request_stub_body if request_stub_body.is_a?(Hash) 39 | 40 | JSON.parse(request_stub_body) 41 | end 42 | 43 | def request_stub_body 44 | request_stub.request_pattern && 45 | request_stub.request_pattern.body_pattern && 46 | request_stub.request_pattern.body_pattern.pattern 47 | end 48 | 49 | def request_signature_parseable_json? 50 | parseable_json?(request_signature.body) 51 | end 52 | 53 | def request_stub_parseable_json? 54 | parseable_json?(request_stub_body) 55 | end 56 | 57 | def parseable_json?(body_pattern) 58 | return false unless body_pattern.is_a?(String) 59 | 60 | JSON.parse(body_pattern) 61 | true 62 | rescue JSON::ParserError 63 | false 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/webmock/request_execution_verifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class RequestExecutionVerifier 5 | 6 | attr_accessor :request_pattern, :expected_times_executed, :times_executed, :at_least_times_executed, :at_most_times_executed 7 | 8 | def initialize(request_pattern = nil, expected_times_executed = nil, at_least_times_executed = nil, at_most_times_executed = nil) 9 | @request_pattern = request_pattern 10 | @expected_times_executed = expected_times_executed 11 | @at_least_times_executed = at_least_times_executed 12 | @at_most_times_executed = at_most_times_executed 13 | end 14 | 15 | def matches? 16 | @times_executed = 17 | RequestRegistry.instance.times_executed(@request_pattern) 18 | 19 | if @at_least_times_executed 20 | @times_executed >= @at_least_times_executed 21 | elsif @at_most_times_executed 22 | @times_executed <= @at_most_times_executed 23 | else 24 | @times_executed == (@expected_times_executed || 1) 25 | end 26 | end 27 | 28 | def does_not_match? 29 | @times_executed = 30 | RequestRegistry.instance.times_executed(@request_pattern) 31 | if @expected_times_executed 32 | @times_executed != @expected_times_executed 33 | else 34 | @times_executed == 0 35 | end 36 | end 37 | 38 | def description 39 | "request #{request_pattern} #{quantity_phrase.strip}" 40 | end 41 | 42 | def failure_message 43 | failure_message_phrase(false) 44 | end 45 | 46 | def failure_message_when_negated 47 | failure_message_phrase(true) 48 | end 49 | 50 | def self.executed_requests_message 51 | "\n\nThe following requests were made:\n\n#{RequestRegistry.instance}\n" + "="*60 52 | end 53 | 54 | private 55 | 56 | def failure_message_phrase(is_negated=false) 57 | negation = is_negated ? "was not" : "was" 58 | "The request #{request_pattern} #{negation} expected to execute #{quantity_phrase(is_negated)}but it executed #{times(times_executed)}" + 59 | self.class.executed_requests_message 60 | end 61 | 62 | def quantity_phrase(is_negated=false) 63 | if @at_least_times_executed 64 | "at least #{times(@at_least_times_executed)} " 65 | elsif @at_most_times_executed 66 | "at most #{times(@at_most_times_executed)} " 67 | elsif @expected_times_executed 68 | "#{times(@expected_times_executed)} " 69 | else 70 | is_negated ? "" : "#{times(1)} " 71 | end 72 | end 73 | 74 | def times(times) 75 | "#{times} time#{ (times == 1) ? '' : 's'}" 76 | end 77 | 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/webmock/request_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | class RequestRegistry 6 | include Singleton 7 | 8 | attr_accessor :requested_signatures 9 | 10 | def initialize 11 | reset! 12 | end 13 | 14 | def reset! 15 | self.requested_signatures = Util::HashCounter.new 16 | end 17 | 18 | def times_executed(request_pattern) 19 | self.requested_signatures.select do |request_signature| 20 | request_pattern.matches?(request_signature) 21 | end.inject(0) { |sum, (_, times_executed)| sum + times_executed } 22 | end 23 | 24 | def to_s 25 | if requested_signatures.hash.empty? 26 | "No requests were made." 27 | else 28 | text = "".dup 29 | self.requested_signatures.each do |request_signature, times_executed| 30 | text << "#{request_signature} was made #{times_executed} time#{times_executed == 1 ? '' : 's' }\n" 31 | end 32 | text 33 | end 34 | end 35 | 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/webmock/request_signature.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | class RequestSignature 6 | 7 | attr_accessor :method, :uri, :body 8 | attr_reader :headers 9 | 10 | def initialize(method, uri, options = {}) 11 | self.method = method.to_sym 12 | self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri) 13 | assign_options(options) 14 | end 15 | 16 | def to_s 17 | string = "#{self.method.to_s.upcase}".dup 18 | string << " #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}" 19 | string << " with body '#{body.to_s}'" if body && body.to_s != '' 20 | if headers && !headers.empty? 21 | string << " with headers #{WebMock::Util::Headers.sorted_headers_string(headers)}" 22 | end 23 | string 24 | end 25 | 26 | def headers=(headers) 27 | @headers = WebMock::Util::Headers.normalize_headers(headers) 28 | end 29 | 30 | def hash 31 | self.to_s.hash 32 | end 33 | 34 | def eql?(other) 35 | self.to_s == other.to_s 36 | end 37 | alias == eql? 38 | 39 | def url_encoded? 40 | !!(headers&.fetch('Content-Type', nil)&.start_with?('application/x-www-form-urlencoded')) 41 | end 42 | 43 | def json_headers? 44 | !!(headers&.fetch('Content-Type', nil)&.start_with?('application/json')) 45 | end 46 | 47 | private 48 | 49 | def assign_options(options) 50 | self.body = options[:body] if options.has_key?(:body) 51 | self.headers = options[:headers] if options.has_key?(:headers) 52 | end 53 | 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/webmock/request_signature_snippet.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "pp" 4 | 5 | module WebMock 6 | class RequestSignatureSnippet 7 | 8 | attr_reader :request_signature, :request_stub 9 | 10 | def initialize(request_signature) 11 | @request_signature = request_signature 12 | @request_stub = RequestStub.from_request_signature(request_signature) 13 | end 14 | 15 | def stubbing_instructions 16 | return unless WebMock.show_stubbing_instructions? 17 | 18 | "You can stub this request with the following snippet:\n\n" + 19 | WebMock::StubRequestSnippet.new(request_stub).to_s 20 | end 21 | 22 | def request_stubs 23 | return if WebMock::StubRegistry.instance.request_stubs.empty? 24 | 25 | text = "registered request stubs:\n".dup 26 | WebMock::StubRegistry.instance.request_stubs.each do |stub| 27 | text << "\n#{WebMock::StubRequestSnippet.new(stub).to_s(false)}" 28 | add_body_diff(stub, text) if WebMock.show_body_diff? 29 | end 30 | text 31 | end 32 | 33 | private 34 | 35 | def add_body_diff(stub, text) 36 | body_diff_str = signature_stub_body_diff(stub) 37 | text << "\n\n#{body_diff_str}" unless body_diff_str.empty? 38 | end 39 | 40 | def signature_stub_body_diff(stub) 41 | diff = RequestBodyDiff.new(request_signature, stub).body_diff 42 | diff.empty? ? "" : "Body diff:\n #{pretty_print_to_string(diff)}" 43 | end 44 | 45 | def request_params 46 | @request_params ||= 47 | if request_signature.json_headers? 48 | JSON.parse(request_signature.body) 49 | else 50 | "" 51 | end 52 | end 53 | 54 | def pretty_print_to_string(string_to_print) 55 | StringIO.open("".dup) do |stream| 56 | PP.pp(string_to_print, stream) 57 | stream.rewind 58 | stream.read 59 | end 60 | end 61 | 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/webmock/request_stub.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class RequestStub 5 | 6 | attr_accessor :request_pattern 7 | 8 | def initialize(method, uri) 9 | @request_pattern = RequestPattern.new(method, uri) 10 | @responses_sequences = [] 11 | self 12 | end 13 | 14 | def with(params = {}, &block) 15 | @request_pattern.with(params, &block) 16 | self 17 | end 18 | 19 | def to_return(*response_hashes, &block) 20 | if block 21 | @responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(block)]) 22 | else 23 | @responses_sequences << ResponsesSequence.new([*response_hashes].flatten.map {|r| ResponseFactory.response_for(r)}) 24 | end 25 | self 26 | end 27 | alias_method :and_return, :to_return 28 | 29 | def to_return_json(*response_hashes) 30 | raise ArgumentError, '#to_return_json does not support passing a block' if block_given? 31 | 32 | json_response_hashes = [*response_hashes].flatten.map do |resp_h| 33 | headers, body = resp_h.values_at(:headers, :body) 34 | 35 | json_body = if body.respond_to?(:call) 36 | ->(request_signature) { 37 | b = if body.respond_to?(:arity) && body.arity == 1 38 | body.call(request_signature) 39 | else 40 | body.call 41 | end 42 | b = b.to_json unless b.is_a?(String) 43 | b 44 | } 45 | elsif !body.is_a?(String) 46 | body.to_json 47 | else 48 | body 49 | end 50 | 51 | resp_h.merge( 52 | headers: {content_type: 'application/json'}.merge(headers.to_h), 53 | body: json_body 54 | ) 55 | end 56 | 57 | to_return(json_response_hashes) 58 | end 59 | alias_method :and_return_json, :to_return_json 60 | 61 | def to_rack(app, options={}) 62 | @responses_sequences << ResponsesSequence.new([RackResponse.new(app)]) 63 | end 64 | 65 | def to_raise(*exceptions) 66 | @responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e| 67 | ResponseFactory.response_for(exception: e) 68 | }) 69 | self 70 | end 71 | alias_method :and_raise, :to_raise 72 | 73 | def to_timeout 74 | @responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(should_timeout: true)]) 75 | self 76 | end 77 | alias_method :and_timeout, :to_timeout 78 | 79 | def response 80 | if @responses_sequences.empty? 81 | WebMock::Response.new 82 | elsif @responses_sequences.length > 1 83 | @responses_sequences.shift if @responses_sequences.first.end? 84 | @responses_sequences.first.next_response 85 | else 86 | @responses_sequences[0].next_response 87 | end 88 | end 89 | 90 | def has_responses? 91 | !@responses_sequences.empty? 92 | end 93 | 94 | def then 95 | self 96 | end 97 | 98 | def times(number) 99 | raise "times(N) accepts integers >= 1 only" if !number.is_a?(Integer) || number < 1 100 | if @responses_sequences.empty? 101 | raise "Invalid WebMock stub declaration." + 102 | " times(N) can be declared only after response declaration." 103 | end 104 | @responses_sequences.last.times_to_repeat += number-1 105 | self 106 | end 107 | 108 | def matches?(request_signature) 109 | self.request_pattern.matches?(request_signature) 110 | end 111 | 112 | def to_s 113 | self.request_pattern.to_s 114 | end 115 | 116 | def self.from_request_signature(signature) 117 | stub = self.new(signature.method.to_sym, signature.uri.to_s) 118 | 119 | if signature.body.to_s != '' 120 | body = if signature.url_encoded? 121 | WebMock::Util::QueryMapper.query_to_values(signature.body, notation: Config.instance.query_values_notation) 122 | else 123 | signature.body 124 | end 125 | stub.with(body: body) 126 | end 127 | 128 | if (signature.headers && !signature.headers.empty?) 129 | stub.with(headers: signature.headers) 130 | end 131 | stub 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/webmock/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "pathname" 4 | 5 | module WebMock 6 | 7 | class ResponseFactory 8 | def self.response_for(options) 9 | if options.respond_to?(:call) 10 | WebMock::DynamicResponse.new(options) 11 | else 12 | WebMock::Response.new(options) 13 | end 14 | end 15 | end 16 | 17 | class Response 18 | def initialize(options = {}) 19 | case options 20 | when IO, StringIO 21 | self.options = read_raw_response(options) 22 | when String 23 | self.options = read_raw_response(StringIO.new(options)) 24 | else 25 | self.options = options 26 | end 27 | end 28 | 29 | def headers 30 | @headers 31 | end 32 | 33 | def headers=(headers) 34 | @headers = headers 35 | if @headers && !@headers.is_a?(Proc) 36 | @headers = Util::Headers.normalize_headers(@headers) 37 | end 38 | end 39 | 40 | def body 41 | @body || String.new("") 42 | end 43 | 44 | def body=(body) 45 | @body = body 46 | assert_valid_body! 47 | stringify_body! 48 | end 49 | 50 | def status 51 | @status || [200, ""] 52 | end 53 | 54 | def status=(status) 55 | @status = status.is_a?(Integer) ? [status, ""] : status 56 | end 57 | 58 | def exception 59 | @exception 60 | end 61 | 62 | def exception=(exception) 63 | @exception = case exception 64 | when String then StandardError.new(exception) 65 | when Class then exception.new('Exception from WebMock') 66 | when Exception then exception 67 | end 68 | end 69 | 70 | def raise_error_if_any 71 | raise @exception if @exception 72 | end 73 | 74 | def should_timeout 75 | @should_timeout == true 76 | end 77 | 78 | def options=(options) 79 | options = WebMock::Util::HashKeysStringifier.stringify_keys!(options) 80 | HashValidator.new(options).validate_keys('headers', 'status', 'body', 'exception', 'should_timeout') 81 | self.headers = options['headers'] 82 | self.status = options['status'] 83 | self.body = options['body'] 84 | self.exception = options['exception'] 85 | @should_timeout = options['should_timeout'] 86 | end 87 | 88 | def evaluate(request_signature) 89 | self.body = @body.call(request_signature) if @body.is_a?(Proc) 90 | self.headers = @headers.call(request_signature) if @headers.is_a?(Proc) 91 | self.status = @status.call(request_signature) if @status.is_a?(Proc) 92 | @should_timeout = @should_timeout.call(request_signature) if @should_timeout.is_a?(Proc) 93 | @exception = @exception.call(request_signature) if @exception.is_a?(Proc) 94 | self 95 | end 96 | 97 | def ==(other) 98 | self.body == other.body && 99 | self.headers === other.headers && 100 | self.status == other.status && 101 | self.exception == other.exception && 102 | self.should_timeout == other.should_timeout 103 | end 104 | 105 | private 106 | 107 | def stringify_body! 108 | if @body.is_a?(IO) || @body.is_a?(Pathname) 109 | io = @body 110 | @body = io.read 111 | io.close if io.respond_to?(:close) 112 | end 113 | end 114 | 115 | def assert_valid_body! 116 | valid_types = [Proc, IO, Pathname, String, Array] 117 | return if @body.nil? 118 | return if valid_types.any? { |c| @body.is_a?(c) } 119 | 120 | if @body.is_a?(Hash) 121 | raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}. " \ 122 | "Please convert it by calling .to_json .to_xml, or otherwise convert it to a string." 123 | else 124 | raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given." 125 | end 126 | end 127 | 128 | def read_raw_response(io) 129 | socket = ::Net::BufferedIO.new(io) 130 | response = ::Net::HTTPResponse.read_new(socket) 131 | transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl 132 | response.reading_body(socket, true) {} 133 | 134 | options = {} 135 | options[:headers] = {} 136 | response.each_header {|name, value| options[:headers][name] = value} 137 | options[:headers]['transfer-encoding'] = transfer_encoding if transfer_encoding 138 | options[:body] = response.read_body 139 | options[:status] = [response.code.to_i, response.message] 140 | options 141 | ensure 142 | socket.close 143 | end 144 | 145 | InvalidBody = Class.new(StandardError) 146 | 147 | end 148 | 149 | class DynamicResponse < Response 150 | attr_accessor :responder 151 | 152 | def initialize(responder) 153 | @responder = responder 154 | end 155 | 156 | def evaluate(request_signature) 157 | options = @responder.call(request_signature) 158 | Response.new(options) 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/webmock/responses_sequence.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | class ResponsesSequence 6 | 7 | attr_accessor :times_to_repeat 8 | 9 | def initialize(responses) 10 | @times_to_repeat = 1 11 | @responses = responses 12 | @current_position = 0 13 | end 14 | 15 | def end? 16 | @times_to_repeat == 0 17 | end 18 | 19 | def next_response 20 | if @times_to_repeat > 0 21 | response = @responses[@current_position] 22 | increase_position 23 | response 24 | else 25 | @responses.last 26 | end 27 | end 28 | 29 | private 30 | 31 | def increase_position 32 | if @current_position == (@responses.length - 1) 33 | @current_position = 0 34 | @times_to_repeat -= 1 35 | else 36 | @current_position += 1 37 | end 38 | end 39 | 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/webmock/rspec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'webmock' 4 | 5 | # RSpec 1.x and 2.x compatibility 6 | if defined?(RSpec::Expectations::ExpectationNotMetError) 7 | RSPEC_NAMESPACE = RSPEC_CONFIGURER = RSpec 8 | elsif defined?(Spec) && defined?(Spec.configure) 9 | RSPEC_NAMESPACE = Spec 10 | RSPEC_CONFIGURER = Spec::Runner 11 | else 12 | begin 13 | require 'rspec/core' 14 | require 'rspec/expectations' 15 | RSPEC_NAMESPACE = RSPEC_CONFIGURER = RSpec 16 | rescue LoadError 17 | require 'spec' 18 | RSPEC_NAMESPACE = Spec 19 | RSPEC_CONFIGURER = Spec::Runner 20 | end 21 | end 22 | 23 | require 'webmock/rspec/matchers' 24 | 25 | RSPEC_CONFIGURER.configure { |config| 26 | 27 | config.include WebMock::API 28 | config.include WebMock::Matchers 29 | 30 | config.before(:suite) do 31 | WebMock.enable! 32 | end 33 | 34 | config.after(:suite) do 35 | WebMock.disable! 36 | end 37 | 38 | config.around(:each) do |example| 39 | example.run 40 | WebMock.reset! 41 | end 42 | } 43 | 44 | WebMock::AssertionFailure.error_class = RSPEC_NAMESPACE::Expectations::ExpectationNotMetError 45 | -------------------------------------------------------------------------------- /lib/webmock/rspec/matchers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'webmock' 4 | require 'webmock/rspec/matchers/request_pattern_matcher' 5 | require 'webmock/rspec/matchers/webmock_matcher' 6 | 7 | module WebMock 8 | module Matchers 9 | def have_been_made 10 | WebMock::RequestPatternMatcher.new 11 | end 12 | 13 | def have_been_requested 14 | WebMock::RequestPatternMatcher.new 15 | end 16 | 17 | def have_not_been_made 18 | WebMock::RequestPatternMatcher.new.times(0) 19 | end 20 | 21 | def have_requested(method, uri) 22 | WebMock::WebMockMatcher.new(method, uri) 23 | end 24 | 25 | def have_not_requested(method, uri) 26 | WebMock::WebMockMatcher.new(method, uri).times(0) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/webmock/rspec/matchers/request_pattern_matcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class RequestPatternMatcher 5 | 6 | def initialize 7 | @request_execution_verifier = RequestExecutionVerifier.new 8 | end 9 | 10 | def once 11 | @request_execution_verifier.expected_times_executed = 1 12 | self 13 | end 14 | 15 | def twice 16 | @request_execution_verifier.expected_times_executed = 2 17 | self 18 | end 19 | 20 | def times(times) 21 | @request_execution_verifier.expected_times_executed = times.to_i 22 | self 23 | end 24 | 25 | def at_least_once 26 | @request_execution_verifier.at_least_times_executed = 1 27 | self 28 | end 29 | 30 | def at_least_twice 31 | @request_execution_verifier.at_least_times_executed = 2 32 | self 33 | end 34 | 35 | def at_least_times(times) 36 | @request_execution_verifier.at_least_times_executed = times.to_i 37 | self 38 | end 39 | 40 | def at_most_once 41 | @request_execution_verifier.at_most_times_executed = 1 42 | self 43 | end 44 | 45 | def at_most_twice 46 | @request_execution_verifier.at_most_times_executed = 2 47 | self 48 | end 49 | 50 | def at_most_times(times) 51 | @request_execution_verifier.at_most_times_executed = times.to_i 52 | self 53 | end 54 | 55 | def matches?(request_pattern) 56 | @request_execution_verifier.request_pattern = request_pattern 57 | @request_execution_verifier.matches? 58 | end 59 | 60 | def does_not_match?(request_pattern) 61 | @request_execution_verifier.request_pattern = request_pattern 62 | @request_execution_verifier.does_not_match? 63 | end 64 | 65 | def failure_message 66 | @request_execution_verifier.failure_message 67 | end 68 | 69 | def failure_message_when_negated 70 | @request_execution_verifier.failure_message_when_negated 71 | end 72 | 73 | def description 74 | @request_execution_verifier.description 75 | end 76 | 77 | # RSpec 2 compatibility: 78 | alias_method :negative_failure_message, :failure_message_when_negated 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/webmock/rspec/matchers/webmock_matcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class WebMockMatcher 5 | 6 | def initialize(method, uri) 7 | @request_execution_verifier = RequestExecutionVerifier.new 8 | @request_execution_verifier.request_pattern = RequestPattern.new(method, uri) 9 | end 10 | 11 | def once 12 | @request_execution_verifier.expected_times_executed = 1 13 | self 14 | end 15 | 16 | def twice 17 | @request_execution_verifier.expected_times_executed = 2 18 | self 19 | end 20 | 21 | def at_least_once 22 | @request_execution_verifier.at_least_times_executed = 1 23 | self 24 | end 25 | 26 | def at_least_twice 27 | @request_execution_verifier.at_least_times_executed = 2 28 | self 29 | end 30 | 31 | def at_least_times(times) 32 | @request_execution_verifier.at_least_times_executed = times 33 | self 34 | end 35 | 36 | def with(options = {}, &block) 37 | @request_execution_verifier.request_pattern.with(options, &block) 38 | self 39 | end 40 | 41 | def times(times) 42 | @request_execution_verifier.expected_times_executed = times.to_i 43 | self 44 | end 45 | 46 | def matches?(webmock) 47 | @request_execution_verifier.matches? 48 | end 49 | 50 | def does_not_match?(webmock) 51 | @request_execution_verifier.does_not_match? 52 | end 53 | 54 | def failure_message 55 | @request_execution_verifier.failure_message 56 | end 57 | 58 | def failure_message_when_negated 59 | @request_execution_verifier.failure_message_when_negated 60 | end 61 | 62 | def description 63 | @request_execution_verifier.description 64 | end 65 | 66 | # RSpec 2 compatibility: 67 | alias_method :negative_failure_message, :failure_message_when_negated 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/webmock/stub_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | class StubRegistry 6 | include Singleton 7 | 8 | attr_accessor :request_stubs 9 | 10 | def initialize 11 | reset! 12 | end 13 | 14 | def global_stubs 15 | @global_stubs ||= Hash.new { |h, k| h[k] = [] } 16 | end 17 | 18 | def reset! 19 | self.request_stubs = [] 20 | end 21 | 22 | def register_global_stub(order = :before_local_stubs, &block) 23 | unless %i[before_local_stubs after_local_stubs].include?(order) 24 | raise ArgumentError.new("Wrong order. Use :before_local_stubs or :after_local_stubs") 25 | end 26 | 27 | # This hash contains the responses returned by the block, 28 | # keyed by the exact request (using the object_id). 29 | # That way, there's no race condition in case #to_return 30 | # doesn't run immediately after stub.with. 31 | responses = {} 32 | response_lock = Mutex.new 33 | 34 | stub = ::WebMock::RequestStub.new(:any, ->(uri) { true }).with { |request| 35 | update_response = -> { responses[request.object_id] = yield(request) } 36 | 37 | # The block can recurse, so only lock if we don't already own it 38 | if response_lock.owned? 39 | update_response.call 40 | else 41 | response_lock.synchronize(&update_response) 42 | end 43 | }.to_return(lambda { |request| 44 | response_lock.synchronize { responses.delete(request.object_id) } 45 | }) 46 | 47 | global_stubs[order].push stub 48 | end 49 | 50 | def register_request_stub(stub) 51 | request_stubs.insert(0, stub) 52 | stub 53 | end 54 | 55 | def remove_request_stub(stub) 56 | if not request_stubs.delete(stub) 57 | raise "Request stub \n\n #{stub.to_s} \n\n is not registered." 58 | end 59 | end 60 | 61 | def registered_request?(request_signature) 62 | request_stub_for(request_signature) 63 | end 64 | 65 | def response_for_request(request_signature) 66 | stub = request_stub_for(request_signature) 67 | stub ? evaluate_response_for_request(stub.response, request_signature) : nil 68 | end 69 | 70 | private 71 | 72 | def request_stub_for(request_signature) 73 | (global_stubs[:before_local_stubs] + request_stubs + global_stubs[:after_local_stubs]) 74 | .detect { |registered_request_stub| 75 | registered_request_stub.request_pattern.matches?(request_signature) 76 | } 77 | end 78 | 79 | def evaluate_response_for_request(response, request_signature) 80 | response.dup.evaluate(request_signature) 81 | end 82 | 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/webmock/stub_request_snippet.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class StubRequestSnippet 5 | def initialize(request_stub) 6 | @request_stub = request_stub 7 | end 8 | 9 | def body_pattern 10 | request_pattern.body_pattern 11 | end 12 | 13 | def to_s(with_response = true) 14 | request_pattern = @request_stub.request_pattern 15 | string = "stub_request(:#{request_pattern.method_pattern.to_s},".dup 16 | string << " \"#{request_pattern.uri_pattern.to_s}\")" 17 | 18 | with = "".dup 19 | 20 | if (request_pattern.body_pattern) 21 | with << "\n body: #{request_pattern.body_pattern.to_s}" 22 | end 23 | 24 | if (request_pattern.headers_pattern) 25 | with << "," unless with.empty? 26 | 27 | with << "\n headers: #{request_pattern.headers_pattern.pp_to_s}" 28 | end 29 | string << ".\n with(#{with})" unless with.empty? 30 | if with_response 31 | if request_pattern.headers_pattern && request_pattern.headers_pattern.matches?({ 'Accept' => "application/json" }) 32 | string << ".\n to_return(status: 200, body: \"{}\", headers: {})" 33 | else 34 | string << ".\n to_return(status: 200, body: \"\", headers: {})" 35 | end 36 | end 37 | string 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/webmock/test_unit.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test/unit' 4 | require 'webmock' 5 | 6 | WebMock.enable! 7 | 8 | module Test 9 | module Unit 10 | class TestCase 11 | include WebMock::API 12 | 13 | teardown 14 | def teardown_with_webmock 15 | WebMock.reset! 16 | end 17 | 18 | end 19 | end 20 | end 21 | 22 | WebMock::AssertionFailure.error_class = Test::Unit::AssertionFailedError 23 | -------------------------------------------------------------------------------- /lib/webmock/util/hash_counter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'thread' 4 | 5 | module WebMock 6 | module Util 7 | class HashCounter 8 | attr_accessor :hash 9 | 10 | def initialize 11 | self.hash = Hash.new(0) 12 | @order = {} 13 | @max = 0 14 | @lock = ::Mutex.new 15 | end 16 | 17 | def put(key, num=1) 18 | @lock.synchronize do 19 | hash[key] += num 20 | @order[key] = @max += 1 21 | end 22 | end 23 | 24 | def get(key) 25 | @lock.synchronize do 26 | hash[key] 27 | end 28 | end 29 | 30 | def select(&block) 31 | return unless block_given? 32 | 33 | @lock.synchronize do 34 | hash.select(&block) 35 | end 36 | end 37 | 38 | def each(&block) 39 | @order.to_a.sort_by { |a| a[1] }.each do |a| 40 | yield(a[0], hash[a[0]]) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/webmock/util/hash_keys_stringifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | module Util 5 | class HashKeysStringifier 6 | 7 | def self.stringify_keys!(arg, options = {}) 8 | case arg 9 | when Array 10 | arg.map { |elem| 11 | options[:deep] ? stringify_keys!(elem, options) : elem 12 | } 13 | when Hash 14 | Hash[ 15 | *arg.map { |key, value| 16 | k = key.is_a?(Symbol) ? key.to_s : key 17 | v = (options[:deep] ? stringify_keys!(value, options) : value) 18 | [k,v] 19 | }.inject([]) {|r,x| r + x}] 20 | else 21 | arg 22 | end 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/webmock/util/hash_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | class HashValidator 5 | def initialize(hash) 6 | @hash = hash 7 | end 8 | 9 | #This code is based on https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/keys.rb 10 | def validate_keys(*valid_keys) 11 | valid_keys.flatten! 12 | @hash.each_key do |k| 13 | unless valid_keys.include?(k) 14 | raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}") 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/webmock/util/headers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | module Util 6 | 7 | class Headers 8 | 9 | STANDARD_HEADER_DELIMITER = '-' 10 | NONSTANDARD_HEADER_DELIMITER = '_' 11 | JOIN = ', ' 12 | 13 | def self.normalize_headers(headers) 14 | return nil unless headers 15 | 16 | headers.each_with_object({}) do |(name, value), new_headers| 17 | new_headers[normalize_name(name)] = 18 | case value 19 | when Regexp then value 20 | when Array then (value.size == 1) ? value.first.to_s : value.map(&:to_s).sort 21 | else value.to_s 22 | end 23 | end 24 | end 25 | 26 | def self.sorted_headers_string(headers) 27 | headers = WebMock::Util::Headers.normalize_headers(headers) 28 | str = '{'.dup 29 | str << headers.map do |k,v| 30 | v = case v 31 | when Regexp then v.inspect 32 | when Array then "["+v.map{|w| "'#{w.to_s}'"}.join(", ")+"]" 33 | else "'#{v.to_s}'" 34 | end 35 | "'#{k}'=>#{v}" 36 | end.sort.join(", ") 37 | str << '}' 38 | end 39 | 40 | def self.pp_headers_string(headers) 41 | headers = WebMock::Util::Headers.normalize_headers(headers) 42 | seperator = "\n\t " 43 | str = "{#{seperator} ".dup 44 | str << headers.map do |k,v| 45 | v = case v 46 | when Regexp then v.inspect 47 | when Array then "["+v.map{|w| "'#{w.to_s}'"}.join(", ")+"]" 48 | else "'#{v.to_s}'" 49 | end 50 | "'#{k}'=>#{v}" 51 | end.sort.join(",#{seperator} ") 52 | str << "\n }" 53 | end 54 | 55 | def self.decode_userinfo_from_header(header) 56 | header.sub(/^Basic /, "").unpack("m").first 57 | end 58 | 59 | def self.basic_auth_header(*credentials) 60 | strict_base64_encoded = [credentials.join(':')].pack("m0") 61 | "Basic #{strict_base64_encoded.chomp}" 62 | end 63 | 64 | def self.normalize_name(name) 65 | name 66 | .to_s 67 | .tr(NONSTANDARD_HEADER_DELIMITER, STANDARD_HEADER_DELIMITER) 68 | .split(STANDARD_HEADER_DELIMITER) 69 | .map!(&:capitalize) 70 | .join(STANDARD_HEADER_DELIMITER) 71 | end 72 | 73 | end 74 | 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /lib/webmock/util/parsers/json.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This is a copy of https://github.com/jnunemaker/crack/blob/master/lib/crack/json.rb 4 | # with date parsing removed 5 | # Copyright (c) 2004-2008 David Heinemeier Hansson 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | require_relative "parse_error" 11 | 12 | module WebMock 13 | module Util 14 | module Parsers 15 | class JSON 16 | def self.parse(json) 17 | yaml = unescape(convert_json_to_yaml(json)) 18 | YAML.load(yaml) 19 | rescue ArgumentError, Psych::SyntaxError => e 20 | raise ParseError, "Invalid JSON string: #{yaml}, Error: #{e.inspect}" 21 | end 22 | 23 | protected 24 | 25 | def self.unescape(str) 26 | str.gsub(/\\u([0-9a-f]{4})/) { [$1.hex].pack("U") } 27 | end 28 | 29 | # Ensure that ":" and "," are always followed by a space 30 | def self.convert_json_to_yaml(json) #:nodoc: 31 | scanner, quoting, marks, times = StringScanner.new(json), false, [], [] 32 | while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/) 33 | case char = scanner[1] 34 | when '"', "'" 35 | if !quoting 36 | quoting = char 37 | elsif quoting == char 38 | quoting = false 39 | end 40 | when ":","," 41 | marks << scanner.pos - 1 unless quoting 42 | when "\\" 43 | scanner.skip(/\\/) 44 | end 45 | end 46 | 47 | if marks.empty? 48 | json.gsub(/\\\//, '/') 49 | else 50 | left_pos = [-1].push(*marks) 51 | right_pos = marks << json.bytesize 52 | output = [] 53 | 54 | left_pos.each_with_index do |left, i| 55 | if json.respond_to?(:byteslice) 56 | output << json.byteslice(left.succ..right_pos[i]) 57 | else 58 | output << json[left.succ..right_pos[i]] 59 | end 60 | end 61 | 62 | output = output * " " 63 | 64 | times.each { |i| output[i-1] = ' ' } 65 | output.gsub!(/\\\//, '/') 66 | output 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end -------------------------------------------------------------------------------- /lib/webmock/util/parsers/parse_error.rb: -------------------------------------------------------------------------------- 1 | module WebMock 2 | module Util 3 | module Parsers 4 | class ParseError < StandardError; end 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /lib/webmock/util/parsers/xml.rb: -------------------------------------------------------------------------------- 1 | require_relative "parse_error" 2 | require "crack/xml" 3 | 4 | module WebMock 5 | module Util 6 | module Parsers 7 | class XML 8 | def self.parse(xml) 9 | ::Crack::XML.parse(xml) 10 | rescue ::REXML::ParseException => e 11 | raise ParseError, "Invalid XML string: #{xml}, Error: #{e.inspect}" 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/webmock/util/uri.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | module Util 6 | 7 | class URI 8 | module CharacterClasses 9 | USERINFO = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS + "\\:" 10 | end 11 | 12 | ADDRESSABLE_URIS = Hash.new do |hash, key| 13 | hash[key] = Addressable::URI.heuristic_parse(key) 14 | end 15 | 16 | NORMALIZED_URIS = Hash.new do |hash, uri| 17 | normalized_uri = WebMock::Util::URI.heuristic_parse(uri) 18 | if normalized_uri.query_values 19 | sorted_query_values = sort_query_values(WebMock::Util::QueryMapper.query_to_values(normalized_uri.query, notation: Config.instance.query_values_notation) || {}) 20 | normalized_uri.query = WebMock::Util::QueryMapper.values_to_query(sorted_query_values, notation: WebMock::Config.instance.query_values_notation) 21 | end 22 | normalized_uri = normalized_uri.normalize #normalize! is slower 23 | normalized_uri.query = normalized_uri.query.gsub("+", "%2B") if normalized_uri.query 24 | normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port 25 | hash[uri] = normalized_uri 26 | end 27 | 28 | def self.heuristic_parse(uri) 29 | ADDRESSABLE_URIS[uri].dup 30 | end 31 | 32 | def self.normalize_uri(uri) 33 | return uri if uri.is_a?(Regexp) 34 | uri = 'http://' + uri unless uri.match('^https?://') if uri.is_a?(String) 35 | NORMALIZED_URIS[uri].dup 36 | end 37 | 38 | def self.variations_of_uri_as_strings(uri_object, only_with_scheme: false) 39 | normalized_uri = normalize_uri(uri_object.dup).freeze 40 | uris = [ normalized_uri ] 41 | 42 | if normalized_uri.path == '/' 43 | uris = uris_with_trailing_slash_and_without(uris) 44 | end 45 | 46 | if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme] 47 | uris = uris_with_inferred_port_and_without(uris) 48 | end 49 | 50 | uris = uris_encoded_and_unencoded(uris) 51 | 52 | if normalized_uri.scheme == "http" && !only_with_scheme 53 | uris = uris_with_scheme_and_without(uris) 54 | end 55 | 56 | uris.map {|uri| uri.to_s.gsub(/^\/\//,'') }.uniq 57 | end 58 | 59 | def self.strip_default_port_from_uri_string(uri_string) 60 | case uri_string 61 | when %r{^http://} then uri_string.sub(%r{:80(/|$)}, '\1') 62 | when %r{^https://} then uri_string.sub(%r{:443(/|$)}, '\1') 63 | else uri_string 64 | end 65 | end 66 | 67 | def self.encode_unsafe_chars_in_userinfo(userinfo) 68 | Addressable::URI.encode_component(userinfo, WebMock::Util::URI::CharacterClasses::USERINFO) 69 | end 70 | 71 | def self.is_uri_localhost?(uri) 72 | uri.is_a?(Addressable::URI) && 73 | %w(localhost 127.0.0.1 0.0.0.0 [::1]).include?(uri.host) 74 | end 75 | 76 | private 77 | 78 | def self.sort_query_values(query_values) 79 | sorted_query_values = query_values.sort 80 | query_values.is_a?(Hash) ? Hash[*sorted_query_values.inject([]) { |values, pair| values + pair}] : sorted_query_values 81 | end 82 | 83 | def self.uris_with_inferred_port_and_without(uris) 84 | uris.map { |uri| 85 | [ uri, uri.omit(:port)] 86 | }.flatten 87 | end 88 | 89 | def self.uris_encoded_and_unencoded(uris) 90 | uris.map do |uri| 91 | [ 92 | uri.to_s.force_encoding(Encoding::ASCII_8BIT), 93 | Addressable::URI.unencode(uri, String).force_encoding(Encoding::ASCII_8BIT).freeze 94 | ] 95 | end.flatten 96 | end 97 | 98 | def self.uris_with_scheme_and_without(uris) 99 | uris.map { |uri| 100 | [ uri, uri.gsub(%r{^https?://},"").freeze ] 101 | }.flatten 102 | end 103 | 104 | def self.uris_with_trailing_slash_and_without(uris) 105 | uris.map { |uri| 106 | [ uri, uri.omit(:path).freeze ] 107 | }.flatten 108 | end 109 | 110 | end 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /lib/webmock/util/values_stringifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WebMock::Util::ValuesStringifier 4 | def self.stringify_values(value) 5 | case value 6 | when String, Numeric, TrueClass, FalseClass 7 | value.to_s 8 | when Hash 9 | Hash[ 10 | value.map do |k, v| 11 | [k, stringify_values(v)] 12 | end 13 | ] 14 | when Array 15 | value.map do |v| 16 | stringify_values(v) 17 | end 18 | else 19 | value 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/webmock/util/version_checker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This code was created based on https://github.com/myronmarston/vcr/blob/master/lib/vcr/util/version_checker.rb 4 | # Thanks to @myronmarston 5 | 6 | # Copyright (c) 2010-2012 Myron Marston 7 | 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | module WebMock 28 | class VersionChecker 29 | def initialize(library_name, library_version, min_patch_level, max_minor_version = nil, unsupported_versions = []) 30 | @library_name, @library_version = library_name, library_version 31 | @min_patch_level, @max_minor_version = min_patch_level, max_minor_version 32 | @unsupported_versions = unsupported_versions || [] 33 | 34 | @major, @minor, @patch = parse_version(library_version) 35 | @min_major, @min_minor, @min_patch = parse_version(min_patch_level) 36 | if max_minor_version 37 | @max_major, @max_minor = parse_version(max_minor_version) 38 | else 39 | @max_major, @max_minor = nil, nil 40 | end 41 | 42 | @comparison_result = compare_version 43 | end 44 | 45 | def check_version! 46 | warn_about_too_low if too_low? 47 | warn_about_too_high if too_high? 48 | warn_about_unsupported_version if unsupported_version? 49 | end 50 | 51 | private 52 | 53 | def too_low? 54 | @comparison_result == :too_low 55 | end 56 | 57 | def too_high? 58 | @comparison_result == :too_high 59 | end 60 | 61 | def unsupported_version? 62 | @unsupported_versions.include?(@library_version) 63 | end 64 | 65 | def warn_about_too_low 66 | warn_in_red "You are using #{@library_name} #{@library_version}. " + 67 | "WebMock supports version #{version_requirement}." 68 | end 69 | 70 | def warn_about_too_high 71 | warn_in_red "You are using #{@library_name} #{@library_version}. " + 72 | "WebMock is known to work with #{@library_name} #{version_requirement}. " + 73 | "It may not work with this version." 74 | end 75 | 76 | def warn_about_unsupported_version 77 | warn_in_red "You are using #{@library_name} #{@library_version}. " + 78 | "WebMock does not support this version. " + 79 | "WebMock supports versions #{version_requirement}." 80 | end 81 | 82 | def warn_in_red(text) 83 | Kernel.warn colorize(text, "\e[31m") 84 | end 85 | 86 | def compare_version 87 | case 88 | when @major < @min_major then :too_low 89 | when @major == @min_major && @minor < @min_minor then :too_low 90 | when @major == @min_major && @minor == @min_minor && @patch < @min_patch then :too_low 91 | when @max_major && @major > @max_major then :too_high 92 | when @max_major && @major == @max_major && @max_minor && @minor > @max_minor then :too_high 93 | 94 | else :ok 95 | end 96 | end 97 | 98 | def version_requirement 99 | req = ">= #{@min_patch_level}" 100 | req += ", < #{@max_major}.#{@max_minor + 1}" if @max_minor 101 | req += ", except versions #{@unsupported_versions.join(',')}" unless @unsupported_versions.empty? 102 | req 103 | end 104 | 105 | def parse_version(version) 106 | version.split('.').map { |v| v.to_i } 107 | end 108 | 109 | def colorize(text, color_code) 110 | "#{color_code}#{text}\e[0m" 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/webmock/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | VERSION = '3.25.1' unless defined?(::WebMock::VERSION) 5 | end 6 | -------------------------------------------------------------------------------- /lib/webmock/webmock.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module WebMock 4 | 5 | def self.included(clazz) 6 | WebMock::Deprecation.warning("include WebMock is deprecated. Please include WebMock::API instead") 7 | if clazz.instance_methods.map(&:to_s).include?('request') 8 | warn "WebMock#request was not included in #{clazz} to avoid name collision" 9 | else 10 | clazz.class_eval do 11 | def request(method, uri) 12 | WebMock::Deprecation.warning("WebMock#request is deprecated. Please use WebMock::API#a_request method instead") 13 | WebMock.a_request(method, uri) 14 | end 15 | end 16 | end 17 | end 18 | 19 | include WebMock::API 20 | extend WebMock::API 21 | 22 | class << self 23 | alias :request :a_request 24 | end 25 | 26 | def self.version 27 | VERSION 28 | end 29 | 30 | def self.disable!(options = {}) 31 | except = [options[:except]].flatten.compact 32 | HttpLibAdapterRegistry.instance.each_adapter do |name, adapter| 33 | adapter.enable! 34 | adapter.disable! unless except.include?(name) 35 | end 36 | end 37 | 38 | def self.enable!(options = {}) 39 | except = [options[:except]].flatten.compact 40 | HttpLibAdapterRegistry.instance.each_adapter do |name, adapter| 41 | adapter.disable! 42 | adapter.enable! unless except.include?(name) 43 | end 44 | end 45 | 46 | def self.allow_net_connect!(options = {}) 47 | Config.instance.allow_net_connect = true 48 | Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start] 49 | end 50 | 51 | def self.disable_net_connect!(options = {}) 52 | Config.instance.allow_net_connect = false 53 | Config.instance.allow_localhost = options[:allow_localhost] 54 | Config.instance.allow = options[:allow] 55 | Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start] 56 | end 57 | 58 | class << self 59 | alias :enable_net_connect! :allow_net_connect! 60 | alias :disallow_net_connect! :disable_net_connect! 61 | end 62 | 63 | def self.net_connect_allowed?(uri = nil) 64 | return !!Config.instance.allow_net_connect if uri.nil? 65 | 66 | if uri.is_a?(String) 67 | uri = WebMock::Util::URI.normalize_uri(uri) 68 | end 69 | 70 | !!Config.instance.allow_net_connect || 71 | ( Config.instance.allow_localhost && WebMock::Util::URI.is_uri_localhost?(uri) || 72 | Config.instance.allow && net_connect_explicit_allowed?(Config.instance.allow, uri) ) 73 | end 74 | 75 | def self.net_http_connect_on_start?(uri) 76 | allowed = Config.instance.net_http_connect_on_start || false 77 | 78 | if [true, false].include?(allowed) 79 | allowed 80 | else 81 | net_connect_explicit_allowed?(allowed, uri) 82 | end 83 | end 84 | 85 | def self.net_connect_explicit_allowed?(allowed, uri=nil) 86 | case allowed 87 | when Array 88 | allowed.any? { |allowed_item| net_connect_explicit_allowed?(allowed_item, uri) } 89 | when Regexp 90 | (uri.to_s =~ allowed) != nil || 91 | (uri.omit(:port).to_s =~ allowed) != nil && uri.port == uri.default_port 92 | when String 93 | allowed == uri.to_s || 94 | allowed == uri.host || 95 | allowed == "#{uri.host}:#{uri.port}" || 96 | allowed == "#{uri.scheme}://#{uri.host}:#{uri.port}" || 97 | allowed == "#{uri.scheme}://#{uri.host}" && uri.port == uri.default_port 98 | else 99 | if allowed.respond_to?(:call) 100 | allowed.call(uri) 101 | end 102 | end 103 | end 104 | 105 | def self.show_body_diff! 106 | Config.instance.show_body_diff = true 107 | end 108 | 109 | def self.hide_body_diff! 110 | Config.instance.show_body_diff = false 111 | end 112 | 113 | def self.show_body_diff? 114 | Config.instance.show_body_diff 115 | end 116 | 117 | def self.hide_stubbing_instructions! 118 | Config.instance.show_stubbing_instructions = false 119 | end 120 | 121 | def self.show_stubbing_instructions! 122 | Config.instance.show_stubbing_instructions = true 123 | end 124 | 125 | def self.show_stubbing_instructions? 126 | Config.instance.show_stubbing_instructions 127 | end 128 | 129 | def self.reset! 130 | WebMock::RequestRegistry.instance.reset! 131 | WebMock::StubRegistry.instance.reset! 132 | end 133 | 134 | def self.reset_webmock 135 | WebMock::Deprecation.warning("WebMock.reset_webmock is deprecated. Please use WebMock.reset! method instead") 136 | reset! 137 | end 138 | 139 | def self.reset_callbacks 140 | WebMock::CallbackRegistry.reset 141 | end 142 | 143 | def self.after_request(options={}, &block) 144 | WebMock::CallbackRegistry.add_callback(options, block) 145 | end 146 | 147 | def self.registered_request?(request_signature) 148 | WebMock::StubRegistry.instance.registered_request?(request_signature) 149 | end 150 | 151 | def self.print_executed_requests 152 | puts WebMock::RequestExecutionVerifier.executed_requests_message 153 | end 154 | 155 | def self.globally_stub_request(order = :before_local_stubs, &block) 156 | WebMock::StubRegistry.instance.register_global_stub(order, &block) 157 | end 158 | 159 | %w( 160 | allow_net_connect! 161 | disable_net_connect! 162 | net_connect_allowed? 163 | reset_webmock 164 | reset_callbacks 165 | after_request 166 | registered_request? 167 | ).each do |method| 168 | self.class_eval(%Q( 169 | def #{method}(*args, &block) 170 | WebMock::Deprecation.warning("WebMock##{method} instance method is deprecated. Please use WebMock.#{method} class method instead") 171 | WebMock.#{method}(*args, &block) 172 | end 173 | )) 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /minitest/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 4 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 5 | 6 | require File.expand_path(File.dirname(__FILE__) + '/../test/http_request') 7 | 8 | gem 'minitest' 9 | 10 | require 'minitest/autorun' 11 | require 'webmock/minitest' 12 | 13 | test_class = defined?(Minitest::Test) ? Minitest::Test : MiniTest::Unit::TestCase 14 | 15 | test_class.class_eval do 16 | 17 | def assert_raise(*exp, &block) 18 | assert_raises(*exp, &block) 19 | end 20 | 21 | def assert_raise_with_message(e, message, &block) 22 | e = assert_raises(e, &block) 23 | if message.is_a?(Regexp) 24 | assert_match(message, e.message) 25 | else 26 | assert_equal(message, e.message) 27 | end 28 | end 29 | 30 | def assert_fail(message, &block) 31 | assert_raise_with_message(Minitest::Assertion, message, &block) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /minitest/test_webmock.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/test_helper') 2 | require File.expand_path(File.dirname(__FILE__) + '/../test/shared_test') 3 | 4 | test_class = defined?(Minitest::Test) ? Minitest::Test : MiniTest::Unit::TestCase 5 | 6 | 7 | class MiniTestWebMock < test_class 8 | include SharedTest 9 | end 10 | -------------------------------------------------------------------------------- /minitest/webmock_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/test_helper') 2 | 3 | describe "Webmock" do 4 | include HttpRequestTestHelper 5 | 6 | before do 7 | @stub_http = stub_http_request(:any, "http://www.example.com") 8 | @stub_https = stub_http_request(:any, "https://www.example.com") 9 | end 10 | 11 | it "should update assertions count" do 12 | assert_equal 0, assertions 13 | http_request(:get, "http://www.example.com/") 14 | 15 | assert_requested(@stub_http) 16 | assert_equal 2, assertions 17 | 18 | assert_not_requested(:post, "http://www.example.com") 19 | assert_equal 4, assertions 20 | end 21 | 22 | it "should raise error on non stubbed request" do 23 | expect { http_request(:get, "http://www.example.net/") }.must_raise(WebMock::NetConnectNotAllowedError) 24 | end 25 | 26 | it "should verify that expected request occured" do 27 | http_request(:get, "http://www.example.com/") 28 | assert_requested(:get, "http://www.example.com", times: 1) 29 | assert_requested(:get, "http://www.example.com") 30 | end 31 | 32 | it "should verify that expected http stub occured" do 33 | http_request(:get, "http://www.example.com/") 34 | assert_requested(@stub_http, times: 1) 35 | assert_requested(@stub_http) 36 | end 37 | 38 | it "should verify that expected https stub occured" do 39 | http_request(:get, "https://www.example.com/") 40 | http_request(:get, "https://www.example.com/") 41 | assert_requested(@stub_https, times: 2) 42 | end 43 | 44 | it "should verify that expect request didn't occur" do 45 | expected_message = "The request GET http://www.example.com/ was expected to execute 1 time but it executed 0 times" 46 | expected_message += "\n\nThe following requests were made:\n\nNo requests were made.\n============================================================" 47 | assert_fail(expected_message) do 48 | assert_requested(:get, "http://www.example.com") 49 | end 50 | end 51 | 52 | it "should verify that expect stub didn't occur" do 53 | expected_message = "The request ANY http://www.example.com/ was expected to execute 1 time but it executed 0 times" 54 | expected_message += "\n\nThe following requests were made:\n\nNo requests were made.\n============================================================" 55 | assert_fail(expected_message) do 56 | assert_requested(@stub_http) 57 | end 58 | end 59 | end 60 | 61 | -------------------------------------------------------------------------------- /spec/acceptance/async_http_client/async_http_client_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module AsyncHttpClientSpecHelper 4 | def http_request(method, url, options = {}, &block) 5 | endpoint = Async::HTTP::Endpoint.parse(url) 6 | 7 | path = endpoint.path 8 | path = path + "?" + options[:query] if options[:query] 9 | 10 | headers = (options[:headers] || {}).each_with_object([]) do |(k, v), o| 11 | Array(v).each do |v| 12 | o.push [k, v] 13 | end 14 | end 15 | headers.push( 16 | ['authorization', WebMock::Util::Headers.basic_auth_header(options[:basic_auth])] 17 | ) if options[:basic_auth] 18 | 19 | body = options[:body] 20 | 21 | result = Async do 22 | begin 23 | Async::HTTP::Client.open(endpoint) do |client| 24 | response = client.send( 25 | method, 26 | path, 27 | headers, 28 | body 29 | ) 30 | 31 | OpenStruct.new( 32 | build_hash_response(response) 33 | ) 34 | end 35 | rescue Exception => e 36 | e 37 | end 38 | end.wait 39 | 40 | result.is_a?(Exception) ? raise(result) : result 41 | end 42 | 43 | def client_timeout_exception_class 44 | Async::TimeoutError 45 | end 46 | 47 | def connection_refused_exception_class 48 | Errno::ECONNREFUSED 49 | end 50 | 51 | def connection_error_class 52 | HTTP::ConnectionError 53 | end 54 | 55 | def http_library 56 | :async_http_client 57 | end 58 | 59 | private 60 | 61 | def build_hash_response(response) 62 | { 63 | 64 | status: response.status.to_s, 65 | message: Protocol::HTTP1::Reason::DESCRIPTIONS[response.status], 66 | headers: build_response_headers(response), 67 | body: response.read 68 | } 69 | end 70 | 71 | def build_response_headers(response) 72 | response.headers.each.each_with_object({}) do |(k, v), o| 73 | o[k] ||= [] 74 | o[k] << v 75 | end.tap do |o| 76 | o.each do |k, v| 77 | o[k] = v.join(', ') 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/acceptance/curb/curb_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module CurbSpecHelper 4 | def http_request(method, uri, options = {}, &block) 5 | uri = Addressable::URI.heuristic_parse(uri) 6 | body = options[:body] 7 | 8 | curl = curb_http_request(uri, method, body, options) 9 | 10 | status, response_headers = 11 | WebMock::HttpLibAdapters::CurbAdapter.parse_header_string(curl.header_str) 12 | 13 | # Deal with the fact that the HTTP spec allows multi-values headers 14 | # to either be a single entry with a comma-separated listed of 15 | # values, or multiple separate entries 16 | response_headers.keys.each do |k| 17 | v = response_headers[k] 18 | if v.is_a?(Array) 19 | response_headers[k] = v.join(', ') 20 | end 21 | end 22 | 23 | OpenStruct.new( 24 | body: curl.body_str, 25 | headers: WebMock::Util::Headers.normalize_headers(response_headers), 26 | status: curl.response_code.to_s, 27 | message: status 28 | ) 29 | end 30 | 31 | def setup_request(uri, curl, options={}) 32 | curl ||= Curl::Easy.new 33 | curl.url = uri.to_s 34 | if options[:basic_auth] 35 | curl.http_auth_types = :basic 36 | curl.username = options[:basic_auth][0] 37 | curl.password = options[:basic_auth][1] 38 | end 39 | curl.timeout = 30 40 | curl.connect_timeout = 5 41 | 42 | if headers = options[:headers] 43 | headers.each {|k,v| curl.headers[k] = v } 44 | end 45 | 46 | curl 47 | end 48 | 49 | def client_timeout_exception_class 50 | Curl::Err::TimeoutError 51 | end 52 | 53 | def connection_refused_exception_class 54 | Curl::Err::ConnectionFailedError 55 | end 56 | 57 | def connection_error_class 58 | end 59 | 60 | def http_library 61 | :curb 62 | end 63 | 64 | module DynamicHttp 65 | def curb_http_request(uri, method, body, options) 66 | curl = setup_request(uri, nil, options) 67 | 68 | case method 69 | when :post 70 | curl.post_body = body 71 | when :put 72 | curl.put_data = body 73 | end 74 | 75 | curl.http(method.to_s.upcase) 76 | curl 77 | end 78 | end 79 | 80 | module NamedHttp 81 | def curb_http_request(uri, method, body, options) 82 | curl = setup_request(uri, nil, options) 83 | 84 | case method 85 | when :put, :post 86 | curl.send( "http_#{method}", body ) 87 | else 88 | curl.send( "http_#{method}" ) 89 | end 90 | curl 91 | end 92 | end 93 | 94 | module Perform 95 | def curb_http_request(uri, method, body, options) 96 | curl = setup_request(uri, nil, options) 97 | 98 | case method 99 | when :post 100 | curl.post_body = body 101 | when :put 102 | curl.put_data = body 103 | when :head 104 | curl.head = true 105 | when :delete 106 | curl.delete = true 107 | end 108 | 109 | curl.perform 110 | curl 111 | end 112 | end 113 | 114 | module ClassNamedHttp 115 | def curb_http_request(uri, method, body, options) 116 | args = ["http_#{method}", uri] 117 | args << body if method == :post || method == :put 118 | 119 | c = Curl::Easy.send(*args) do |curl| 120 | setup_request(uri, curl, options) 121 | end 122 | 123 | c 124 | end 125 | end 126 | 127 | module ClassPerform 128 | def curb_http_request(uri, method, body, options) 129 | args = ["http_#{method}", uri] 130 | args << body if method == :post || method == :put 131 | 132 | c = Curl::Easy.send(*args) do |curl| 133 | setup_request(uri, curl, options) 134 | 135 | case method 136 | when :post 137 | curl.post_body = body 138 | when :put 139 | curl.put_data = body 140 | when :head 141 | curl.head = true 142 | when :delete 143 | curl.delete = true 144 | end 145 | end 146 | 147 | c 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /spec/acceptance/em_http_request/em_http_request_spec_helper.rb: -------------------------------------------------------------------------------- 1 | module EMHttpRequestSpecHelper 2 | 3 | def failed 4 | EventMachine.stop 5 | fail 6 | end 7 | 8 | def http_request(method, uri, options = {}, &block) 9 | @http = nil 10 | head = options[:headers] || {} 11 | if options[:basic_auth] 12 | head.merge!('authorization' => options[:basic_auth]) 13 | end 14 | response = nil 15 | error = nil 16 | error_set = false 17 | uri = Addressable::URI.heuristic_parse(uri) 18 | EventMachine.run { 19 | request = EventMachine::HttpRequest.new("#{uri.normalize.to_s}", ssl: {verify_peer: true}) 20 | http = request.send(method, { 21 | timeout: 30, 22 | body: options[:body], 23 | file: options[:file], 24 | query: options[:query], 25 | head: head, 26 | compressed: false 27 | }, &block) 28 | http.errback { 29 | error_set = true 30 | error = if http.respond_to?(:errors) 31 | http.errors 32 | else 33 | http.error 34 | end 35 | failed 36 | } 37 | http.callback { 38 | response = OpenStruct.new({ 39 | body: http.response, 40 | headers: WebMock::Util::Headers.normalize_headers(extract_response_headers(http)), 41 | message: http.response_header.http_reason, 42 | status: http.response_header.status.to_s 43 | }) 44 | EventMachine.stop 45 | } 46 | @http = http 47 | } 48 | raise error.to_s if error_set 49 | response 50 | end 51 | 52 | def client_timeout_exception_class 53 | RuntimeError # 'Errno::ETIMEDOUT' 54 | end 55 | 56 | def connection_refused_exception_class 57 | RuntimeError 58 | end 59 | 60 | def connection_error_class 61 | end 62 | 63 | def http_library 64 | :em_http_request 65 | end 66 | 67 | private 68 | 69 | def extract_response_headers(http) 70 | headers = {} 71 | if http.response_header 72 | http.response_header.each do |k,v| 73 | v = v.join(", ") if v.is_a?(Array) 74 | headers[k] = v 75 | end 76 | end 77 | headers 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /spec/acceptance/excon/excon_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'acceptance/webmock_shared' 3 | require 'acceptance/excon/excon_spec_helper' 4 | 5 | describe "Excon" do 6 | include ExconSpecHelper 7 | include_context "with WebMock", :no_url_auth 8 | 9 | it 'should allow Excon requests to use query hash paramters' do 10 | stub_request(:get, "http://example.com/resource/?a=1&b=2").to_return(body: "abc") 11 | expect(Excon.new('http://example.com').get(path: "resource/", query: {a: 1, b: 2}).body).to eq("abc") 12 | end 13 | 14 | it 'should support Excon :expects options' do 15 | stub_request(:get, "http://example.com/").to_return(body: 'a') 16 | expect { Excon.new('http://example.com').get(expects: 204) }.to raise_error(Excon::Errors::OK) 17 | end 18 | 19 | context "with response_block" do 20 | it "should support excon response_block for real requests", net_connect: true do 21 | a = [] 22 | WebMock.allow_net_connect! 23 | r = Excon.new('https://httpstat.us/200', headers: { "Accept" => "*" }). 24 | get(response_block: lambda {|e, remaining, total| a << e}, chunk_size: 1) 25 | expect(a).to eq(["2", "0", "0", " ", "O", "K"]) 26 | expect(r.body).to eq("") 27 | end 28 | 29 | it "should support excon response_block" do 30 | a = [] 31 | stub_request(:get, "http://example.com/").to_return(body: "abc") 32 | r = Excon.new('http://example.com').get(response_block: lambda {|e, remaining, total| a << e}, chunk_size: 1) 33 | expect(a).to eq(['a', 'b', 'c']) 34 | expect(r.body).to eq("") 35 | end 36 | 37 | it "should invoke callbacks with response body even if a real request is made", net_connect: true do 38 | a = [] 39 | WebMock.allow_net_connect! 40 | response = nil 41 | WebMock.after_request { |_, res| 42 | response = res 43 | } 44 | r = Excon.new('https://httpstat.us/200', headers: { "Accept" => "*" }). 45 | get(response_block: lambda {|e, remaining, total| a << e}, chunk_size: 1) 46 | expect(response.body).to eq("200 OK") 47 | expect(a).to eq(["2", "0", "0", " ", "O", "K"]) 48 | expect(r.body).to eq("") 49 | end 50 | end 51 | 52 | let(:file) { File.new(__FILE__) } 53 | let(:file_contents) { File.read(__FILE__) } 54 | 55 | it 'handles file uploads correctly' do 56 | stub_request(:put, "http://example.com/upload").with(body: file_contents) 57 | 58 | yielded_request_body = nil 59 | WebMock.after_request do |req, res| 60 | yielded_request_body = req.body 61 | end 62 | 63 | Excon.new("http://example.com").put(path: "upload", body: file) 64 | 65 | expect(yielded_request_body).to eq(file_contents) 66 | end 67 | 68 | describe '.request_params_from' do 69 | 70 | it 'rejects invalid request keys' do 71 | request_params = WebMock::HttpLibAdapters::ExconAdapter.request_params_from(body: :keep, fake: :reject) 72 | expect(request_params).to eq(body: :keep) 73 | end 74 | 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /spec/acceptance/excon/excon_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module ExconSpecHelper 4 | 5 | def http_request(method, uri, options = {}, &block) 6 | Excon.defaults[:ssl_verify_peer] = false 7 | Excon.defaults[:ciphers] = 'DEFAULT' 8 | uri = Addressable::URI.heuristic_parse(uri) 9 | uri = uri.to_s.gsub(' ', '%20') 10 | 11 | excon_options = {} 12 | 13 | if basic_auth = options.delete(:basic_auth) 14 | excon_options = {user: basic_auth[0], password: basic_auth[1]} 15 | end 16 | 17 | if Gem::Version.new(Excon::VERSION) < Gem::Version.new("0.29.0") 18 | options = options.merge(method: method, nonblock: false) # Dup and merge 19 | response = Excon.new(uri, excon_options).request(options, &block) 20 | else 21 | options = options.merge(method: method) # Dup and merge 22 | response = Excon.new(uri, excon_options.merge(nonblock: false)).request(options, &block) 23 | end 24 | 25 | headers = WebMock::Util::Headers.normalize_headers(response.headers) 26 | headers = headers.inject({}) do |res, (name, value)| 27 | res[name] = value.is_a?(Array) ? value.flatten.join(', ') : value 28 | res 29 | end 30 | 31 | Excon.set_raise_on_warnings!(true) 32 | 33 | OpenStruct.new \ 34 | body: response.body, 35 | headers: headers, 36 | status: response.status.to_s, 37 | message: response.reason_phrase 38 | end 39 | 40 | def client_timeout_exception_class 41 | Excon::Errors::Timeout 42 | end 43 | 44 | def connection_refused_exception_class 45 | Excon::Errors::SocketError 46 | end 47 | 48 | def connection_error_class 49 | end 50 | 51 | def http_library 52 | :excon 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /spec/acceptance/http_rb/http_rb_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "acceptance/webmock_shared" 3 | require "acceptance/http_rb/http_rb_spec_helper" 4 | 5 | describe "HTTP.rb" do 6 | include HttpRbSpecHelper 7 | 8 | include_examples "with WebMock", :no_status_message 9 | 10 | context "streaming body" do 11 | let(:response) { HTTP.get "http://example.com" } 12 | before { stub_simple_request "example.com", 302, {}, "abc" } 13 | 14 | it "works as if it was streamed from socket" do 15 | expect(response.body.readpartial 1).to eq "a" 16 | end 17 | 18 | it "fails if body was already streamed" do 19 | response.body.to_s 20 | expect { response.body.readpartial 1 }.to raise_error(HTTP::StateError) 21 | end 22 | end 23 | 24 | context "without following redirects" do 25 | let(:response) { http_request(:get, "http://example.com") } 26 | let(:headers) { response.headers } 27 | 28 | it "stops on first request" do 29 | stub_simple_request("example.com", 302, "Location" => "http://www.example.com") 30 | stub_simple_request("www.example.com") 31 | 32 | expect(headers).to include "Host" => "example.com" 33 | end 34 | end 35 | 36 | context "following redirects" do 37 | let(:options) { { follow: true } } 38 | let(:response) { http_request(:get, "http://example.com", options) } 39 | let(:headers) { response.headers } 40 | 41 | it "returns response of destination" do 42 | stub_simple_request("example.com", 302, "Location" => "http://www.example.com") 43 | stub_simple_request("www.example.com") 44 | 45 | expect(headers).to include "Host" => "www.example.com" 46 | end 47 | end 48 | 49 | context "restored request uri on replayed response object" do 50 | it "keeps non-default port" do 51 | stub_request :get, "example.com:1234/foo" 52 | response = HTTP.get "http://example.com:1234/foo" 53 | 54 | expect(response.uri.to_s).to eq "http://example.com:1234/foo" 55 | end 56 | 57 | it "does not injects default port" do 58 | stub_request :get, "example.com/foo" 59 | response = HTTP.get "http://example.com/foo" 60 | 61 | expect(response.uri.to_s).to eq "http://example.com/foo" 62 | end 63 | 64 | it "strips out default port even if it was explicitly given" do 65 | stub_request :get, "example.com/foo" 66 | response = HTTP.get "http://example.com:80/foo" 67 | 68 | expect(response.uri.to_s).to eq "http://example.com/foo" 69 | end 70 | end 71 | 72 | context "streamer" do 73 | it "can be read to a provided buffer" do 74 | stub_request(:get, "example.com/foo") 75 | .to_return(status: 200, body: "Hello world! ") 76 | response = HTTP.get "http://example.com/foo" 77 | 78 | buffer = "" 79 | response.body.readpartial(1024, buffer) 80 | 81 | expect(buffer).to eq "Hello world! " 82 | end 83 | 84 | it "can be closed" do 85 | stub_request :get, "example.com/foo" 86 | response = HTTP.get "http://example.com/foo" 87 | 88 | response.connection.close 89 | end 90 | 91 | it "reports request finish" do 92 | stub_request(:get, "example.com/foo") 93 | .to_return(body: 'XX') 94 | response = HTTP.get "http://example.com/foo" 95 | 96 | expect(response.connection.finished_request?).to be(false) 97 | 98 | response.body.readpartial(1) 99 | expect(response.connection.finished_request?).to be(false) 100 | 101 | response.body.readpartial 102 | expect(response.connection.finished_request?).to be(true) 103 | end 104 | end 105 | 106 | it "should preserve request body encoding when matching requests" do 107 | stub_request(:post, "www.example.com").with(body: (lambda {|body| 108 | body.encoding == Encoding::UTF_8 109 | })) 110 | http_request(:post, "http://www.example.com/", body: "abc") 111 | end 112 | 113 | describe "when making real requests", net_connect: true do 114 | before do 115 | WebMock.allow_net_connect! 116 | end 117 | 118 | it "should allow streaming the response body" do 119 | response = HTTP.get("http://localhost:#{WebMockServer.instance.port}") 120 | 121 | read_body = "" 122 | response.body.each do |chunk| 123 | read_body << chunk 124 | end 125 | 126 | expect(read_body).to eql("hello world") 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/acceptance/http_rb/http_rb_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | module HttpRbSpecHelper 4 | def http_request(method, uri, options = {}) 5 | chain = HTTP 6 | 7 | if basic_auth = options.delete(:basic_auth) 8 | chain = chain.basic_auth(user: basic_auth[0], pass: basic_auth[1]) 9 | end 10 | 11 | ssl_ctx = OpenSSL::SSL::SSLContext.new 12 | ssl_ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE 13 | 14 | response = chain.request(method, normalize_uri(uri), options.merge(ssl_context: ssl_ctx)) 15 | 16 | OpenStruct.new({ 17 | body: response.body.to_s, 18 | headers: normalize_headers(response.headers.to_h), 19 | status: response.code.to_s, 20 | message: response.reason 21 | }) 22 | end 23 | 24 | def client_timeout_exception_class 25 | return Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0" 26 | HTTP::TimeoutError 27 | end 28 | 29 | def connection_refused_exception_class 30 | return Errno::ECONNREFUSED if HTTP::VERSION < "1.0.0" 31 | HTTP::ConnectionError 32 | end 33 | 34 | def connection_error_class 35 | end 36 | 37 | def http_library 38 | :http_rb 39 | end 40 | 41 | def normalize_uri(uri) 42 | Addressable::URI.heuristic_parse(uri).normalize.to_s 43 | end 44 | 45 | def normalize_headers(headers) 46 | headers = Hash[headers.map { |k, v| [k, Array(v).join(", ")] }] 47 | WebMock::Util::Headers.normalize_headers headers 48 | end 49 | 50 | def stub_simple_request(host, status = 200, headers = {}, body = nil) 51 | stub_request(:any, host).to_return({ 52 | status: status, 53 | headers: headers.merge({ "Host" => host }), 54 | body: body 55 | }) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/acceptance/httpclient/httpclient_spec_helper.rb: -------------------------------------------------------------------------------- 1 | module HTTPClientSpecHelper 2 | class << self 3 | attr_accessor :async_mode 4 | end 5 | 6 | def http_request(method, uri, options = {}, &block) 7 | uri = Addressable::URI.heuristic_parse(uri) 8 | c = options.fetch(:client) { HTTPClient.new } 9 | c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE 10 | c.reset_all 11 | if options[:basic_auth] 12 | c.force_basic_auth = true 13 | c.set_basic_auth(nil, options[:basic_auth][0], options[:basic_auth][1]) 14 | end 15 | params = [method, uri.normalize.to_s, 16 | WebMock::Util::QueryMapper.query_to_values(uri.query, notation: WebMock::Config.instance.query_values_notation), options[:body], options[:headers] || {}] 17 | if HTTPClientSpecHelper.async_mode 18 | connection = c.request_async(*params) 19 | connection.join 20 | response = connection.pop 21 | else 22 | response = c.request(*params, &block) 23 | end 24 | headers = merge_headers(response) 25 | OpenStruct.new({ 26 | body: HTTPClientSpecHelper.async_mode ? response.content.read : response.content, 27 | headers: headers, 28 | status: response.code.to_s, 29 | message: response.reason 30 | }) 31 | end 32 | 33 | def client_timeout_exception_class 34 | HTTPClient::TimeoutError 35 | end 36 | 37 | def connection_refused_exception_class 38 | Errno::ECONNREFUSED 39 | end 40 | 41 | def connection_error_class 42 | end 43 | 44 | def http_library 45 | :httpclient 46 | end 47 | 48 | private 49 | 50 | def merge_headers(response) 51 | response.header.all.inject({}) do |headers, header| 52 | if !headers.has_key?(header[0]) 53 | headers[header[0]] = header[1] 54 | else 55 | headers[header[0]] = [headers[header[0]], header[1]].join(', ') 56 | end 57 | headers 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/acceptance/manticore/manticore_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'acceptance/webmock_shared' 3 | 4 | if RUBY_PLATFORM =~ /java/ 5 | require 'acceptance/manticore/manticore_spec_helper' 6 | 7 | describe "Manticore" do 8 | include ManticoreSpecHelper 9 | 10 | include_context "with WebMock", :no_status_message 11 | 12 | context "calling http methods on Manticore directly using Manticore's facade" do 13 | it "handles GET" do 14 | stub_request(:get, "http://example-foo.com").to_return(status: 301) 15 | response = Manticore.get("http://example-foo.com") 16 | expect(response.code).to eq(301) 17 | end 18 | 19 | it "handles POST" do 20 | stub_request(:post, "http://example-foo.com").to_return(status: 201) 21 | response = Manticore.post("http://example-foo.com", {hello: "world"}) 22 | expect(response.code).to eq(201) 23 | end 24 | 25 | it "handles PUT" do 26 | stub_request(:put, "http://example-foo.com").to_return(status: 409) 27 | response = Manticore.put("http://example-foo.com", {hello: "world"}) 28 | expect(response.code).to eq(409) 29 | end 30 | 31 | it "handles PATCH" do 32 | stub_request(:patch, "http://example-foo.com").to_return(status: 409) 33 | response = Manticore.patch("http://example-foo.com", {hello: "world"}) 34 | expect(response.code).to eq(409) 35 | end 36 | 37 | it "handles DELETE" do 38 | stub_request(:delete, "http://example-foo.com").to_return(status: 204) 39 | response = Manticore.delete("http://example-foo.com", {id: 1}) 40 | expect(response.code).to eq(204) 41 | end 42 | 43 | it "handles OPTIONS" do 44 | stub_request(:options, "http://example-foo.com").to_return(status: 200) 45 | response = Manticore.options("http://example-foo.com") 46 | expect(response.code).to eq(200) 47 | end 48 | 49 | it "handles HEAD" do 50 | stub_request(:head, "http://example-foo.com").to_return(status: 204) 51 | response = Manticore.head("http://example-foo.com") 52 | expect(response.code).to eq(204) 53 | end 54 | 55 | context "when a custom failure handler is defined" do 56 | let(:failure_handler) { proc {} } 57 | 58 | before do 59 | allow(failure_handler).to receive(:call).with(kind_of(Manticore::Timeout)) do |ex| 60 | raise ex 61 | end 62 | end 63 | 64 | it "handles timeouts by invoking the failure handler" do 65 | stub_request(:get, "http://example-foo.com").to_timeout 66 | request = Manticore.get("http://example-foo.com").tap do |req| 67 | req.on_failure(&failure_handler) 68 | end 69 | expect { request.call }.to raise_error(Manticore::Timeout) 70 | expect(failure_handler).to have_received(:call) 71 | end 72 | end 73 | 74 | context 'when used in a streaming mode' do 75 | let(:webmock_server_url) {"http://#{WebMockServer.instance.host_with_port}/"} 76 | let(:result_chunks) { [] } 77 | 78 | def manticore_streaming_get 79 | Manticore.get(webmock_server_url).tap do |req| 80 | req.on_success do |response| 81 | response.body do |chunk| 82 | result_chunks << chunk 83 | end 84 | end 85 | end 86 | end 87 | 88 | context 'when connections are allowed' do 89 | it 'works' do 90 | WebMock.allow_net_connect! 91 | expect { manticore_streaming_get.call }.to_not raise_error 92 | expect(result_chunks).to_not be_empty 93 | end 94 | end 95 | 96 | context 'when stubbed' do 97 | it 'works' do 98 | stub_body = 'hello!' 99 | stub_request(:get, webmock_server_url).to_return(body: stub_body) 100 | expect { manticore_streaming_get.call }.to_not raise_error 101 | expect(result_chunks).to eq [stub_body] 102 | end 103 | end 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /spec/acceptance/manticore/manticore_spec_helper.rb: -------------------------------------------------------------------------------- 1 | module ManticoreSpecHelper 2 | def http_request(method, uri, options = {}) 3 | client = Manticore::Client.new 4 | 5 | if basic_auth = options[:basic_auth] 6 | options = options.merge(auth: {user: basic_auth[0], pass: basic_auth[1]}) 7 | end 8 | 9 | response = client.http(method, uri, options) 10 | OpenStruct.new({ 11 | body: response.body || '', 12 | headers: WebMock::Util::Headers.normalize_headers(join_array_values(response.headers)), 13 | status: response.code.to_s 14 | }) 15 | end 16 | 17 | def join_array_values(hash) 18 | hash.reduce({}) do |h, (k,v)| 19 | v = v.join(', ') if v.is_a?(Array) 20 | h.merge(k => v) 21 | end 22 | end 23 | 24 | def client_timeout_exception_class 25 | Manticore::ConnectTimeout 26 | end 27 | 28 | def connection_refused_exception_class 29 | Manticore::SocketException 30 | end 31 | 32 | def connection_error_class 33 | end 34 | 35 | def http_library 36 | :manticore 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/acceptance/net_http/net_http_spec_helper.rb: -------------------------------------------------------------------------------- 1 | module NetHTTPSpecHelper 2 | def http_request(method, uri, options = {}, &block) 3 | begin 4 | uri = URI.parse(uri) 5 | rescue 6 | uri = Addressable::URI.heuristic_parse(uri) 7 | end 8 | response = nil 9 | clazz = Net::HTTP.const_get("#{method.to_s.capitalize}") 10 | req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", nil) 11 | options[:headers].each do |k,v| 12 | if v.is_a?(Array) 13 | v.each_with_index do |e,i| 14 | i == 0 ? (req[k] = e) : req.add_field(k, e) 15 | end 16 | else 17 | req[k] = v 18 | end 19 | end if options[:headers] 20 | 21 | if options[:basic_auth] 22 | req.basic_auth(options[:basic_auth][0], options[:basic_auth][1]) 23 | end 24 | 25 | http = Net::HTTP.new(uri.host, uri.port) 26 | if uri.scheme == "https" 27 | http.use_ssl = true 28 | #1.9.1 has a bug with ssl_timeout 29 | http.ssl_timeout = 20 unless RUBY_PLATFORM =~ /java/ 30 | http.open_timeout = 60 31 | http.read_timeout = 60 32 | end 33 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 34 | response = http.start {|open_http| 35 | open_http.request(req, options[:body], &block) 36 | } 37 | headers = {} 38 | response.each_header {|name, value| headers[name] = value} 39 | OpenStruct.new({ 40 | body: response.body, 41 | headers: WebMock::Util::Headers.normalize_headers(headers), 42 | status: response.code, 43 | message: response.message 44 | }) 45 | end 46 | 47 | def client_timeout_exception_class 48 | if defined?(Net::OpenTimeout) 49 | Net::OpenTimeout 50 | elsif defined?(Net::HTTP::OpenTimeout) 51 | Net::HTTP::OpenTimeout 52 | else 53 | Timeout::Error 54 | end 55 | end 56 | 57 | def connection_refused_exception_class 58 | Errno::ECONNREFUSED 59 | end 60 | 61 | def connection_error_class 62 | end 63 | 64 | def http_library 65 | :net_http 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/acceptance/net_http/real_net_http_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rspec' 3 | require 'net/http' 4 | require 'net/https' 5 | require 'stringio' 6 | require 'acceptance/net_http/net_http_shared' 7 | require 'support/webmock_server' 8 | 9 | describe "Real Net:HTTP without webmock", without_webmock: true do 10 | before(:all) do 11 | raise "WebMock has no access here!!!" if defined?(WebMock::NetHTTPUtility) 12 | WebMockServer.instance.start 13 | end 14 | 15 | after(:all) do 16 | WebMockServer.instance.stop 17 | end 18 | 19 | it_should_behave_like "Net::HTTP" 20 | end 21 | -------------------------------------------------------------------------------- /spec/acceptance/patron/patron_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'acceptance/webmock_shared' 3 | 4 | unless RUBY_PLATFORM =~ /java/ 5 | require 'acceptance/patron/patron_spec_helper' 6 | require 'tmpdir' 7 | require 'fileutils' 8 | 9 | describe "Patron" do 10 | include PatronSpecHelper 11 | 12 | include_examples "with WebMock" 13 | 14 | describe "when custom functionality is used" do 15 | before(:each) do 16 | @sess = Patron::Session.new 17 | @sess.base_url = "http://www.example.com" 18 | end 19 | 20 | it "should allow stubbing PATCH request with body" do 21 | stub_request(:patch, "http://www.example.com/") 22 | .with(body: "abc") 23 | 24 | @sess.patch('/', "abc") 25 | end 26 | 27 | describe "file requests" do 28 | 29 | before(:each) do 30 | @dir_path = Dir.mktmpdir('webmock-') 31 | @file_path = File.join(@dir_path, "webmock_temp_test_file") 32 | FileUtils.rm_rf(@file_path) if File.exist?(@file_path) 33 | end 34 | 35 | after(:each) do 36 | FileUtils.rm_rf(@dir_path) if File.exist?(@dir_path) 37 | end 38 | 39 | it "should work with get_file" do 40 | stub_request(:get, "www.example.com").to_return(body: "abc") 41 | @sess.get_file("/", @file_path) 42 | expect(File.read(@file_path)).to eq("abc") 43 | end 44 | 45 | it "should raise same error as Patron if file is not readable for get request" do 46 | stub_request(:get, "www.example.com") 47 | File.open("/tmp/read_only_file", "w") do |tmpfile| 48 | tmpfile.chmod(0400) 49 | end 50 | begin 51 | expect { 52 | @sess.get_file("/", "/tmp/read_only_file") 53 | }.to raise_error(ArgumentError, "Unable to open specified file.") 54 | ensure 55 | File.unlink("/tmp/read_only_file") 56 | end 57 | end 58 | 59 | it "should work with put_file" do 60 | File.open(@file_path, "w") {|f| f.write "abc"} 61 | stub_request(:put, "www.example.com").with(body: "abc") 62 | @sess.put_file("/", @file_path) 63 | end 64 | 65 | it "should work with post_file" do 66 | File.open(@file_path, "w") {|f| f.write "abc"} 67 | stub_request(:post, "www.example.com").with(body: "abc") 68 | @sess.post_file("/", @file_path) 69 | end 70 | 71 | it "should raise same error as Patron if file is not readable for post request" do 72 | stub_request(:post, "www.example.com").with(body: "abc") 73 | expect { 74 | @sess.post_file("/", "/path/to/non/existing/file") 75 | }.to raise_error(ArgumentError, "Unable to open specified file.") 76 | end 77 | 78 | end 79 | 80 | describe "handling errors same way as patron" do 81 | it "should raise error if put request has neither upload_data nor file_name" do 82 | stub_request(:post, "www.example.com") 83 | expect { 84 | @sess.post("/", nil) 85 | }.to raise_error(ArgumentError, "Must provide either data or a filename when doing a PUT or POST") 86 | end 87 | end 88 | 89 | it "should work with WebDAV copy request" do 90 | stub_request(:copy, "www.example.com/abc").with(headers: {'Destination' => "/def"}) 91 | @sess.copy("/abc", "/def") 92 | end 93 | 94 | describe "handling encoding same way as patron" do 95 | around(:each) do |example| 96 | @encoding = Encoding.default_internal 97 | Encoding.default_internal = "UTF-8" 98 | example.run 99 | Encoding.default_internal = @encoding 100 | end 101 | 102 | it "should not encode body with default encoding" do 103 | stub_request(:get, "www.example.com"). 104 | to_return(body: "Øl") 105 | 106 | expect(@sess.get("").body.encoding).to eq(Encoding::ASCII_8BIT) 107 | expect(@sess.get("").inspectable_body.encoding).to eq(Encoding::UTF_8) 108 | end 109 | 110 | it "should not encode body to default internal" do 111 | stub_request(:get, "www.example.com"). 112 | to_return(headers: {'Content-Type' => 'text/html; charset=iso-8859-1'}, 113 | body: "Øl".encode("iso-8859-1")) 114 | 115 | expect(@sess.get("").body.encoding).to eq(Encoding::ASCII_8BIT) 116 | expect(@sess.get("").decoded_body.encoding).to eq(Encoding.default_internal) 117 | end 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /spec/acceptance/patron/patron_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module PatronSpecHelper 4 | def http_request(method, uri, options = {}, &block) 5 | method = method.to_sym 6 | uri = Addressable::URI.heuristic_parse(uri) 7 | sess = Patron::Session.new 8 | sess.base_url = "#{uri.omit(:path, :query).normalize.to_s}".gsub(/\/$/,"") 9 | 10 | if options[:basic_auth] 11 | sess.username = options[:basic_auth][0] 12 | sess.password = options[:basic_auth][1] 13 | end 14 | 15 | sess.connect_timeout = 30 16 | sess.timeout = 30 17 | sess.max_redirects = 0 18 | uri = "#{uri.path}#{uri.query ? '?' : ''}#{uri.query}" 19 | uri = uri.gsub(' ','%20') 20 | response = sess.request(method, uri, options[:headers] || {}, { 21 | data: options[:body] 22 | }) 23 | headers = {} 24 | if response.headers 25 | response.headers.each do |k,v| 26 | v = v.join(", ") if v.is_a?(Array) 27 | headers[k] = v 28 | end 29 | end 30 | 31 | status_line_pattern = %r(\AHTTP/(\d+(\.\d+)?)\s+(\d\d\d)\s*([^\r\n]+)?) 32 | message = response.status_line.match(status_line_pattern)[4] || "" 33 | 34 | OpenStruct.new({ 35 | body: response.body, 36 | headers: WebMock::Util::Headers.normalize_headers(headers), 37 | status: response.status.to_s, 38 | message: message 39 | }) 40 | end 41 | 42 | def client_timeout_exception_class 43 | Patron::TimeoutError 44 | end 45 | 46 | def connection_refused_exception_class 47 | Patron::ConnectionFailed 48 | end 49 | 50 | def connection_error_class 51 | end 52 | 53 | def http_library 54 | :patron 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /spec/acceptance/shared/callbacks.rb: -------------------------------------------------------------------------------- 1 | shared_context "callbacks" do |*adapter_info| 2 | describe "when after_request callback is declared" do 3 | before(:each) do 4 | @called = nil 5 | WebMock.reset_callbacks 6 | stub_request(:get, "http://www.example.com") 7 | end 8 | 9 | it "should not invoke callback unless request is made" do 10 | WebMock.after_request { 11 | @called = true 12 | } 13 | expect(@called).to eq(nil) 14 | end 15 | 16 | it "should invoke a callback after request is made" do 17 | WebMock.after_request { 18 | @called = true 19 | } 20 | http_request(:get, "http://www.example.com/") 21 | expect(@called).to eq(true) 22 | end 23 | 24 | it "should not invoke a callback if this http library should be ignored" do 25 | WebMock.after_request(except: [http_library()]) { 26 | @called = true 27 | } 28 | http_request(:get, "http://www.example.com/") 29 | expect(@called).to eq(nil) 30 | end 31 | 32 | it "should invoke a callback even if other http libraries should be ignored" do 33 | WebMock.after_request(except: [:other_lib]) { 34 | @called = true 35 | } 36 | http_request(:get, "http://www.example.com/") 37 | expect(@called).to eq(true) 38 | end 39 | 40 | it "should pass request signature to the callback" do 41 | WebMock.after_request(except: [:other_lib]) do |request_signature, _| 42 | @request_signature = request_signature 43 | end 44 | http_request(:get, "http://www.example.com/") 45 | expect(@request_signature.uri.to_s).to eq("http://www.example.com:80/") 46 | end 47 | 48 | after(:each) do 49 | WebMock::StubRegistry.instance.global_stubs.clear 50 | end 51 | 52 | it 'passes the same request signature instance to the callback that was passed to the global stub callback' do 53 | global_stub_request_sig = after_request_request_sig = nil 54 | WebMock.globally_stub_request do |request_sig| 55 | global_stub_request_sig = request_sig 56 | nil 57 | end 58 | 59 | WebMock.after_request do |request_sig, _| 60 | after_request_request_sig = request_sig 61 | end 62 | 63 | http_request(:get, "http://www.example.com/") 64 | expect(global_stub_request_sig).to be(after_request_request_sig) 65 | end 66 | 67 | context "passing response to callback" do 68 | context "when request is stubbed" do 69 | before(:each) do 70 | stub_request(:get, "http://www.example.com"). 71 | to_return( 72 | status: [200, "hello"], 73 | headers: {'Content-Length' => '666', 'Hello' => 'World'}, 74 | body: "foo bar" 75 | ) 76 | WebMock.after_request(except: [:other_lib]) do |_, response| 77 | @response = response 78 | end 79 | http_request(:get, "http://www.example.com/") 80 | end 81 | 82 | it "should pass response to callback with the status and message" do 83 | expect(@response.status).to eq([200, "hello"]) 84 | end 85 | 86 | it "should pass response to callback with headers" do 87 | expect(@response.headers).to eq({ 88 | 'Content-Length' => '666', 89 | 'Hello' => 'World' 90 | }) 91 | end 92 | 93 | it "should pass response to callback with body" do 94 | expect(@response.body).to eq("foo bar") 95 | end 96 | end 97 | 98 | describe "when request is not stubbed", net_connect: true do 99 | before(:each) do 100 | WebMock.reset! 101 | WebMock.allow_net_connect! 102 | WebMock.after_request(except: [:other_lib]) do |_, response| 103 | @response = response 104 | end 105 | http_request(:get, "https://httpstat.us/201", headers: { "Accept" => "*" }) 106 | end 107 | 108 | it "should pass real response to callback with status and message" do 109 | expect(@response.status[0]).to eq(201) 110 | expect(@response.status[1]).to eq("Created") unless adapter_info.include?(:no_status_message) 111 | end 112 | 113 | it "should pass real response to callback with headers" do 114 | expect(@response.headers["Server"]).to eq( "Kestrel") 115 | expect(@response.headers["Content-Length"]).to eq("11") unless adapter_info.include?(:no_content_length_header) 116 | end 117 | 118 | it "should pass response to callback with body" do 119 | expect(@response.body.size).to eq(11) 120 | end 121 | end 122 | end 123 | 124 | it "should invoke multiple callbacks in order of their declarations" do 125 | WebMock.after_request { @called = 1 } 126 | WebMock.after_request { @called += 1 } 127 | http_request(:get, "http://www.example.com/") 128 | expect(@called).to eq(2) 129 | end 130 | 131 | it "should invoke callbacks only for real requests if requested", net_connect: true do 132 | WebMock.after_request(real_requests_only: true) { @called = true } 133 | http_request(:get, "http://www.example.com/") 134 | expect(@called).to eq(nil) 135 | WebMock.allow_net_connect! 136 | http_request(:get, "http://www.example.net/") 137 | expect(@called).to eq(true) 138 | end 139 | 140 | it "should not invoke any callbacks after callbacks were reset" do 141 | WebMock.after_request { @called = 1 } 142 | WebMock.reset_callbacks 143 | stub_request(:get, "http://www.example.com/") 144 | http_request(:get, "http://www.example.com/") 145 | expect(@called).to eq(nil) 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /spec/acceptance/shared/complex_cross_concern_behaviors.rb: -------------------------------------------------------------------------------- 1 | shared_context "complex cross-concern behaviors" do |*adapter_info| 2 | it 'allows a response with multiple values for the same header to be recorded and played back exactly as-is' do 3 | WebMock.allow_net_connect! 4 | 5 | recorded_response = nil 6 | WebMock.after_request { |_,r| recorded_response = r } 7 | real_response = http_request(:get, webmock_server_url) 8 | 9 | stub_request(:get, webmock_server_url).to_return( 10 | status: recorded_response.status, 11 | body: recorded_response.body, 12 | headers: recorded_response.headers 13 | ) 14 | 15 | played_back_response = http_request(:get, webmock_server_url) 16 | 17 | expect(played_back_response.headers.keys).to include('Set-Cookie') 18 | expect(played_back_response).to eq(real_response) 19 | end 20 | 21 | let(:no_content_url) { 'https://httpstat.us/204' } 22 | [nil, ''].each do |stub_val| 23 | it "returns the same value (nil or "") for a request stubbed as #{stub_val.inspect} that a real empty response has", net_connect: true do 24 | unless http_library == :curb 25 | WebMock.allow_net_connect! 26 | 27 | real_response = http_request(:get, no_content_url) 28 | stub_request(:get, no_content_url).to_return(status: 204, body: stub_val) 29 | stubbed_response = http_request(:get, no_content_url) 30 | 31 | expect(stubbed_response.body).to eq(real_response.body) 32 | end 33 | end 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /spec/acceptance/shared/enabling_and_disabling_webmock.rb: -------------------------------------------------------------------------------- 1 | shared_context "enabled and disabled webmock" do |*adapter_info| 2 | describe "when webmock is disabled" do 3 | before(:each) do 4 | WebMock.disable! 5 | end 6 | after(:each) do 7 | WebMock.enable! 8 | end 9 | include_context "disabled WebMock" 10 | end 11 | 12 | describe "when webmock is enabled again" do 13 | before(:each) do 14 | WebMock.disable! 15 | WebMock.enable! 16 | end 17 | include_context "enabled WebMock" 18 | end 19 | 20 | describe "when webmock is disabled except this lib" do 21 | before(:each) do 22 | WebMock.disable!(except: [http_library]) 23 | end 24 | after(:each) do 25 | WebMock.enable! 26 | end 27 | include_context "enabled WebMock" 28 | end 29 | 30 | describe "when webmock is enabled except this lib" do 31 | before(:each) do 32 | WebMock.disable! 33 | WebMock.enable!(except: [http_library]) 34 | end 35 | after(:each) do 36 | WebMock.enable! 37 | end 38 | include_context "disabled WebMock" 39 | end 40 | end 41 | 42 | shared_context "disabled WebMock" do 43 | it "should not register executed requests" do 44 | http_request(:get, webmock_server_url) 45 | expect(a_request(:get, webmock_server_url)).not_to have_been_made 46 | end 47 | 48 | it "should not block unstubbed requests" do 49 | expect { 50 | http_request(:get, webmock_server_url) 51 | }.not_to raise_error 52 | end 53 | 54 | it "should return real response even if there are stubs" do 55 | stub_request(:get, /.*/).to_return(body: "x") 56 | expect(http_request(:get, webmock_server_url).body).to eq("hello world") 57 | end 58 | 59 | it "should not invoke any callbacks" do 60 | WebMock.reset_callbacks 61 | stub_request(:get, webmock_server_url) 62 | @called = nil 63 | WebMock.after_request { @called = 1 } 64 | http_request(:get, webmock_server_url) 65 | expect(@called).to eq(nil) 66 | end 67 | end 68 | 69 | shared_context "enabled WebMock" do 70 | it "should register executed requests" do 71 | WebMock.allow_net_connect! 72 | http_request(:get, webmock_server_url) 73 | expect(a_request(:get, webmock_server_url)).to have_been_made 74 | end 75 | 76 | it "should block unstubbed requests" do 77 | expect { 78 | http_request(:get, "http://www.example.com/") 79 | }.to raise_error(WebMock::NetConnectNotAllowedError) 80 | end 81 | 82 | it "should return stubbed response" do 83 | stub_request(:get, /.*/).to_return(body: "x") 84 | expect(http_request(:get, "http://www.example.com/").body).to eq("x") 85 | end 86 | 87 | it "should invoke callbacks" do 88 | WebMock.allow_net_connect! 89 | WebMock.reset_callbacks 90 | @called = nil 91 | WebMock.after_request { @called = 1 } 92 | http_request(:get, webmock_server_url) 93 | expect(@called).to eq(1) 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec/acceptance/shared/precedence_of_stubs.rb: -------------------------------------------------------------------------------- 1 | shared_context "precedence of stubs" do |*adapter_info| 2 | describe "when choosing a matching request stub" do 3 | it "should use the last declared matching request stub" do 4 | stub_request(:get, "www.example.com").to_return(body: "abc") 5 | stub_request(:get, "www.example.com").to_return(body: "def") 6 | expect(http_request(:get, "http://www.example.com/").body).to eq("def") 7 | end 8 | 9 | it "should not be affected by the type of uri or request method" do 10 | stub_request(:get, "www.example.com").to_return(body: "abc") 11 | stub_request(:any, /.*example.*/).to_return(body: "def") 12 | expect(http_request(:get, "http://www.example.com/").body).to eq("def") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/acceptance/typhoeus/typhoeus_hydra_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'acceptance/webmock_shared' 3 | 4 | unless RUBY_PLATFORM =~ /java/ 5 | require 'acceptance/typhoeus/typhoeus_hydra_spec_helper' 6 | 7 | describe "Typhoeus::Hydra" do 8 | include TyphoeusHydraSpecHelper 9 | let(:hydra) { Typhoeus::Hydra.new } 10 | 11 | before do 12 | Typhoeus::Expectation.clear 13 | end 14 | 15 | include_context "with WebMock" 16 | 17 | describe "Typhoeus::Hydra features" do 18 | before(:each) do 19 | WebMock.disable_net_connect! 20 | WebMock.reset! 21 | end 22 | 23 | describe "supposed response fields" do 24 | it "present" do 25 | stub_request(:get, "http://www.example.com").to_return(headers: {'X-Test' => '1'}) 26 | response = Typhoeus.get("http://www.example.com") 27 | expect(response.code).not_to be_nil 28 | expect(response.status_message).not_to be_nil 29 | expect(response.body).not_to be_nil 30 | expect(response.headers).not_to be_nil 31 | expect(response.effective_url).not_to be_nil 32 | expect(response.total_time).to eq 0.0 33 | expect(response.time).to eq 0.0 # aliased by Typhoeus::Response::Informations 34 | expect(response.starttransfer_time).to eq 0.0 35 | expect(response.start_transfer_time).to eq 0.0 # aliased by Typhoeus::Response::Informations 36 | expect(response.appconnect_time).to eq 0.0 37 | expect(response.pretransfer_time).to eq 0.0 38 | expect(response.connect_time).to eq 0.0 39 | expect(response.namelookup_time).to eq 0.0 40 | expect(response.redirect_time).to eq 0.0 41 | end 42 | end 43 | 44 | describe "when params are used" do 45 | it "should take into account params for POST request" do 46 | stub_request(:post, "www.example.com/?hello=world").with(query: {hello: 'world'}) 47 | request = Typhoeus::Request.new("http://www.example.com", method: :post, params: {hello: 'world'}) 48 | hydra.queue(request) 49 | hydra.run 50 | end 51 | 52 | it "should take into account body for POST request" do 53 | stub_request(:post, "www.example.com").with(body: {hello: 'world'}) 54 | response = Typhoeus.post("http://www.example.com", method: :post, body: {hello: 'world'}) 55 | expect(response.code).to eq(200) 56 | end 57 | 58 | it "should take into account params for GET request" do 59 | stub_request(:get, "http://www.example.com/?hello=world").to_return({}) 60 | request = Typhoeus::Request.new("http://www.example.com/?hello=world", method: :get) 61 | hydra.queue(request) 62 | hydra.run 63 | end 64 | end 65 | 66 | describe "timeouts" do 67 | it "should support native typhoeus timeouts" do 68 | stub_request(:any, "example.com").to_timeout 69 | 70 | request = Typhoeus::Request.new("http://example.com", method: :get) 71 | hydra.queue(request) 72 | hydra.run 73 | 74 | expect(request.response).to be_timed_out 75 | end 76 | end 77 | 78 | describe "callbacks" do 79 | before(:each) do 80 | @request = Typhoeus::Request.new("http://example.com") 81 | end 82 | 83 | it "should call on_complete with 2xx response" do 84 | body = "on_success fired" 85 | stub_request(:any, "example.com").to_return(body: body) 86 | 87 | test = nil 88 | Typhoeus.on_complete do |c| 89 | test = c.body 90 | end 91 | hydra.queue @request 92 | hydra.run 93 | expect(test).to eq(body) 94 | end 95 | 96 | it "should call on_complete with 5xx response" do 97 | response_code = 599 98 | stub_request(:any, "example.com").to_return(status: [response_code, "Server On Fire"]) 99 | 100 | test = nil 101 | Typhoeus.on_complete do |c| 102 | test = c.code 103 | end 104 | hydra.queue @request 105 | hydra.run 106 | expect(test).to eq(response_code) 107 | end 108 | 109 | it "should call on_body with 2xx response" do 110 | body = "on_body fired" 111 | stub_request(:any, "example.com").to_return(body: body) 112 | 113 | test_body = nil 114 | test_complete = nil 115 | skip("This test requires a newer version of Typhoeus") unless @request.respond_to?(:on_body) 116 | @request.on_body do |body_chunk, response| 117 | test_body = body_chunk 118 | end 119 | @request.on_complete do |response| 120 | test_complete = response.body 121 | end 122 | hydra.queue @request 123 | hydra.run 124 | expect(test_body).to eq(body) 125 | expect(test_complete).to eq("") 126 | end 127 | 128 | it "should initialize the streaming response body with a mutible (non-frozen) string" do 129 | skip("This test requires a newer version of Typhoeus") unless @request.respond_to?(:on_body) 130 | 131 | stub_request(:any, "example.com").to_return(body: "body") 132 | 133 | @request.on_body do |body_chunk, response| 134 | response.body << body_chunk 135 | end 136 | hydra.queue @request 137 | 138 | expect{ hydra.run }.not_to raise_error 139 | end 140 | 141 | it "should call on_headers with 2xx response" do 142 | body = "on_headers fired" 143 | stub_request(:any, "example.com").to_return(body: body, headers: {'X-Test' => '1'}) 144 | 145 | test_headers = nil 146 | skip("This test requires a newer version of Typhoeus") unless @request.respond_to?(:on_headers) 147 | @request.on_headers do |response| 148 | test_headers = response.headers 149 | end 150 | hydra.queue @request 151 | hydra.run 152 | expect(test_headers.to_h).to include('X-Test' => '1') 153 | end 154 | end 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module TyphoeusHydraSpecHelper 4 | class FakeTyphoeusHydraTimeoutError < StandardError; end 5 | class FakeTyphoeusHydraConnectError < StandardError; end 6 | 7 | 8 | def http_request(method, uri, options = {}, &block) 9 | uri = uri.gsub(" ", "%20") #typhoeus doesn't like spaces in the uri 10 | request_options = { 11 | method: method, 12 | body: options[:body], 13 | headers: options[:headers], 14 | timeout: 25000 15 | } 16 | if options[:basic_auth] 17 | request_options[:userpwd] = options[:basic_auth].join(':') 18 | end 19 | request = Typhoeus::Request.new(uri, request_options) 20 | 21 | hydra = Typhoeus::Hydra.new 22 | hydra.queue(request) 23 | hydra.run 24 | 25 | response = request.response 26 | raise FakeTyphoeusHydraConnectError.new if response.return_code == :couldnt_connect 27 | raise FakeTyphoeusHydraTimeoutError.new if response.timed_out? 28 | OpenStruct.new({ 29 | body: response.body, 30 | headers: WebMock::Util::Headers.normalize_headers(join_array_values(response.headers)), 31 | status: response.code.to_s, 32 | message: response.status_message 33 | }) 34 | end 35 | 36 | def join_array_values(hash) 37 | joined = {} 38 | if hash 39 | hash.each do |k,v| 40 | v = v.join(", ") if v.is_a?(Array) 41 | joined[k] = v 42 | end 43 | end 44 | joined 45 | end 46 | 47 | 48 | def client_timeout_exception_class 49 | FakeTyphoeusHydraTimeoutError 50 | end 51 | 52 | def connection_refused_exception_class 53 | FakeTyphoeusHydraConnectError 54 | end 55 | 56 | def connection_error_class 57 | end 58 | 59 | def http_library 60 | :typhoeus 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /spec/acceptance/webmock_shared.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'acceptance/shared/enabling_and_disabling_webmock' 3 | require 'acceptance/shared/returning_declared_responses' 4 | require 'acceptance/shared/callbacks' 5 | require 'acceptance/shared/request_expectations' 6 | require 'acceptance/shared/stubbing_requests' 7 | require 'acceptance/shared/allowing_and_disabling_net_connect' 8 | require 'acceptance/shared/precedence_of_stubs' 9 | require 'acceptance/shared/complex_cross_concern_behaviors' 10 | 11 | unless defined? SAMPLE_HEADERS 12 | SAMPLE_HEADERS = { "Content-Length" => "8888", "Accept" => "application/json" } 13 | ESCAPED_PARAMS = "x=ab%20c&z=%27Stop%21%27%20said%20Fred%20m" 14 | NOT_ESCAPED_PARAMS = "z='Stop!' said Fred m&x=ab c" 15 | end 16 | 17 | shared_examples "with WebMock" do |*adapter_info| 18 | describe "with WebMock" do 19 | let(:webmock_server_url) {"http://#{WebMockServer.instance.host_with_port}/"} 20 | before(:each) do 21 | WebMock.disable_net_connect! 22 | WebMock.reset! 23 | end 24 | 25 | around(:each, net_connect: true) do |ex| 26 | ex.run_with_retry retry: 2, exceptions_to_retry: [ 27 | client_timeout_exception_class, 28 | connection_refused_exception_class, 29 | connection_error_class 30 | ].compact 31 | end 32 | 33 | include_context "allowing and disabling net connect", *adapter_info 34 | 35 | include_context "stubbing requests", *adapter_info 36 | 37 | include_context "declared responses", *adapter_info 38 | 39 | include_context "precedence of stubs", *adapter_info 40 | 41 | include_context "request expectations", *adapter_info 42 | 43 | include_context "callbacks", *adapter_info 44 | 45 | include_context "enabled and disabled webmock", *adapter_info 46 | 47 | include_context "complex cross-concern behaviors", *adapter_info 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/fixtures/test.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /spec/quality_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | # Borrowed from Bundler 4 | # https://github.com/carlhuda/bundler/blob/1-0-stable/spec/quality_spec.rb 5 | # Portions copyright (c) 2010 Andre Arko 6 | # Portions copyright (c) 2009 Engine Yard 7 | 8 | # MIT License 9 | 10 | # Permission is hereby granted, free of charge, to any person obtaining 11 | # a copy of this software and associated documentation files (the 12 | # "Software"), to deal in the Software without restriction, including 13 | # without limitation the rights to use, copy, modify, merge, publish, 14 | # distribute, sublicense, and/or sell copies of the Software, and to 15 | # permit persons to whom the Software is furnished to do so, subject to 16 | # the following conditions: 17 | 18 | # The above copyright notice and this permission notice shall be 19 | # included in all copies or substantial portions of the Software. 20 | 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | describe "The library itself" do 30 | def check_for_tab_characters(filename) 31 | failing_lines = [] 32 | File.readlines(filename).each_with_index do |line,number| 33 | failing_lines << number + 1 if line =~ /\t/ 34 | end 35 | 36 | unless failing_lines.empty? 37 | "#{filename} has tab characters on lines #{failing_lines.join(', ')}" 38 | end 39 | end 40 | 41 | def check_for_extra_spaces(filename) 42 | failing_lines = [] 43 | File.readlines(filename).each_with_index do |line,number| 44 | next if line =~ /^\s+#.*\s+\n$/ 45 | failing_lines << number + 1 if line =~ /\s+\n$/ 46 | end 47 | 48 | unless failing_lines.empty? 49 | "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}" 50 | end 51 | end 52 | 53 | RSpec::Matchers.define :be_well_formed do 54 | failure_message do |actual| 55 | actual.join("\n") 56 | end 57 | 58 | match do |actual| 59 | actual.empty? 60 | end 61 | end 62 | 63 | it "has no malformed whitespace" do 64 | error_messages = [] 65 | Dir.chdir(File.expand_path("../..", __FILE__)) do 66 | `git ls-files`.split("\n").each do |filename| 67 | next if filename =~ /\.gitmodules|fixtures/ 68 | error_messages << check_for_tab_characters(filename) 69 | error_messages << check_for_extra_spaces(filename) 70 | end 71 | end 72 | expect(error_messages.compact).to be_well_formed 73 | end 74 | 75 | it "can still be built" do 76 | Dir.chdir(File.expand_path('../../', __FILE__)) do 77 | `gem build webmock.gemspec` 78 | expect($?).to eq(0) 79 | 80 | # clean up the .gem generated 81 | system("rm webmock-#{WebMock.version}.gem") 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'httpclient' 3 | unless RUBY_PLATFORM =~ /java/ 4 | require 'curb' 5 | require 'patron' 6 | require 'typhoeus' 7 | end 8 | if RUBY_PLATFORM =~ /java/ 9 | require 'manticore' 10 | end 11 | 12 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 13 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 14 | require 'rspec' 15 | require 'rspec/retry' 16 | 17 | require 'webmock/rspec' 18 | 19 | require 'support/network_connection' 20 | require 'support/webmock_server' 21 | require 'support/my_rack_app' 22 | require 'support/failures' 23 | 24 | Warning[:deprecated] = true if Warning.respond_to?(:[]=) 25 | 26 | CURL_EXAMPLE_OUTPUT_PATH = File.expand_path('../support/example_curl_output.txt', __FILE__) 27 | 28 | RSpec.configure do |config| 29 | no_network_connection = ENV["NO_CONNECTION"] || ! NetworkConnection.is_network_available? 30 | if no_network_connection 31 | warn("No network connectivity. Only examples which do not make real network connections will run.") 32 | config.filter_run_excluding net_connect: true 33 | end 34 | 35 | config.filter_run_excluding without_webmock: true 36 | 37 | config.before(:suite) do 38 | WebMockServer.instance.start unless WebMockServer.instance.started 39 | end 40 | 41 | config.after(:suite) do 42 | WebMockServer.instance.stop 43 | end 44 | 45 | config.filter_run focus: true 46 | config.run_all_when_everything_filtered = true 47 | 48 | config.include Failures 49 | end 50 | 51 | -------------------------------------------------------------------------------- /spec/support/example_curl_output.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 202 OK 2 | Content-Type: text/html; charset=UTF-8 3 | Connection: Keep-Alive 4 | Date: Sat, 23 Jan 2010 01:01:05 GMT 5 | Content-Length: 419 6 | Accept: image/jpeg 7 | Accept: image/png 8 | 9 | 10 |
11 |You have reached this web page by typing "example.com", 15 | "example.net", 16 | or "example.org" into your web browser.
17 |These domain names are reserved for use in documentation and are not available 18 | for registration. See RFC 19 | 2606, Section 3.
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /spec/support/failures.rb: -------------------------------------------------------------------------------- 1 | module Failures 2 | def fail() 3 | raise_error(RSpec::Expectations::ExpectationNotMetError) 4 | end 5 | 6 | def fail_with(message) 7 | raise_error(RSpec::Expectations::ExpectationNotMetError, message) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/support/my_rack_app.rb: -------------------------------------------------------------------------------- 1 | require 'rack' 2 | 3 | class MyRackApp 4 | class NonArrayResponse 5 | # The rack response body need not implement #join, 6 | # but it must implement #each. It need not be an Array. 7 | # ActionDispatch::Response, for example, exercises that fact. 8 | # See: http://rack.rubyforge.org/doc/SPEC.html 9 | 10 | def each(*args, &blk) 11 | ["This is not in an array!"].each(*args, &blk) 12 | end 13 | end 14 | 15 | def self.call(env) 16 | case env.values_at('REQUEST_METHOD', 'PATH_INFO') 17 | when ['GET', '/'] 18 | [200, {}, ["This is my root!"]] 19 | when ['GET', '/greet'] 20 | name = env['QUERY_STRING'][/name=([^&]*)/, 1] || "World" 21 | [200, {}, ["Hello, #{name}"]] 22 | when ['GET', '/non_array_response'] 23 | [200, {}, NonArrayResponse.new] 24 | when ['GET', '/locked'] 25 | [200, {}, ["Single threaded response."]] 26 | when ['POST', '/greet'] 27 | name = env["rack.input"].read(env["CONTENT_LENGTH"]) 28 | name = name.force_encoding("UTF-8") if name.respond_to? :force_encoding 29 | name = name[/name=([^&]*)/, 1] || "World" 30 | [200, {}, ["Good to meet you, #{name}!"]] 31 | when ['GET', '/compute'] 32 | if env['SERVER_PORT'] == 80 && env["SCRIPT_NAME"] == "" 33 | [200, {}, [""]] 34 | else 35 | [401, {}, [""]] 36 | end 37 | when ['GET', '/error'] 38 | env['rack.errors'].puts('Error!') 39 | [500, {}, ['']] 40 | when ['GET', '/env'] 41 | [200, {}, [JSON.dump(env)]] 42 | else 43 | [404, {}, ['']] 44 | end 45 | end 46 | end 47 | 48 | class MyLockedRackApp 49 | MUTEX = Mutex.new 50 | 51 | def self.call(env) 52 | lock = Rack::Lock.new(MyRackApp, MUTEX) 53 | lock.call(env) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/support/network_connection.rb: -------------------------------------------------------------------------------- 1 | require 'timeout' 2 | require 'socket' 3 | 4 | module NetworkConnection 5 | def self.connect_to(host, port, timeout=10) 6 | Timeout.timeout(timeout) do 7 | TCPSocket.new(host, port) 8 | end 9 | end 10 | 11 | def self.is_network_available? 12 | begin 13 | self.connect_to("8.8.8.8", 53, 5) 14 | true 15 | rescue 16 | false 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/webmock_server.rb: -------------------------------------------------------------------------------- 1 | require 'webrick' 2 | require 'logger' 3 | require 'singleton' 4 | 5 | class WebMockServer 6 | include Singleton 7 | 8 | attr_reader :port, :started 9 | 10 | def host_with_port 11 | "localhost:#{port}" 12 | end 13 | 14 | def concurrent 15 | unless RUBY_PLATFORM =~ /java/ 16 | @pid = Process.fork do 17 | yield 18 | end 19 | else 20 | Thread.new { yield } 21 | end 22 | end 23 | 24 | def start 25 | @started = true 26 | server = WEBrick::GenericServer.new(Port: 0, Logger: Logger.new("/dev/null")) 27 | server.logger.level = 0 28 | @port = server.config[:Port] 29 | 30 | concurrent do 31 | ['TERM', 'INT'].each do |signal| 32 | trap(signal) do 33 | Thread.new do 34 | server.shutdown 35 | end 36 | end 37 | end 38 | server.start do |socket| 39 | socket.read(1) 40 | socket.puts <<-EOT.gsub(/^\s+\|/, '') 41 | |HTTP/1.1 200 OK\r 42 | |Date: Fri, 31 Dec 1999 23:59:59 GMT\r 43 | |Content-Type: text/html\r 44 | |Content-Length: 11\r 45 | |Set-Cookie: bar\r 46 | |Set-Cookie: foo\r 47 | |\r 48 | |hello world 49 | EOT 50 | end 51 | end 52 | 53 | 54 | loop do 55 | begin 56 | TCPSocket.new("localhost", port) 57 | sleep 0.1 58 | break 59 | rescue Errno::ECONNREFUSED 60 | sleep 0.1 61 | end 62 | end 63 | end 64 | 65 | def stop 66 | if @pid 67 | Process.kill('INT', @pid) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/unit/http_lib_adapters/http_lib_adapter_registry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::HttpLibAdapterRegistry do 4 | describe "each_adapter" do 5 | it "should yield block over each adapter" do 6 | class MyAdapter < WebMock::HttpLibAdapter; end 7 | WebMock::HttpLibAdapterRegistry.instance.register(:my_lib, MyAdapter) 8 | adapters = [] 9 | WebMock::HttpLibAdapterRegistry.instance.each_adapter {|n,a| 10 | adapters << [n, a] 11 | } 12 | expect(adapters).to include([:my_lib, MyAdapter]) 13 | WebMock::HttpLibAdapterRegistry.instance. 14 | http_lib_adapters.delete(:my_lib) 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /spec/unit/http_lib_adapters/http_lib_adapter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::HttpLibAdapter do 4 | describe "adapter_for" do 5 | it "should add adapter to adapter registry" do 6 | class MyAdapter < WebMock::HttpLibAdapter; end 7 | expect(WebMock::HttpLibAdapterRegistry.instance). 8 | to receive(:register).with(:my_lib, MyAdapter) 9 | MyAdapter.adapter_for(:my_lib) 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/unit/matchers/hash_excluding_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module WebMock 4 | module Matchers 5 | describe HashExcludingMatcher do 6 | it 'stringifies the given hash keys' do 7 | expect(HashExcludingMatcher.new(a: 1, b: 2)).not_to eq('a' => 1, 'b' => 2) 8 | end 9 | 10 | it 'sorts elements in the hash' do 11 | expect(HashExcludingMatcher.new(b: 2, a: 1)).not_to eq('a' => 1, 'b' => 2) 12 | end 13 | 14 | it 'describes itself properly' do 15 | expect(HashExcludingMatcher.new(a: 1).inspect).to eq "hash_excluding(#{{'a' => 1}})" 16 | end 17 | 18 | describe 'success' do 19 | it 'match with hash with a missing key' do 20 | expect(HashExcludingMatcher.new(a: 1)).to eq('b' => 2) 21 | end 22 | 23 | it 'match an empty hash with a given key' do 24 | expect(HashExcludingMatcher.new(a: 1)).to eq({}) 25 | end 26 | 27 | it 'match when values are nil but keys are different' do 28 | expect(HashExcludingMatcher.new(a: nil)).to eq('b' => nil) 29 | end 30 | 31 | describe 'when matching an empty hash' do 32 | it 'does not matches against any hash' do 33 | expect(HashExcludingMatcher.new({})).to eq(a: 1, b: 2, c: 3) 34 | end 35 | end 36 | end 37 | 38 | describe 'failing' do 39 | it 'does not match a hash with a one missing key when one pair is matching' do 40 | expect(HashExcludingMatcher.new(a: 1, b: 2)).not_to eq('b' => 2) 41 | end 42 | 43 | it 'match a hash with an incorrect value' do 44 | expect(HashExcludingMatcher.new(a: 1, b: 2)).not_to eq('a' => 1, 'b' => 3) 45 | end 46 | 47 | it 'does not matches the same hash' do 48 | expect(HashExcludingMatcher.new('a' => 1, 'b' => 2)).not_to eq('a' => 1, 'b' => 2) 49 | end 50 | 51 | it 'does not matches a hash with extra stuff' do 52 | expect(HashExcludingMatcher.new(a: 1)).not_to eq('a' => 1, 'b' => 2) 53 | end 54 | 55 | it 'does not match a non-hash' do 56 | expect(HashExcludingMatcher.new(a: 1)).not_to eq 1 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/matchers/hash_including_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module WebMock 4 | module Matchers 5 | 6 | describe HashIncludingMatcher do 7 | 8 | it "stringifies the given hash keys" do 9 | expect(HashIncludingMatcher.new(a: 1, b: 2)).to eq("a" => 1, "b" => 2) 10 | end 11 | 12 | it "sorts elements in the hash" do 13 | expect(HashIncludingMatcher.new(b: 2, a: 1)).to eq("a" => 1, "b" => 2) 14 | end 15 | 16 | it "describes itself properly" do 17 | expect(HashIncludingMatcher.new(a: 1).inspect).to eq "hash_including(#{{"a" => 1}})" 18 | end 19 | 20 | describe "success" do 21 | it "matches the same hash" do 22 | expect(HashIncludingMatcher.new("a" => 1, "b" => 2)).to eq("a" => 1, "b" => 2) 23 | end 24 | 25 | it "matches a hash with extra stuff" do 26 | expect(HashIncludingMatcher.new(a: 1)).to eq("a" => 1, "b" => 2) 27 | end 28 | 29 | describe "when matching anythingized keys" do 30 | let(:anything) { WebMock::Matchers::AnyArgMatcher.new(nil) } 31 | 32 | it "matches an int against anything()" do 33 | expect(HashIncludingMatcher.new(a: anything, b: 2)).to eq({'a' => 1, 'b' => 2}) 34 | end 35 | 36 | it "matches a string against anything()" do 37 | expect(HashIncludingMatcher.new(a: anything, b: 2)).to eq({'a' => "1", 'b' => 2}) 38 | end 39 | 40 | it "matches if the key is present" do 41 | expect(HashIncludingMatcher.new(a: anything)).to eq({'a' => 1, 'b' => 2}) 42 | end 43 | 44 | it "matches if more keys are present" do 45 | expect(HashIncludingMatcher.new(a: anything, b: anything)).to eq({'a' => 1, 'b' => 2, 'c' => 3}) 46 | end 47 | 48 | it "matches if passed many keys and many key/value pairs" do 49 | expect(HashIncludingMatcher.new(a: anything, b: anything, c: 3, e: 5)).to eq({'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5}) 50 | end 51 | end 52 | 53 | describe "when matching an empty hash" do 54 | it "matches against any hash" do 55 | expect(HashIncludingMatcher.new({})).to eq({a: 1, b: 2, c: 3}) 56 | end 57 | end 58 | end 59 | 60 | describe "failing" do 61 | it "does not match a non-hash" do 62 | expect(HashIncludingMatcher.new(a: 1)).not_to eq 1 63 | end 64 | 65 | it "does not match a hash with a missing key" do 66 | expect(HashIncludingMatcher.new(a: 1)).not_to eq('b' => 2) 67 | end 68 | 69 | it "does not match an empty hash with a given key" do 70 | expect(HashIncludingMatcher.new(a: 1)).not_to eq({}) 71 | end 72 | 73 | it "does not match a hash with a missing key when one pair is matching" do 74 | expect(HashIncludingMatcher.new(a: 1, b: 2)).not_to eq('b' => 2) 75 | end 76 | 77 | it "does not match a hash with an incorrect value" do 78 | expect(HashIncludingMatcher.new(a: 1, b: 2)).not_to eq('a' => 1, 'b' => 3) 79 | end 80 | 81 | it "does not match when values are nil but keys are different" do 82 | expect(HashIncludingMatcher.new(a: nil)).not_to eq('b' => nil) 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/unit/rack_response_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::RackResponse do 4 | before :each do 5 | @rack_response = WebMock::RackResponse.new(MyRackApp) 6 | end 7 | 8 | it "should hook up to a rack appliance" do 9 | request = WebMock::RequestSignature.new(:get, 'www.example.com') 10 | response = @rack_response.evaluate(request) 11 | 12 | expect(response.status.first).to eq(200) 13 | expect(response.body).to include('This is my root!') 14 | end 15 | 16 | it "should set the reason phrase based on the status code" do 17 | request = WebMock::RequestSignature.new(:get, 'www.example.com') 18 | response = @rack_response.evaluate(request) 19 | expect(response.status).to eq([200, "OK"]) 20 | 21 | request = WebMock::RequestSignature.new(:get, 'www.example.com/error') 22 | response = @rack_response.evaluate(request) 23 | expect(response.status).to eq([500, "Internal Server Error"]) 24 | end 25 | 26 | it "should behave correctly when the rack response is not a simple array of strings" do 27 | request = WebMock::RequestSignature.new(:get, 'www.example.com/non_array_response') 28 | response = @rack_response.evaluate(request) 29 | 30 | expect(response.status.first).to eq(200) 31 | expect(response.body).to include('This is not in an array!') 32 | end 33 | 34 | it "should shouldn't blow up when hitting a locked resource twice" do 35 | @locked_rack_response = WebMock::RackResponse.new(MyLockedRackApp) 36 | request = WebMock::RequestSignature.new(:get, 'www.example.com/locked') 37 | @locked_rack_response.evaluate(request) 38 | response2 = @locked_rack_response.evaluate(request) 39 | 40 | expect(response2.body).to include('Single threaded response.') 41 | expect(response2.status.first).to eq(200) 42 | end 43 | 44 | it "should send along params" do 45 | request = WebMock::RequestSignature.new(:get, 'www.example.com/greet?name=Johnny') 46 | 47 | response = @rack_response.evaluate(request) 48 | 49 | expect(response.status.first).to eq(200) 50 | expect(response.body).to include('Hello, Johnny') 51 | end 52 | 53 | it "should send along POST params" do 54 | request = WebMock::RequestSignature.new(:post, 'www.example.com/greet', 55 | body: 'name=Jimmy' 56 | ) 57 | 58 | response = @rack_response.evaluate(request) 59 | expect(response.body).to include('Good to meet you, Jimmy!') 60 | end 61 | 62 | it "should send params with proper content length if params have non-ascii symbols" do 63 | request = WebMock::RequestSignature.new(:post, 'www.example.com/greet', 64 | body: 'name=Олег' 65 | ) 66 | 67 | response = @rack_response.evaluate(request) 68 | expect(response.body).to include('Good to meet you, Олег!') 69 | end 70 | 71 | it "should send or not a rack.version depending on the Rack version" do 72 | request = WebMock::RequestSignature.new(:get, 'www.example.com/env') 73 | response = @rack_response.evaluate(request) 74 | 75 | expect(response.status.first).to eq(200) 76 | body = JSON.parse(response.body) 77 | 78 | if Gem.loaded_specs["rack"].version > Gem::Version.new("3.0.0") 79 | expect(body).not_to include("rack.version") 80 | else 81 | expect(body).to include("rack.version") 82 | end 83 | end 84 | 85 | describe 'rack error output' do 86 | before :each do 87 | @original_stderr = $stderr 88 | $stderr = StringIO.new 89 | end 90 | 91 | after :each do 92 | $stderr = @original_stderr 93 | end 94 | 95 | it 'should behave correctly when an app uses rack.errors' do 96 | request = WebMock::RequestSignature.new(:get, 'www.example.com/error') 97 | 98 | expect { @rack_response.evaluate(request) }.to_not raise_error 99 | expect($stderr.length).to_not eq 0 100 | end 101 | end 102 | 103 | describe 'basic auth request' do 104 | before :each do 105 | @rack_response_with_basic_auth = WebMock::RackResponse.new( 106 | Rack::Auth::Basic.new(MyRackApp) do |username, password| 107 | username == 'username' && password == 'password' 108 | end 109 | ) 110 | end 111 | it 'should be failure when wrong credentials' do 112 | request = WebMock::RequestSignature.new(:get, 'foo:bar@www.example.com') 113 | response = @rack_response_with_basic_auth.evaluate(request) 114 | expect(response.status.first).to eq(401) 115 | expect(response.body).not_to include('This is my root!') 116 | end 117 | 118 | it 'should be success when valid credentials' do 119 | request = WebMock::RequestSignature.new(:get, 'username:password@www.example.com') 120 | response = @rack_response_with_basic_auth.evaluate(request) 121 | expect(response.status.first).to eq(200) 122 | expect(response.body).to include('This is my root!') 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/unit/request_body_diff_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe WebMock::RequestBodyDiff do 4 | subject { WebMock::RequestBodyDiff.new(request_signature, request_stub) } 5 | 6 | let(:uri) { "http://example.com" } 7 | let(:method) { "GET" } 8 | 9 | let(:request_stub) { WebMock::RequestStub.new(method, uri) } 10 | let(:request_signature) { WebMock::RequestSignature.new(method, uri) } 11 | 12 | let(:request_stub_body) { { "key" => "value"} } 13 | let(:request_signature_body) { {"key" => "different value"}.to_json } 14 | 15 | let(:request_pattern) { 16 | WebMock::RequestPattern.new( 17 | method, uri, {body: request_stub_body} 18 | ) 19 | } 20 | 21 | before :each do 22 | request_stub.request_pattern = request_pattern 23 | request_signature.headers = {"Content-Type" => "application/json"} 24 | request_signature.body = request_signature_body 25 | end 26 | 27 | describe "#body_diff" do 28 | context "request signature is unparseable json" do 29 | let(:request_signature_body) { "youcan'tparsethis!" } 30 | 31 | it "returns an empty hash" do 32 | expect(subject.body_diff).to eq({}) 33 | end 34 | end 35 | 36 | context "request stub body as unparseable json" do 37 | let(:request_stub_body) { "youcan'tparsethis!" } 38 | 39 | it "returns an empty hash" do 40 | expect(subject.body_diff).to eq({}) 41 | end 42 | end 43 | 44 | context "request stub body pattern is hash" do 45 | let(:request_stub_body) { { "key" => "value"} } 46 | 47 | it "generates a diff" do 48 | expect(subject.body_diff).to eq( 49 | [["~", "key", "different value", "value"]] 50 | ) 51 | end 52 | end 53 | 54 | context "request signature doesn't have json headers" do 55 | before :each do 56 | request_signature.headers = {"Content-Type" => "application/xml"} 57 | end 58 | 59 | it "returns an empty hash" do 60 | expect(subject.body_diff).to eq({}) 61 | end 62 | end 63 | 64 | context "request stub body pattern is a string" do 65 | let(:request_stub_body) { { "key" => "value"}.to_json } 66 | 67 | it "generates a diff" do 68 | expect(subject.body_diff).to eq( 69 | [["~", "key", "different value", "value"]] 70 | ) 71 | end 72 | end 73 | 74 | context "stub request has no request pattern" do 75 | let(:request_signature_body) { nil } 76 | 77 | it "returns an empty hash" do 78 | expect(subject.body_diff).to eq({}) 79 | end 80 | end 81 | 82 | context "stub request has no body pattern" do 83 | let(:request_stub_body) { nil } 84 | 85 | it "returns an empty hash" do 86 | expect(subject.body_diff).to eq({}) 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/unit/request_registry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::RequestRegistry do 4 | 5 | before(:each) do 6 | WebMock::RequestRegistry.instance.reset! 7 | @request_pattern = WebMock::RequestPattern.new(:get, "www.example.com") 8 | @request_signature = WebMock::RequestSignature.new(:get, "www.example.com") 9 | end 10 | 11 | describe "reset!" do 12 | before(:each) do 13 | WebMock::RequestRegistry.instance.requested_signatures.put(@request_signature) 14 | end 15 | 16 | it "should clean list of executed requests" do 17 | expect(WebMock::RequestRegistry.instance.times_executed(@request_pattern)).to eq(1) 18 | WebMock::RequestRegistry.instance.reset! 19 | expect(WebMock::RequestRegistry.instance.times_executed(@request_pattern)).to eq(0) 20 | end 21 | 22 | end 23 | 24 | describe "times executed" do 25 | 26 | before(:each) do 27 | @request_stub1 = WebMock::RequestStub.new(:get, "www.example.com") 28 | @request_stub2 = WebMock::RequestStub.new(:get, "www.example.net") 29 | @request_stub3 = WebMock::RequestStub.new(:get, "www.example.org") 30 | WebMock::RequestRegistry.instance.requested_signatures.put(WebMock::RequestSignature.new(:get, "www.example.com")) 31 | WebMock::RequestRegistry.instance.requested_signatures.put(WebMock::RequestSignature.new(:get, "www.example.com")) 32 | WebMock::RequestRegistry.instance.requested_signatures.put(WebMock::RequestSignature.new(:get, "www.example.org")) 33 | end 34 | 35 | it "should report 0 if no request matching pattern was requested" do 36 | expect(WebMock::RequestRegistry.instance.times_executed(WebMock::RequestPattern.new(:get, "www.example.net"))).to eq(0) 37 | end 38 | 39 | it "should report number of times matching pattern was requested" do 40 | expect(WebMock::RequestRegistry.instance.times_executed(WebMock::RequestPattern.new(:get, "www.example.com"))).to eq(2) 41 | end 42 | 43 | it "should report number of times all matching pattern were requested" do 44 | expect(WebMock::RequestRegistry.instance.times_executed(WebMock::RequestPattern.new(:get, /.*example.*/))).to eq(3) 45 | end 46 | 47 | describe "multithreading" do 48 | let(:request_pattern) { WebMock::RequestPattern.new(:get, "www.example.com") } 49 | 50 | # Reproduce a multithreading issue that causes a RuntimeError: 51 | # can't add a new key into hash during iteration. 52 | it "works normally iterating on the requested signature hash while another thread is setting it" do 53 | thread_injected = false 54 | allow(request_pattern).to receive(:matches?).and_wrap_original do |m, *args| 55 | unless thread_injected 56 | thread_injected = true 57 | Thread.new { WebMock::RequestRegistry.instance.requested_signatures.put(:abc) }.join(0.1) 58 | end 59 | m.call(*args) 60 | end 61 | expect(WebMock::RequestRegistry.instance.times_executed(request_pattern)).to eq(2) 62 | sleep 0.1 while !WebMock::RequestRegistry.instance.requested_signatures.hash.key?(:abc) 63 | end 64 | end 65 | end 66 | 67 | describe "request_signatures" do 68 | it "should return hash of unique request signatures with accumulated number" do 69 | WebMock::RequestRegistry.instance.requested_signatures.put(WebMock::RequestSignature.new(:get, "www.example.com")) 70 | WebMock::RequestRegistry.instance.requested_signatures.put(WebMock::RequestSignature.new(:get, "www.example.com")) 71 | expect(WebMock::RequestRegistry.instance.requested_signatures. 72 | get(WebMock::RequestSignature.new(:get, "www.example.com"))).to eq(2) 73 | end 74 | end 75 | 76 | describe "to_s" do 77 | it "should output string with all executed requests and numbers of executions" do 78 | [ 79 | WebMock::RequestSignature.new(:get, "www.example.com"), 80 | WebMock::RequestSignature.new(:get, "www.example.com"), 81 | WebMock::RequestSignature.new(:put, "www.example.org"), 82 | ].each do |s| 83 | WebMock::RequestRegistry.instance.requested_signatures.put(s) 84 | end 85 | expect(WebMock::RequestRegistry.instance.to_s).to eq( 86 | "GET http://www.example.com/ was made 2 times\nPUT http://www.example.org/ was made 1 time\n" 87 | ) 88 | end 89 | 90 | it "should output info if no requests were executed" do 91 | expect(WebMock::RequestRegistry.instance.to_s).to eq("No requests were made.") 92 | end 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /spec/unit/request_signature_snippet_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe WebMock::RequestSignatureSnippet do 4 | it("is real"){expect{subject}.not_to(raise_error)} 5 | 6 | subject { WebMock::RequestSignatureSnippet.new(request_signature) } 7 | 8 | let(:uri) { "http://example.com" } 9 | let(:method) { "GET" } 10 | 11 | let(:request_signature) { WebMock::RequestSignature.new(method, uri) } 12 | let(:request_signature_body) { {"key" => "different value"}.to_json } 13 | 14 | let(:request_pattern) { 15 | WebMock::RequestPattern.new( 16 | method, uri, {body: request_signature_body} 17 | ) 18 | } 19 | 20 | before :each do 21 | request_signature.headers = {"Content-Type" => "application/json"} 22 | request_signature.body = request_signature_body 23 | end 24 | 25 | describe "#stubbing_instructions" do 26 | context "with stubbing instructions turned off" do 27 | before :each do 28 | WebMock.hide_stubbing_instructions! 29 | end 30 | 31 | it "returns nil" do 32 | expect(subject.stubbing_instructions).to be nil 33 | end 34 | 35 | after :each do 36 | WebMock.show_stubbing_instructions! 37 | end 38 | end 39 | 40 | context "with stubbing instructions turned on" do 41 | it "returns a stub snippet" do 42 | expect(subject.stubbing_instructions).to include( 43 | "You can stub this request with the following snippet:" 44 | ) 45 | end 46 | end 47 | end 48 | 49 | describe "#request_stubs" do 50 | before :each do 51 | WebMock.stub_request(:get, "https://www.example.com").with(body: {"a" => "b"}) 52 | end 53 | 54 | context "when showing the body diff is turned off" do 55 | before :each do 56 | WebMock.hide_body_diff! 57 | end 58 | 59 | it "returns does not show the body diff" do 60 | result = subject.request_stubs 61 | result.sub!("registered request stubs:\n\n", "") 62 | expect(result).to eq( 63 | "stub_request(:get, \"https://www.example.com/\").\n with(\n body: #{{"a" => "b"}})" 64 | ) 65 | end 66 | 67 | after :each do 68 | WebMock.show_body_diff! 69 | end 70 | end 71 | 72 | context "when showing the body diff is turned on" do 73 | it "shows the body diff" do 74 | result = subject.request_stubs 75 | result.sub!("registered request stubs:\n\n", "") 76 | expect(result).to eq( 77 | "stub_request(:get, \"https://www.example.com/\").\n with(\n body: #{{"a" => "b"}})\n\nBody diff:\n [[\"-\", \"key\", \"different value\"], [\"+\", \"a\", \"b\"]]\n" 78 | ) 79 | end 80 | end 81 | 82 | context "with no request stubs" do 83 | it "returns nil" do 84 | WebMock.reset! 85 | expect(subject.request_stubs).to be nil 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/unit/stub_registry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::StubRegistry do 4 | 5 | before(:each) do 6 | WebMock::StubRegistry.instance.reset! 7 | @request_pattern = WebMock::RequestPattern.new(:get, "www.example.com") 8 | @request_signature = WebMock::RequestSignature.new(:get, "www.example.com") 9 | @request_stub = WebMock::RequestStub.new(:get, "www.example.com") 10 | end 11 | 12 | describe "remove_request_stub" do 13 | it "should remove stub from registry" do 14 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 15 | expect(WebMock::StubRegistry.instance.registered_request?(@request_signature)).to eq(@request_stub) 16 | WebMock::StubRegistry.instance.remove_request_stub(@request_stub) 17 | expect(WebMock::StubRegistry.instance.registered_request?(@request_signature)).to eq(nil) 18 | end 19 | end 20 | 21 | describe "reset!" do 22 | before(:each) do 23 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 24 | end 25 | 26 | it "should clean request stubs" do 27 | expect(WebMock::StubRegistry.instance.registered_request?(@request_signature)).to eq(@request_stub) 28 | WebMock::StubRegistry.instance.reset! 29 | expect(WebMock::StubRegistry.instance.registered_request?(@request_signature)).to eq(nil) 30 | end 31 | end 32 | 33 | describe "registering and reporting registered requests" do 34 | 35 | it "should return registered stub" do 36 | expect(WebMock::StubRegistry.instance.register_request_stub(@request_stub)).to eq(@request_stub) 37 | end 38 | 39 | it "should report if request stub is not registered" do 40 | expect(WebMock::StubRegistry.instance.registered_request?(@request_signature)).to eq(nil) 41 | end 42 | 43 | it "should register and report registered stub" do 44 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 45 | expect(WebMock::StubRegistry.instance.registered_request?(@request_signature)).to eq(@request_stub) 46 | end 47 | 48 | 49 | end 50 | 51 | describe "response for request" do 52 | 53 | it "should report registered evaluated response for request pattern" do 54 | @request_stub.to_return(body: "abc") 55 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 56 | expect(WebMock::StubRegistry.instance.response_for_request(@request_signature)). 57 | to eq(WebMock::Response.new(body: "abc")) 58 | end 59 | 60 | it "should report evaluated response" do 61 | @request_stub.to_return {|request| {body: request.method.to_s} } 62 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 63 | response1 = WebMock::StubRegistry.instance.response_for_request(@request_signature) 64 | expect(response1).to eq(WebMock::Response.new(body: "get")) 65 | end 66 | 67 | it "should report clone of the response" do 68 | @request_stub.to_return(body: lambda{|r| r.method.to_s}) 69 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 70 | response1 = WebMock::StubRegistry.instance.response_for_request(@request_signature) 71 | response2 = WebMock::StubRegistry.instance.response_for_request(@request_signature) 72 | expect(response1).not_to be(response2) 73 | end 74 | 75 | it "should report clone of the dynamic response" do 76 | @request_stub.to_return {|request| {body: request.method.to_s} } 77 | WebMock::StubRegistry.instance.register_request_stub(@request_stub) 78 | response1 = WebMock::StubRegistry.instance.response_for_request(@request_signature) 79 | response2 = WebMock::StubRegistry.instance.response_for_request(@request_signature) 80 | expect(response1).not_to be(response2) 81 | end 82 | 83 | it "should report nothing if no response for request is registered" do 84 | expect(WebMock::StubRegistry.instance.response_for_request(@request_signature)).to eq(nil) 85 | end 86 | 87 | it "should always return last registered matching response" do 88 | @request_stub1 = WebMock::RequestStub.new(:get, "www.example.com") 89 | @request_stub1.to_return(body: "abc") 90 | @request_stub2 = WebMock::RequestStub.new(:get, "www.example.com") 91 | @request_stub2.to_return(body: "def") 92 | @request_stub3 = WebMock::RequestStub.new(:get, "www.example.org") 93 | @request_stub3.to_return(body: "ghj") 94 | WebMock::StubRegistry.instance.register_request_stub(@request_stub1) 95 | WebMock::StubRegistry.instance.register_request_stub(@request_stub2) 96 | WebMock::StubRegistry.instance.register_request_stub(@request_stub3) 97 | expect(WebMock::StubRegistry.instance.response_for_request(@request_signature)). 98 | to eq(WebMock::Response.new(body: "def")) 99 | end 100 | 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /spec/unit/stub_request_snippet_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::StubRequestSnippet do 4 | describe "to_s" do 5 | describe "GET" do 6 | before(:each) do 7 | @request_signature = WebMock::RequestSignature.new(:get, "www.example.com/?a=b&c=d", headers: {}) 8 | end 9 | 10 | it "should print stub request snippet with url with params and method and empty successful response" do 11 | expected = %Q(stub_request(:get, "http://www.example.com/?a=b&c=d").\n to_return(status: 200, body: "", headers: {})) 12 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 13 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected) 14 | end 15 | 16 | it "should print stub request snippet with body if available" do 17 | @request_signature.body = "abcdef" 18 | expected = %Q(stub_request(:get, "http://www.example.com/?a=b&c=d").)+ 19 | "\n with(\n body: \"abcdef\")." + 20 | "\n to_return(status: 200, body: \"\", headers: {})" 21 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 22 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected) 23 | end 24 | 25 | it "should print stub request snippet with multiline body" do 26 | @request_signature.body = "abc\ndef" 27 | expected = %Q(stub_request(:get, "http://www.example.com/?a=b&c=d").)+ 28 | "\n with(\n body: \"abc\\ndef\")." + 29 | "\n to_return(status: 200, body: \"\", headers: {})" 30 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 31 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected) 32 | end 33 | 34 | it "should print stub request snippet with headers if any" do 35 | @request_signature.headers = {'B' => 'b', 'A' => 'a'} 36 | expected = 'stub_request(:get, "http://www.example.com/?a=b&c=d").'+ 37 | "\n with(\n headers: {\n\t\ 'A\'=>\'a\',\n\t \'B\'=>\'b\'\n })." + 38 | "\n to_return(status: 200, body: \"\", headers: {})" 39 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 40 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected) 41 | end 42 | 43 | it "should print stub request snippet with body and headers" do 44 | @request_signature.body = "abcdef" 45 | @request_signature.headers = {'B' => 'b', 'A' => 'a'} 46 | expected = 'stub_request(:get, "http://www.example.com/?a=b&c=d").'+ 47 | "\n with(\n body: \"abcdef\",\n headers: {\n\t \'A\'=>\'a\',\n\t \'B\'=>\'b\'\n })." + 48 | "\n to_return(status: 200, body: \"\", headers: {})" 49 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 50 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected) 51 | end 52 | 53 | it "should not print to_return part if not wanted" do 54 | expected = 'stub_request(:get, "http://www.example.com/").'+ 55 | "\n with(\n body: \"abcdef\")" 56 | stub = WebMock::RequestStub.new(:get, "www.example.com").with(body: "abcdef").to_return(body: "hello") 57 | expect(WebMock::StubRequestSnippet.new(stub).to_s(false)).to eq(expected) 58 | end 59 | end 60 | 61 | describe "POST" do 62 | let(:form_body) { 'user%5bfirst_name%5d=Bartosz' } 63 | let(:multipart_form_body) { 'complicated stuff--ABC123--goes here' } 64 | it "should print stub request snippet with body as a hash using rails conventions on form posts" do 65 | @request_signature = WebMock::RequestSignature.new(:post, "www.example.com", 66 | headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, 67 | body: form_body) 68 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 69 | expected = <<-STUB 70 | stub_request(:post, "http://www.example.com/"). 71 | with( 72 | body: #{{"user" => {"first_name" => "Bartosz"}}}, 73 | headers: { 74 | \t 'Content-Type'=>'application/x-www-form-urlencoded' 75 | }). 76 | to_return(status: 200, body: \"\", headers: {}) 77 | STUB 78 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected.strip) 79 | end 80 | 81 | it "should print stub request snippet leaving body as string when not a urlencoded form" do 82 | @request_signature = WebMock::RequestSignature.new(:post, "www.example.com", 83 | headers: {'Content-Type' => 'multipart/form-data; boundary=ABC123'}, 84 | body: multipart_form_body) 85 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 86 | expected = <<-STUB 87 | stub_request(:post, "http://www.example.com/"). 88 | with( 89 | body: "#{multipart_form_body}", 90 | headers: { 91 | \t 'Content-Type'=>'multipart/form-data; boundary=ABC123' 92 | }). 93 | to_return(status: 200, body: \"\", headers: {}) 94 | STUB 95 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected.strip) 96 | end 97 | 98 | it "should print stub request snippet with valid JSON body when request header contains 'Accept'=>'application/json' " do 99 | @request_signature = WebMock::RequestSignature.new(:post, "www.example.com", 100 | headers: {'Accept' => 'application/json'}) 101 | @request_stub = WebMock::RequestStub.from_request_signature(@request_signature) 102 | expected = <<-STUB 103 | stub_request(:post, "http://www.example.com/"). 104 | with( 105 | headers: { 106 | \t 'Accept'=>'application/json' 107 | }). 108 | to_return(status: 200, body: \"{}\", headers: {}) 109 | STUB 110 | expect(WebMock::StubRequestSnippet.new(@request_stub).to_s).to eq(expected.strip) 111 | end 112 | end 113 | 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/unit/util/hash_counter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::Util::HashCounter do 4 | 5 | it "should return 0 for non existing key" do 6 | expect(WebMock::Util::HashCounter.new.get(:abc)).to eq(0) 7 | end 8 | 9 | it "should increase the returned value on every put with the same key" do 10 | counter = WebMock::Util::HashCounter.new 11 | counter.put(:abc) 12 | expect(counter.get(:abc)).to eq(1) 13 | counter.put(:abc) 14 | expect(counter.get(:abc)).to eq(2) 15 | end 16 | 17 | it "should only increase value for given key provided to put" do 18 | counter = WebMock::Util::HashCounter.new 19 | counter.put(:abc) 20 | expect(counter.get(:abc)).to eq(1) 21 | expect(counter.get(:def)).to eq(0) 22 | end 23 | 24 | describe "each" do 25 | it "should provide elements in order of the last modified" do 26 | counter = WebMock::Util::HashCounter.new 27 | counter.put(:a) 28 | counter.put(:b) 29 | counter.put(:c) 30 | counter.put(:b) 31 | counter.put(:a) 32 | counter.put(:d) 33 | 34 | elements = [] 35 | counter.each {|k,v| elements << [k,v]} 36 | expect(elements).to eq([[:c, 1], [:b, 2], [:a, 2], [:d, 1]]) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/util/hash_keys_stringifier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::Util::HashKeysStringifier do 4 | 5 | it "should recursively stringify all symbol keys" do 6 | hash = { 7 | a: { 8 | b: [ 9 | { 10 | c: [{d: "1"}] 11 | } 12 | ] 13 | } 14 | } 15 | stringified = { 16 | 'a' => { 17 | 'b' => [ 18 | { 19 | 'c' => [{'d' => "1"}] 20 | } 21 | ] 22 | } 23 | } 24 | expect(WebMock::Util::HashKeysStringifier.stringify_keys!(hash, deep: true)).to eq(stringified) 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/util/headers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::Util::Headers do 4 | 5 | it "should decode_userinfo_from_header handles basic auth" do 6 | authorization_header = "Basic dXNlcm5hbWU6c2VjcmV0" 7 | userinfo = WebMock::Util::Headers.decode_userinfo_from_header(authorization_header) 8 | expect(userinfo).to eq("username:secret") 9 | end 10 | 11 | describe "sorted_headers_string" do 12 | 13 | it "should return nice string for hash with string values" do 14 | expect(WebMock::Util::Headers.sorted_headers_string({"a" => "b"})).to eq("{'A'=>'b'}") 15 | end 16 | 17 | it "should return nice string for hash with array values" do 18 | expect(WebMock::Util::Headers.sorted_headers_string({"a" => ["b", "c"]})).to eq("{'A'=>['b', 'c']}") 19 | end 20 | 21 | it "should return nice string for hash with array values and string values" do 22 | expect(WebMock::Util::Headers.sorted_headers_string({"a" => ["b", "c"], "d" => "e"})).to eq("{'A'=>['b', 'c'], 'D'=>'e'}") 23 | end 24 | 25 | 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/util/parsers/json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe WebMock::Util::Parsers::JSON do 4 | describe ".parse" do 5 | it "should parse json without parsing dates" do 6 | expect(described_class.parse("\"a\":\"2011-01-01\"")).to eq( 7 | {"a" => "2011-01-01"} 8 | ) 9 | end 10 | 11 | it "can parse json with multibyte characters" do 12 | expect(described_class.parse( 13 | "{\"name\":\"山田太郎\"\,\"job\":\"会社員\"}" 14 | )).to eq({"name" => "山田太郎", "job" => "会社員"}) 15 | end 16 | 17 | it "rescues ArgumentError's from YAML.load" do 18 | allow(YAML).to receive(:load).and_raise(ArgumentError) 19 | expect { 20 | described_class.parse("Bad JSON") 21 | }.to raise_error WebMock::Util::Parsers::ParseError 22 | end 23 | 24 | it "rescues Psych::SyntaxError's from YAML.load" do 25 | allow(YAML).to receive(:load).and_raise(Psych::SyntaxError) 26 | expect { 27 | described_class.parse("Bad JSON") 28 | }.to raise_error WebMock::Util::Parsers::ParseError 29 | end 30 | end 31 | 32 | describe ".convert_json_to_yaml" do 33 | it "parses multibyte characters" do 34 | expect(described_class.convert_json_to_yaml( 35 | "{\"name\":\"山田太郎\"\,\"job\":\"会社員\"}" 36 | )).to eq "{\"name\": \"山田太郎\", \"job\": \"会社員\"}" 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/webmock_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "WebMock" do 4 | 5 | describe ".version" do 6 | it "should report version" do 7 | expect(WebMock.version).to eq(WebMock::VERSION) 8 | end 9 | 10 | it "should not require safe_yaml" do 11 | expect(defined?SafeYAML).to eq(nil) 12 | end 13 | 14 | it "should alias enable_net_connect! to allow_net_connect!" do 15 | expect(WebMock.method(:enable_net_connect!)).to eq(WebMock.method(:allow_net_connect!)) 16 | end 17 | 18 | it "should alias disallow_net_connect! to disable_net_connect!" do 19 | expect(WebMock.method(:disallow_net_connect!)).to eq(WebMock.method(:disable_net_connect!)) 20 | end 21 | end 22 | 23 | describe ".net_connect_allowed?" do 24 | context 'enabled globally' do 25 | before do 26 | WebMock.enable_net_connect! 27 | end 28 | 29 | context 'without arguments' do 30 | it 'returns WebMock::Config.instance.allow_net_connect' do 31 | expect(WebMock.net_connect_allowed?).to eql(true) 32 | end 33 | end 34 | end 35 | 36 | context 'disabled with allowed remote string' do 37 | before do 38 | WebMock.disable_net_connect!(allow: "http://192.168.64.2:20031") 39 | end 40 | 41 | context 'without arguments' do 42 | it 'returns WebMock::Config.instance.allow_net_connect' do 43 | expect(WebMock.net_connect_allowed?).to eql(false) 44 | end 45 | end 46 | end 47 | 48 | context 'disabled globally' do 49 | before do 50 | WebMock.disable_net_connect! 51 | end 52 | 53 | context 'without arguments' do 54 | it 'returns WebMock::Config.instance.allow_net_connect' do 55 | expect(WebMock.net_connect_allowed?).to eql(false) 56 | end 57 | end 58 | end 59 | end 60 | 61 | describe ".net_http_connect_on_start?" do 62 | let(:uri) { Addressable::URI.parse("http://example.org:5432") } 63 | 64 | it "will not connect on start when false" do 65 | WebMock.disable_net_connect! 66 | expect(WebMock.net_http_connect_on_start?(uri)).to be(false) 67 | end 68 | 69 | it "will connect on start when true" do 70 | WebMock.disable_net_connect!(net_http_connect_on_start: true) 71 | expect(WebMock.net_http_connect_on_start?(uri)).to be(true) 72 | end 73 | 74 | it "will connect on start when regexp matches" do 75 | WebMock.disable_net_connect!(net_http_connect_on_start: /example/) 76 | expect(WebMock.net_http_connect_on_start?(uri)).to be(true) 77 | end 78 | 79 | it "will not connect on start when regexp does not match" do 80 | WebMock.disable_net_connect!(net_http_connect_on_start: /nope/) 81 | expect(WebMock.net_http_connect_on_start?(uri)).to be(false) 82 | end 83 | 84 | it "will connect on start when host matches" do 85 | WebMock.disable_net_connect!(net_http_connect_on_start: "example.org") 86 | expect(WebMock.net_http_connect_on_start?(uri)).to be(true) 87 | end 88 | 89 | it "will not connect on start when host does not match" do 90 | WebMock.disable_net_connect!(net_http_connect_on_start: "localhost") 91 | expect(WebMock.net_http_connect_on_start?(uri)).to be(false) 92 | end 93 | 94 | it "will connect on start when host + port matches" do 95 | WebMock.disable_net_connect!(net_http_connect_on_start: "example.org:5432") 96 | expect(WebMock.net_http_connect_on_start?(uri)).to be(true) 97 | end 98 | 99 | it "will not connect on start when host + port does not match" do 100 | WebMock.disable_net_connect!(net_http_connect_on_start: "example.org:80") 101 | expect(WebMock.net_http_connect_on_start?(uri)).to be(false) 102 | end 103 | 104 | it "will connect on start when scheme + host + port matches" do 105 | WebMock.disable_net_connect!(net_http_connect_on_start: "http://example.org:5432") 106 | expect(WebMock.net_http_connect_on_start?(uri)).to be(true) 107 | end 108 | 109 | it "will not connect on start when scheme + host + port does not match" do 110 | WebMock.disable_net_connect!(net_http_connect_on_start: "https://example.org:5432") 111 | expect(WebMock.net_http_connect_on_start?(uri)).to be(false) 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /test/http_request.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module HttpRequestTestHelper 4 | def http_request(method, uri, options = {}) 5 | begin 6 | uri = URI.parse(uri) 7 | rescue 8 | uri = Addressable::URI.heuristic_parse(uri) 9 | end 10 | response = nil 11 | clazz = ::Net::HTTP.const_get("#{method.to_s.capitalize}") 12 | req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", options[:headers]) 13 | req.basic_auth uri.user, uri.password if uri.user 14 | http = ::Net::HTTP.new(uri.host, uri.port) 15 | http.use_ssl = true if uri.scheme == "https" 16 | response = http.start {|http| 17 | http.request(req, options[:body]) 18 | } 19 | OpenStruct.new({ 20 | body: response.body, 21 | headers: response, 22 | status: response.code }) 23 | end 24 | end -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 4 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 5 | 6 | require 'webmock/test_unit' 7 | require 'test/unit' 8 | 9 | class Test::Unit::TestCase 10 | AssertionFailedError = Test::Unit::AssertionFailedError rescue MiniTest::Assertion 11 | def assert_raise_with_message(e, message, &block) 12 | e = assert_raises(e, &block) 13 | if message.is_a?(Regexp) 14 | assert_match(message, e.message) 15 | else 16 | assert_equal(message, e.message) 17 | end 18 | end 19 | 20 | def assert_fail(message, &block) 21 | assert_raise_with_message(AssertionFailedError, message, &block) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/test_webmock.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/test_helper') 2 | require File.expand_path(File.dirname(__FILE__) + '/shared_test') 3 | 4 | class TestWebMock < Test::Unit::TestCase 5 | include SharedTest 6 | 7 | def teardown 8 | # Ensure global Test::Unit teardown was called 9 | assert_empty WebMock::RequestRegistry.instance.requested_signatures.hash 10 | assert_empty WebMock::StubRegistry.instance.request_stubs 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /webmock.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path('../lib', __FILE__) 2 | require 'webmock/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'webmock' 6 | s.version = WebMock::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ['Bartosz Blimke'] 9 | s.email = ['bartosz.blimke@gmail.com'] 10 | s.homepage = 'https://github.com/bblimke/webmock' 11 | s.summary = %q{Library for stubbing HTTP requests in Ruby.} 12 | s.description = %q{WebMock allows stubbing HTTP requests and setting expectations on HTTP requests.} 13 | s.license = "MIT" 14 | 15 | s.metadata = { 16 | 'bug_tracker_uri' => 'https://github.com/bblimke/webmock/issues', 17 | 'changelog_uri' => "https://github.com/bblimke/webmock/blob/v#{s.version}/CHANGELOG.md", 18 | 'documentation_uri' => "https://www.rubydoc.info/gems/webmock/#{s.version}", 19 | 'source_code_uri' => "https://github.com/bblimke/webmock/tree/v#{s.version}", 20 | 'wiki_uri' => 'https://github.com/bblimke/webmock/wiki' 21 | } 22 | 23 | s.required_ruby_version = '>= 2.6' 24 | 25 | s.add_dependency 'addressable', '>= 2.8.0' 26 | s.add_dependency 'crack', '>= 0.3.2' 27 | s.add_dependency 'hashdiff', ['>= 0.4.0', '< 2.0.0'] 28 | 29 | unless RUBY_PLATFORM =~ /java/ 30 | s.add_development_dependency 'patron', '>= 0.4.18' 31 | s.add_development_dependency 'curb', '>= 0.7.16' 32 | s.add_development_dependency 'typhoeus', '>= 0.5.0' 33 | s.add_development_dependency 'em-http-request', '>= 1.0.2' 34 | s.add_development_dependency 'em-synchrony', '>= 1.0.0' 35 | s.add_development_dependency 'async-http', '>= 0.48.0' 36 | end 37 | 38 | s.add_development_dependency 'http', '>= 0.8.0' 39 | s.add_development_dependency 'manticore', '>= 0.5.1' if RUBY_PLATFORM =~ /java/ 40 | s.add_development_dependency 'rack', '> 1.6' 41 | s.add_development_dependency 'rspec', '>= 3.1.0' 42 | s.add_development_dependency 'httpclient', '>= 2.2.4' 43 | s.add_development_dependency 'mutex_m' # For Ruby 3.4 compatibility until this commit of httpclient is released https://github.com/nahi/httpclient/commit/552a56770689e800ad11a21cd06075064736569f 44 | s.add_development_dependency 'excon', '>= 0.27.5' 45 | s.add_development_dependency 'minitest', '>= 5.0.0' 46 | s.add_development_dependency 'test-unit', '>= 3.0.0' 47 | s.add_development_dependency 'rdoc', '> 3.5.0' 48 | s.add_development_dependency 'webrick' 49 | s.add_development_dependency 'rspec-retry' 50 | 51 | s.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "lib/**/*"] 52 | s.require_path = "lib" 53 | end 54 | --------------------------------------------------------------------------------