vcr 2.0.0.beta1 → 2.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +37 -2
- data/Gemfile +2 -2
- data/README.md +10 -1
- data/Rakefile +43 -7
- data/Upgrade.md +45 -0
- data/features/.nav +1 -0
- data/features/cassettes/automatic_re_recording.feature +19 -17
- data/features/cassettes/dynamic_erb.feature +32 -28
- data/features/cassettes/exclusive.feature +28 -24
- data/features/cassettes/format.feature +213 -31
- data/features/cassettes/update_content_length_header.feature +20 -18
- data/features/configuration/filter_sensitive_data.feature +4 -4
- data/features/configuration/hooks.feature +27 -23
- data/features/http_libraries/em_http_request.feature +79 -75
- data/features/record_modes/all.feature +14 -14
- data/features/record_modes/new_episodes.feature +15 -15
- data/features/record_modes/none.feature +15 -15
- data/features/record_modes/once.feature +15 -15
- data/features/request_matching/body.feature +25 -23
- data/features/request_matching/custom_matcher.feature +25 -23
- data/features/request_matching/headers.feature +32 -36
- data/features/request_matching/host.feature +27 -25
- data/features/request_matching/identical_request_sequence.feature +27 -25
- data/features/request_matching/method.feature +27 -25
- data/features/request_matching/path.feature +27 -25
- data/features/request_matching/playback_repeats.feature +27 -25
- data/features/request_matching/uri.feature +27 -25
- data/features/request_matching/uri_without_param.feature +28 -26
- data/features/step_definitions/cli_steps.rb +71 -17
- data/features/support/env.rb +3 -1
- data/features/support/http_lib_filters.rb +6 -3
- data/features/support/vcr_cucumber_helpers.rb +4 -2
- data/lib/vcr.rb +6 -2
- data/lib/vcr/cassette.rb +75 -51
- data/lib/vcr/cassette/migrator.rb +111 -0
- data/lib/vcr/cassette/serializers.rb +35 -0
- data/lib/vcr/cassette/serializers/json.rb +23 -0
- data/lib/vcr/cassette/serializers/psych.rb +24 -0
- data/lib/vcr/cassette/serializers/syck.rb +35 -0
- data/lib/vcr/cassette/serializers/yaml.rb +24 -0
- data/lib/vcr/configuration.rb +6 -1
- data/lib/vcr/errors.rb +1 -1
- data/lib/vcr/library_hooks/excon.rb +1 -7
- data/lib/vcr/library_hooks/typhoeus.rb +6 -22
- data/lib/vcr/library_hooks/webmock.rb +1 -1
- data/lib/vcr/middleware/faraday.rb +1 -1
- data/lib/vcr/request_matcher_registry.rb +43 -30
- data/lib/vcr/structs.rb +209 -0
- data/lib/vcr/tasks/vcr.rake +9 -0
- data/lib/vcr/version.rb +1 -1
- data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
- data/spec/fixtures/cassette_spec/example.yml +79 -78
- data/spec/fixtures/cassette_spec/with_localhost_requests.yml +79 -77
- data/spec/fixtures/fake_example.com_responses.yml +78 -76
- data/spec/fixtures/match_requests_on.yml +147 -145
- data/spec/monkey_patches.rb +5 -5
- data/spec/support/http_library_adapters.rb +48 -0
- data/spec/support/shared_example_groups/hook_into_http_library.rb +53 -20
- data/spec/support/sinatra_app.rb +12 -0
- data/spec/vcr/cassette/http_interaction_list_spec.rb +1 -1
- data/spec/vcr/cassette/migrator_spec.rb +183 -0
- data/spec/vcr/cassette/serializers_spec.rb +122 -0
- data/spec/vcr/cassette_spec.rb +147 -83
- data/spec/vcr/configuration_spec.rb +11 -1
- data/spec/vcr/library_hooks/typhoeus_spec.rb +3 -3
- data/spec/vcr/library_hooks/webmock_spec.rb +7 -1
- data/spec/vcr/request_ignorer_spec.rb +1 -1
- data/spec/vcr/request_matcher_registry_spec.rb +46 -4
- data/spec/vcr/structs_spec.rb +309 -0
- data/spec/vcr_spec.rb +7 -0
- data/vcr.gemspec +9 -12
- metadata +75 -61
- data/lib/vcr/structs/http_interaction.rb +0 -58
- data/lib/vcr/structs/normalizers/body.rb +0 -24
- data/lib/vcr/structs/normalizers/header.rb +0 -64
- data/lib/vcr/structs/normalizers/status_message.rb +0 -17
- data/lib/vcr/structs/normalizers/uri.rb +0 -34
- data/lib/vcr/structs/request.rb +0 -13
- data/lib/vcr/structs/response.rb +0 -13
- data/lib/vcr/structs/response_status.rb +0 -5
- data/lib/vcr/util/yaml.rb +0 -11
- data/spec/support/shared_example_groups/normalizers.rb +0 -94
- data/spec/vcr/structs/http_interaction_spec.rb +0 -89
- data/spec/vcr/structs/request_spec.rb +0 -39
- data/spec/vcr/structs/response_spec.rb +0 -44
- data/spec/vcr/structs/response_status_spec.rb +0 -9
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'vcr/structs'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module VCR
|
6
|
+
class Cassette
|
7
|
+
class Migrator
|
8
|
+
def initialize(dir, out = $stdout)
|
9
|
+
@dir, @out = dir, out
|
10
|
+
@yaml_load_errors = yaml_load_errors
|
11
|
+
end
|
12
|
+
|
13
|
+
def migrate!
|
14
|
+
@out.puts "Migrating VCR cassettes in #{@dir}..."
|
15
|
+
Dir["#{@dir}/**/*.yml"].each do |cassette|
|
16
|
+
migrate_cassette(cassette)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def migrate_cassette(cassette)
|
23
|
+
unless http_interactions = load_yaml(cassette)
|
24
|
+
@out.puts " - Ignored #{relative_casssette_name(cassette)} since it could not be parsed as YAML (does it have some ERB?)"
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
unless valid_vcr_1_cassette?(http_interactions)
|
29
|
+
@out.puts " - Ignored #{relative_casssette_name(cassette)} since it does not appear to be a valid VCR 1.x cassette"
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
http_interactions.map! do |interaction|
|
34
|
+
interaction.recorded_at = File.mtime(cassette)
|
35
|
+
remove_unnecessary_standard_port(interaction)
|
36
|
+
denormalize_http_header_keys(interaction.request)
|
37
|
+
denormalize_http_header_keys(interaction.response)
|
38
|
+
normalize_body(interaction.request)
|
39
|
+
normalize_body(interaction.response)
|
40
|
+
interaction.to_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
hash = {
|
44
|
+
"http_interactions" => http_interactions,
|
45
|
+
"recorded_with" => "VCR 1.11.3" # assume the last 1.x release
|
46
|
+
}
|
47
|
+
|
48
|
+
def hash.each
|
49
|
+
yield 'http_interactions', self['http_interactions']
|
50
|
+
yield 'recorded_with', self['recorded_with']
|
51
|
+
end
|
52
|
+
|
53
|
+
File.open(cassette, 'w') { |f| f.write ::YAML.dump(hash) }
|
54
|
+
@out.puts " - Migrated #{relative_casssette_name(cassette)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_yaml(cassette)
|
58
|
+
::YAML.load_file(cassette)
|
59
|
+
rescue *@yaml_load_errors
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def yaml_load_errors
|
64
|
+
[ArgumentError].tap do |errors|
|
65
|
+
errors << Psych::SyntaxError if defined?(Psych::SyntaxError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def relative_casssette_name(cassette)
|
70
|
+
cassette.gsub(%r|\A#{Regexp.escape(@dir)}/?|, '')
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_vcr_1_cassette?(content)
|
74
|
+
content.is_a?(Array) &&
|
75
|
+
content.map(&:class).uniq == [HTTPInteraction]
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_unnecessary_standard_port(interaction)
|
79
|
+
uri = URI(interaction.request.uri)
|
80
|
+
if uri.scheme == 'http' && uri.port == 80 ||
|
81
|
+
uri.scheme == 'https' && uri.port == 443
|
82
|
+
uri.port = nil
|
83
|
+
interaction.request.uri = uri.to_s
|
84
|
+
end
|
85
|
+
rescue URI::InvalidURIError
|
86
|
+
# ignore this URI.
|
87
|
+
# This can occur when the user uses the filter_sensitive_data option
|
88
|
+
# to put a substitution string in their URI
|
89
|
+
end
|
90
|
+
|
91
|
+
def denormalize_http_header_keys(object)
|
92
|
+
object.headers = {}.tap do |denormalized|
|
93
|
+
object.headers.each do |k, v|
|
94
|
+
denormalized[denormalize_header_key(k)] = v
|
95
|
+
end if object.headers
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def denormalize_header_key(key)
|
100
|
+
key.split('-'). # 'user-agent' => %w(user agent)
|
101
|
+
each { |w| w.capitalize! }. # => %w(User Agent)
|
102
|
+
join('-')
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_body(object)
|
106
|
+
object.body = '' if object.body.nil?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module VCR
|
2
|
+
class Cassette
|
3
|
+
class Serializers
|
4
|
+
autoload :YAML, 'vcr/cassette/serializers/yaml'
|
5
|
+
autoload :Syck, 'vcr/cassette/serializers/syck'
|
6
|
+
autoload :Psych, 'vcr/cassette/serializers/psych'
|
7
|
+
autoload :JSON, 'vcr/cassette/serializers/json'
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@serializers = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](name)
|
14
|
+
@serializers.fetch(name) do |_|
|
15
|
+
@serializers[name] = case name
|
16
|
+
when :yaml then YAML
|
17
|
+
when :syck then Syck
|
18
|
+
when :psych then Psych
|
19
|
+
when :json then JSON
|
20
|
+
else raise ArgumentError.new("The requested VCR cassette serializer (#{name.inspect}) is not registered.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(name, value)
|
26
|
+
if @serializers.has_key?(name)
|
27
|
+
warn "WARNING: There is already a VCR cassette serializer registered for #{name.inspect}. Overriding it."
|
28
|
+
end
|
29
|
+
|
30
|
+
@serializers[name] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
class Cassette
|
5
|
+
class Serializers
|
6
|
+
module JSON
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def file_extension
|
10
|
+
"json"
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(hash)
|
14
|
+
MultiJson.encode(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(string)
|
18
|
+
MultiJson.decode(string)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'psych'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
class Cassette
|
5
|
+
class Serializers
|
6
|
+
module Psych
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def file_extension
|
10
|
+
"yml"
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(hash)
|
14
|
+
::Psych.dump(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(string)
|
18
|
+
::Psych.load(string)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
class Cassette
|
5
|
+
class Serializers
|
6
|
+
module Syck
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def file_extension
|
10
|
+
"yml"
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(hash)
|
14
|
+
using_syck { ::YAML.dump(hash) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(string)
|
18
|
+
using_syck { ::YAML.load(string) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def using_syck
|
22
|
+
return yield unless defined?(::YAML::ENGINE)
|
23
|
+
original_engine = ::YAML::ENGINE.yamler
|
24
|
+
::YAML::ENGINE.yamler = 'syck'
|
25
|
+
|
26
|
+
begin
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
::YAML::ENGINE.yamler = original_engine
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
class Cassette
|
5
|
+
class Serializers
|
6
|
+
module YAML
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def file_extension
|
10
|
+
"yml"
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(hash)
|
14
|
+
::YAML.dump(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(string)
|
18
|
+
::YAML.load(string)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
data/lib/vcr/configuration.rb
CHANGED
@@ -14,7 +14,8 @@ module VCR
|
|
14
14
|
@allow_http_connections_when_no_cassette = nil
|
15
15
|
@default_cassette_options = {
|
16
16
|
:record => :once,
|
17
|
-
:match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS
|
17
|
+
:match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
|
18
|
+
:serialize_with => :yaml
|
18
19
|
}
|
19
20
|
end
|
20
21
|
|
@@ -62,6 +63,10 @@ module VCR
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
def cassette_serializers
|
67
|
+
VCR.cassette_serializers
|
68
|
+
end
|
69
|
+
|
65
70
|
private
|
66
71
|
|
67
72
|
def load_library_hook(hook)
|
data/lib/vcr/errors.rb
CHANGED
@@ -6,7 +6,7 @@ module VCR
|
|
6
6
|
class MissingERBVariableError < Error; end
|
7
7
|
class LibraryVersionTooLowError < Error; end
|
8
8
|
class UnregisteredMatcherError < Error; end
|
9
|
-
|
9
|
+
class InvalidCassetteFormatError < Error; end
|
10
10
|
|
11
11
|
class HTTPConnectionNotAllowedError < Error
|
12
12
|
def initialize(request)
|
@@ -112,17 +112,11 @@ module VCR
|
|
112
112
|
normalized = {}
|
113
113
|
headers.each do |k, v|
|
114
114
|
v = v.join(', ') if v.respond_to?(:join)
|
115
|
-
normalized[
|
115
|
+
normalized[k] = v
|
116
116
|
end
|
117
117
|
normalized
|
118
118
|
end
|
119
119
|
|
120
|
-
def normalize_header_key(key)
|
121
|
-
key.split('-'). # 'user-agent' => %w(user agent)
|
122
|
-
each { |w| w.capitalize! }. # => %w(User Agent)
|
123
|
-
join('-')
|
124
|
-
end
|
125
|
-
|
126
120
|
::Excon.stub({}) do |params|
|
127
121
|
self.new(params).handle
|
128
122
|
end
|
@@ -2,7 +2,7 @@ require 'vcr/util/version_checker'
|
|
2
2
|
require 'vcr/request_handler'
|
3
3
|
require 'typhoeus'
|
4
4
|
|
5
|
-
VCR::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.2
|
5
|
+
VCR::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.3.2', '0.3').check_version!
|
6
6
|
|
7
7
|
module VCR
|
8
8
|
class LibraryHooks
|
@@ -35,16 +35,12 @@ module VCR
|
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
|
-
def on_stubbed_request
|
39
|
-
hydra_mock
|
40
|
-
end
|
41
|
-
|
42
38
|
def vcr_request
|
43
39
|
@vcr_request ||= vcr_request_from(request)
|
44
40
|
end
|
45
41
|
|
46
|
-
def
|
47
|
-
|
42
|
+
def on_stubbed_request
|
43
|
+
::Typhoeus::Response.new \
|
48
44
|
:http_version => stubbed_response.http_version,
|
49
45
|
:code => stubbed_response.status.code,
|
50
46
|
:status_message => stubbed_response.status.message,
|
@@ -52,12 +48,6 @@ module VCR
|
|
52
48
|
:body => stubbed_response.body
|
53
49
|
end
|
54
50
|
|
55
|
-
def hydra_mock
|
56
|
-
@hydra_mock ||= ::Typhoeus::HydraMock.new(/.*/, :any).tap do |m|
|
57
|
-
m.and_return(typhoeus_response)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
51
|
def stubbed_response_headers
|
62
52
|
@stubbed_response_headers ||= {}.tap do |hash|
|
63
53
|
stubbed_response.headers.each do |key, values|
|
@@ -75,19 +65,13 @@ module VCR
|
|
75
65
|
end
|
76
66
|
end
|
77
67
|
|
68
|
+
::Typhoeus::Hydra.register_stub_finder do |request|
|
69
|
+
VCR::LibraryHooks::Typhoeus::RequestHandler.new(request).handle
|
70
|
+
end
|
78
71
|
end
|
79
72
|
end
|
80
73
|
end
|
81
74
|
|
82
|
-
# TODO: add Typhoeus::Hydra.register_stub_finder API to Typhoeus
|
83
|
-
# so we can use that instead of monkey-patching it.
|
84
|
-
Typhoeus::Hydra::Stubbing::SharedMethods.class_eval do
|
85
|
-
undef find_stub_from_request
|
86
|
-
def find_stub_from_request(request)
|
87
|
-
VCR::LibraryHooks::Typhoeus::RequestHandler.new(request).handle
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
75
|
VCR.configuration.after_library_hooks_loaded do
|
92
76
|
# ensure WebMock's Typhoeus adapter does not conflict with us here
|
93
77
|
# (i.e. to double record requests or whatever).
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'vcr/errors'
|
2
|
+
|
1
3
|
module VCR
|
2
4
|
class RequestMatcherRegistry
|
3
5
|
DEFAULT_MATCHERS = [:method, :uri]
|
@@ -8,6 +10,33 @@ module VCR
|
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
13
|
+
class URIWithoutParamsMatcher < Struct.new(:params_to_ignore)
|
14
|
+
def partial_uri_from(request)
|
15
|
+
URI(request.uri).tap do |uri|
|
16
|
+
break unless uri.query # ignore uris without params, e.g. "http://example.com/"
|
17
|
+
|
18
|
+
uri.query = uri.query.split('&').tap { |params|
|
19
|
+
params.map! do |p|
|
20
|
+
key, value = p.split('=')
|
21
|
+
key.gsub!(/\[\]\z/, '') # handle params like tag[]=
|
22
|
+
[key, value]
|
23
|
+
end
|
24
|
+
|
25
|
+
params.reject! { |p| params_to_ignore.include?(p.first) }
|
26
|
+
params.map! { |p| p.join('=') }
|
27
|
+
}.join('&')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(request_1, request_2)
|
32
|
+
partial_uri_from(request_1) == partial_uri_from(request_2)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_proc
|
36
|
+
lambda { |r1, r2| call(r1, r2) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
11
40
|
def initialize
|
12
41
|
@registry = {}
|
13
42
|
register_built_ins
|
@@ -30,31 +59,19 @@ module VCR
|
|
30
59
|
end
|
31
60
|
|
32
61
|
def uri_without_params(*ignores)
|
33
|
-
ignores
|
34
|
-
|
35
|
-
lambda do |request_1, request_2|
|
36
|
-
uri_1, uri_2 = [request_1, request_2].map do |r|
|
37
|
-
URI(r.uri).tap do |uri|
|
38
|
-
uri.query = uri.query.split('&').tap { |params|
|
39
|
-
params.map! do |p|
|
40
|
-
key, value = p.split('=')
|
41
|
-
key.gsub!(/\[\]\z/, '') # handle params like tag[]=
|
42
|
-
[key, value]
|
43
|
-
end
|
44
|
-
|
45
|
-
params.reject! { |p| ignores.include?(p.first) }
|
46
|
-
params.map! { |p| p.join('=') }
|
47
|
-
}.join('&')
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
uri_1 == uri_2
|
52
|
-
end
|
62
|
+
uri_without_param_matchers[ignores]
|
53
63
|
end
|
54
64
|
alias uri_without_param uri_without_params
|
55
65
|
|
56
66
|
private
|
57
67
|
|
68
|
+
def uri_without_param_matchers
|
69
|
+
@uri_without_param_matchers ||= Hash.new do |hash, params|
|
70
|
+
params = params.map(&:to_s)
|
71
|
+
hash[params] = URIWithoutParamsMatcher.new(params)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
58
75
|
def raise_unregistered_matcher_error(name)
|
59
76
|
raise Errors::UnregisteredMatcherError.new \
|
60
77
|
"There is no matcher registered for #{name.inspect}. " +
|
@@ -63,22 +80,18 @@ module VCR
|
|
63
80
|
|
64
81
|
def register_built_ins
|
65
82
|
register(:method) { |r1, r2| r1.method == r2.method }
|
66
|
-
register(:uri) { |r1, r2|
|
83
|
+
register(:uri) { |r1, r2| without_standard_port(r1.uri) == without_standard_port(r2.uri) }
|
67
84
|
register(:host) { |r1, r2| URI(r1.uri).host == URI(r2.uri).host }
|
68
85
|
register(:path) { |r1, r2| URI(r1.uri).path == URI(r2.uri).path }
|
69
86
|
register(:body) { |r1, r2| r1.body == r2.body }
|
70
87
|
register(:headers) { |r1, r2| r1.headers == r2.headers }
|
71
88
|
end
|
72
89
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
# Faraday normalizes URIs by replacing '+' with '%20'
|
79
|
-
uri.gsub('+', '%20')
|
80
|
-
else
|
81
|
-
uri
|
90
|
+
def without_standard_port(uri)
|
91
|
+
URI(uri).tap do |u|
|
92
|
+
if [['http', 80], ['https', 443]].include?([u.scheme, u.port])
|
93
|
+
u.port = nil
|
94
|
+
end
|
82
95
|
end
|
83
96
|
end
|
84
97
|
end
|