faraday 0.9.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +360 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +28 -195
  5. data/Rakefile +4 -68
  6. data/examples/client_spec.rb +97 -0
  7. data/examples/client_test.rb +118 -0
  8. data/lib/faraday/adapter/test.rb +158 -58
  9. data/lib/faraday/adapter/typhoeus.rb +7 -115
  10. data/lib/faraday/adapter.rb +79 -20
  11. data/lib/faraday/adapter_registry.rb +30 -0
  12. data/lib/faraday/autoload.rb +39 -36
  13. data/lib/faraday/connection.rb +378 -168
  14. data/lib/faraday/dependency_loader.rb +37 -0
  15. data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  16. data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
  17. data/lib/faraday/error.rb +128 -29
  18. data/lib/faraday/file_part.rb +128 -0
  19. data/lib/faraday/logging/formatter.rb +105 -0
  20. data/lib/faraday/methods.rb +6 -0
  21. data/lib/faraday/middleware.rb +19 -25
  22. data/lib/faraday/middleware_registry.rb +129 -0
  23. data/lib/faraday/options/connection_options.rb +22 -0
  24. data/lib/faraday/options/env.rb +181 -0
  25. data/lib/faraday/options/proxy_options.rb +32 -0
  26. data/lib/faraday/options/request_options.rb +22 -0
  27. data/lib/faraday/options/ssl_options.rb +59 -0
  28. data/lib/faraday/options.rb +61 -193
  29. data/lib/faraday/param_part.rb +53 -0
  30. data/lib/faraday/parameters.rb +4 -180
  31. data/lib/faraday/rack_builder.rb +84 -47
  32. data/lib/faraday/request/authorization.rb +51 -31
  33. data/lib/faraday/request/basic_authentication.rb +14 -7
  34. data/lib/faraday/request/instrumentation.rb +45 -27
  35. data/lib/faraday/request/multipart.rb +88 -45
  36. data/lib/faraday/request/retry.rb +212 -121
  37. data/lib/faraday/request/token_authentication.rb +15 -10
  38. data/lib/faraday/request/url_encoded.rb +43 -23
  39. data/lib/faraday/request.rb +96 -32
  40. data/lib/faraday/response/logger.rb +22 -48
  41. data/lib/faraday/response/raise_error.rb +49 -14
  42. data/lib/faraday/response.rb +33 -25
  43. data/lib/faraday/utils/headers.rb +139 -0
  44. data/lib/faraday/utils/params_hash.rb +61 -0
  45. data/lib/faraday/utils.rb +38 -218
  46. data/lib/faraday/version.rb +5 -0
  47. data/lib/faraday.rb +130 -213
  48. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  49. data/spec/faraday/adapter/em_http_spec.rb +49 -0
  50. data/spec/faraday/adapter/em_synchrony_spec.rb +18 -0
  51. data/spec/faraday/adapter/excon_spec.rb +49 -0
  52. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  53. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  54. data/spec/faraday/adapter/patron_spec.rb +18 -0
  55. data/spec/faraday/adapter/rack_spec.rb +8 -0
  56. data/spec/faraday/adapter/test_spec.rb +377 -0
  57. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  58. data/spec/faraday/adapter_registry_spec.rb +28 -0
  59. data/spec/faraday/adapter_spec.rb +55 -0
  60. data/spec/faraday/composite_read_io_spec.rb +80 -0
  61. data/spec/faraday/connection_spec.rb +736 -0
  62. data/spec/faraday/error_spec.rb +60 -0
  63. data/spec/faraday/middleware_spec.rb +52 -0
  64. data/spec/faraday/options/env_spec.rb +70 -0
  65. data/spec/faraday/options/options_spec.rb +297 -0
  66. data/spec/faraday/options/proxy_options_spec.rb +44 -0
  67. data/spec/faraday/options/request_options_spec.rb +19 -0
  68. data/spec/faraday/params_encoders/flat_spec.rb +42 -0
  69. data/spec/faraday/params_encoders/nested_spec.rb +142 -0
  70. data/spec/faraday/rack_builder_spec.rb +345 -0
  71. data/spec/faraday/request/authorization_spec.rb +96 -0
  72. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  73. data/spec/faraday/request/multipart_spec.rb +302 -0
  74. data/spec/faraday/request/retry_spec.rb +242 -0
  75. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  76. data/spec/faraday/request_spec.rb +120 -0
  77. data/spec/faraday/response/logger_spec.rb +220 -0
  78. data/spec/faraday/response/middleware_spec.rb +68 -0
  79. data/spec/faraday/response/raise_error_spec.rb +169 -0
  80. data/spec/faraday/response_spec.rb +75 -0
  81. data/spec/faraday/utils/headers_spec.rb +82 -0
  82. data/spec/faraday/utils_spec.rb +56 -0
  83. data/spec/faraday_spec.rb +37 -0
  84. data/spec/spec_helper.rb +132 -0
  85. data/spec/support/disabling_stub.rb +14 -0
  86. data/spec/support/fake_safe_buffer.rb +15 -0
  87. data/spec/support/helper_methods.rb +133 -0
  88. data/spec/support/shared_examples/adapter.rb +105 -0
  89. data/spec/support/shared_examples/params_encoder.rb +18 -0
  90. data/spec/support/shared_examples/request_method.rb +262 -0
  91. data/spec/support/streaming_response_checker.rb +35 -0
  92. data/spec/support/webmock_rack_app.rb +68 -0
  93. metadata +199 -98
  94. data/.document +0 -6
  95. data/CONTRIBUTING.md +0 -36
  96. data/Gemfile +0 -25
  97. data/faraday.gemspec +0 -34
  98. data/lib/faraday/adapter/em_http.rb +0 -237
  99. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -56
  100. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -66
  101. data/lib/faraday/adapter/em_synchrony.rb +0 -92
  102. data/lib/faraday/adapter/excon.rb +0 -80
  103. data/lib/faraday/adapter/httpclient.rb +0 -106
  104. data/lib/faraday/adapter/net_http.rb +0 -130
  105. data/lib/faraday/adapter/net_http_persistent.rb +0 -48
  106. data/lib/faraday/adapter/patron.rb +0 -72
  107. data/lib/faraday/adapter/rack.rb +0 -58
  108. data/lib/faraday/upload_io.rb +0 -67
  109. data/script/cached-bundle +0 -46
  110. data/script/console +0 -7
  111. data/script/generate_certs +0 -42
  112. data/script/package +0 -7
  113. data/script/proxy-server +0 -42
  114. data/script/release +0 -17
  115. data/script/s3-put +0 -71
  116. data/script/server +0 -36
  117. data/script/test +0 -172
  118. data/test/adapters/default_test.rb +0 -14
  119. data/test/adapters/em_http_test.rb +0 -20
  120. data/test/adapters/em_synchrony_test.rb +0 -20
  121. data/test/adapters/excon_test.rb +0 -20
  122. data/test/adapters/httpclient_test.rb +0 -21
  123. data/test/adapters/integration.rb +0 -254
  124. data/test/adapters/logger_test.rb +0 -82
  125. data/test/adapters/net_http_persistent_test.rb +0 -20
  126. data/test/adapters/net_http_test.rb +0 -14
  127. data/test/adapters/patron_test.rb +0 -20
  128. data/test/adapters/rack_test.rb +0 -31
  129. data/test/adapters/test_middleware_test.rb +0 -114
  130. data/test/adapters/typhoeus_test.rb +0 -28
  131. data/test/authentication_middleware_test.rb +0 -65
  132. data/test/composite_read_io_test.rb +0 -111
  133. data/test/connection_test.rb +0 -522
  134. data/test/env_test.rb +0 -218
  135. data/test/helper.rb +0 -81
  136. data/test/live_server.rb +0 -67
  137. data/test/middleware/instrumentation_test.rb +0 -88
  138. data/test/middleware/retry_test.rb +0 -177
  139. data/test/middleware_stack_test.rb +0 -173
  140. data/test/multibyte.txt +0 -1
  141. data/test/options_test.rb +0 -252
  142. data/test/parameters_test.rb +0 -64
  143. data/test/request_middleware_test.rb +0 -142
  144. data/test/response_middleware_test.rb +0 -72
  145. data/test/strawberry.rb +0 -2
  146. data/test/utils_test.rb +0 -58
data/Rakefile CHANGED
@@ -1,71 +1,7 @@
1
- require 'date'
2
- require 'fileutils'
3
- require 'openssl'
4
- require 'rake/testtask'
5
- require 'bundler'
6
- Bundler::GemHelper.install_tasks
1
+ # frozen_string_literal: true
7
2
 
8
- task :default => :test
3
+ require 'rspec/core/rake_task'
9
4
 
10
- ## helper functions
5
+ RSpec::Core::RakeTask.new(:spec)
11
6
 
12
- def name
13
- @name ||= Dir['*.gemspec'].first.split('.').first
14
- end
15
-
16
- def version
17
- line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
18
- line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
19
- end
20
-
21
- def gemspec_file
22
- "#{name}.gemspec"
23
- end
24
-
25
- def gem_file
26
- "#{name}-#{version}.gem"
27
- end
28
-
29
- def replace_header(head, header_name)
30
- head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
31
- end
32
-
33
- # Adapted from WEBrick::Utils. Skips cert extensions so it
34
- # can be used as a CA bundle
35
- def create_self_signed_cert(bits, cn, comment)
36
- rsa = OpenSSL::PKey::RSA.new(bits)
37
- cert = OpenSSL::X509::Certificate.new
38
- cert.version = 2
39
- cert.serial = 1
40
- name = OpenSSL::X509::Name.new(cn)
41
- cert.subject = name
42
- cert.issuer = name
43
- cert.not_before = Time.now
44
- cert.not_after = Time.now + (365*24*60*60)
45
- cert.public_key = rsa.public_key
46
- cert.sign(rsa, OpenSSL::Digest::SHA1.new)
47
- return [cert, rsa]
48
- end
49
-
50
- ## standard tasks
51
-
52
- desc "Run all tests"
53
- task :test do
54
- exec 'script/test'
55
- end
56
-
57
- desc "Generate certificates for SSL tests"
58
- task :'test:generate_certs' do
59
- cert, key = create_self_signed_cert(1024, [['CN', 'localhost']], 'Faraday Test CA')
60
- FileUtils.mkdir_p 'tmp'
61
- File.open('tmp/faraday-cert.key', 'w') {|f| f.puts(key) }
62
- File.open('tmp/faraday-cert.crt', 'w') {|f| f.puts(cert.to_s) }
63
- end
64
-
65
- file 'tmp/faraday-cert.key' => :'test:generate_certs'
66
- file 'tmp/faraday-cert.crt' => :'test:generate_certs'
67
-
68
- desc "Open an irb session preloaded with this library"
69
- task :console do
70
- sh "irb -rubygems -r ./lib/#{name}.rb"
71
- end
7
+ task default: :spec
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Requires Ruby with rspec and faraday gems.
4
+ # rspec client_spec.rb
5
+
6
+ require 'faraday'
7
+ require 'json'
8
+
9
+ # Example API client
10
+ class Client
11
+ def initialize(conn)
12
+ @conn = conn
13
+ end
14
+
15
+ def sushi(jname, params: {})
16
+ res = @conn.get("/#{jname}", params)
17
+ data = JSON.parse(res.body)
18
+ data['name']
19
+ end
20
+ end
21
+
22
+ RSpec.describe Client do
23
+ let(:stubs) { Faraday::Adapter::Test::Stubs.new }
24
+ let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
25
+ let(:client) { Client.new(conn) }
26
+
27
+ it 'parses name' do
28
+ stubs.get('/ebi') do |env|
29
+ # optional: you can inspect the Faraday::Env
30
+ expect(env.url.path).to eq('/ebi')
31
+ [
32
+ 200,
33
+ { 'Content-Type': 'application/javascript' },
34
+ '{"name": "shrimp"}'
35
+ ]
36
+ end
37
+
38
+ # uncomment to trigger stubs.verify_stubbed_calls failure
39
+ # stubs.get('/unused') { [404, {}, ''] }
40
+
41
+ expect(client.sushi('ebi')).to eq('shrimp')
42
+ stubs.verify_stubbed_calls
43
+ end
44
+
45
+ it 'handles 404' do
46
+ stubs.get('/ebi') do
47
+ [
48
+ 404,
49
+ { 'Content-Type': 'application/javascript' },
50
+ '{}'
51
+ ]
52
+ end
53
+ expect(client.sushi('ebi')).to be_nil
54
+ stubs.verify_stubbed_calls
55
+ end
56
+
57
+ it 'handles exception' do
58
+ stubs.get('/ebi') do
59
+ raise Faraday::ConnectionFailed, nil
60
+ end
61
+
62
+ expect { client.sushi('ebi') }.to raise_error(Faraday::ConnectionFailed)
63
+ stubs.verify_stubbed_calls
64
+ end
65
+
66
+ context 'When the test stub is run in strict_mode' do
67
+ let(:stubs) { Faraday::Adapter::Test::Stubs.new(strict_mode: true) }
68
+
69
+ it 'verifies the all parameter values are identical' do
70
+ stubs.get('/ebi?abc=123') do
71
+ [
72
+ 200,
73
+ { 'Content-Type': 'application/javascript' },
74
+ '{"name": "shrimp"}'
75
+ ]
76
+ end
77
+
78
+ # uncomment to raise Stubs::NotFound
79
+ # expect(client.sushi('ebi', params: { abc: 123, foo: 'Kappa' })).to eq('shrimp')
80
+ expect(client.sushi('ebi', params: { abc: 123 })).to eq('shrimp')
81
+ stubs.verify_stubbed_calls
82
+ end
83
+ end
84
+
85
+ context 'When the Faraday connection is configured with FlatParamsEncoder' do
86
+ let(:conn) { Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) { |b| b.adapter(:test, stubs) } }
87
+
88
+ it 'handles the same multiple URL parameters' do
89
+ stubs.get('/ebi?a=x&a=y&a=z') { [200, { 'Content-Type' => 'application/json' }, '{"name": "shrimp"}'] }
90
+
91
+ # uncomment to raise Stubs::NotFound
92
+ # expect(client.sushi('ebi', params: { a: %w[x y] })).to eq('shrimp')
93
+ expect(client.sushi('ebi', params: { a: %w[x y z] })).to eq('shrimp')
94
+ stubs.verify_stubbed_calls
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Requires Ruby with test-unit and faraday gems.
4
+ # ruby client_test.rb
5
+
6
+ require 'faraday'
7
+ require 'json'
8
+ require 'test/unit'
9
+
10
+ # Example API client
11
+ class Client
12
+ def initialize(conn)
13
+ @conn = conn
14
+ end
15
+
16
+ def sushi(jname, params: {})
17
+ res = @conn.get("/#{jname}", params)
18
+ data = JSON.parse(res.body)
19
+ data['name']
20
+ end
21
+ end
22
+
23
+ # Example API client test
24
+ class ClientTest < Test::Unit::TestCase
25
+ def test_sushi_name
26
+ stubs = Faraday::Adapter::Test::Stubs.new
27
+ stubs.get('/ebi') do |env|
28
+ # optional: you can inspect the Faraday::Env
29
+ assert_equal '/ebi', env.url.path
30
+ [
31
+ 200,
32
+ { 'Content-Type': 'application/javascript' },
33
+ '{"name": "shrimp"}'
34
+ ]
35
+ end
36
+
37
+ # uncomment to trigger stubs.verify_stubbed_calls failure
38
+ # stubs.get('/unused') { [404, {}, ''] }
39
+
40
+ cli = client(stubs)
41
+ assert_equal 'shrimp', cli.sushi('ebi')
42
+ stubs.verify_stubbed_calls
43
+ end
44
+
45
+ def test_sushi_404
46
+ stubs = Faraday::Adapter::Test::Stubs.new
47
+ stubs.get('/ebi') do
48
+ [
49
+ 404,
50
+ { 'Content-Type': 'application/javascript' },
51
+ '{}'
52
+ ]
53
+ end
54
+
55
+ cli = client(stubs)
56
+ assert_nil cli.sushi('ebi')
57
+ stubs.verify_stubbed_calls
58
+ end
59
+
60
+ def test_sushi_exception
61
+ stubs = Faraday::Adapter::Test::Stubs.new
62
+ stubs.get('/ebi') do
63
+ raise Faraday::ConnectionFailed, nil
64
+ end
65
+
66
+ cli = client(stubs)
67
+ assert_raise Faraday::ConnectionFailed do
68
+ cli.sushi('ebi')
69
+ end
70
+ stubs.verify_stubbed_calls
71
+ end
72
+
73
+ def test_strict_mode
74
+ stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
75
+ stubs.get('/ebi?abc=123') do
76
+ [
77
+ 200,
78
+ { 'Content-Type': 'application/javascript' },
79
+ '{"name": "shrimp"}'
80
+ ]
81
+ end
82
+
83
+ cli = client(stubs)
84
+ assert_equal 'shrimp', cli.sushi('ebi', params: { abc: 123 })
85
+
86
+ # uncomment to raise Stubs::NotFound
87
+ # assert_equal 'shrimp', cli.sushi('ebi', params: { abc: 123, foo: 'Kappa' })
88
+ stubs.verify_stubbed_calls
89
+ end
90
+
91
+ def test_non_default_params_encoder
92
+ stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
93
+ stubs.get('/ebi?a=x&a=y&a=z') do
94
+ [
95
+ 200,
96
+ { 'Content-Type': 'application/javascript' },
97
+ '{"name": "shrimp"}'
98
+ ]
99
+ end
100
+ conn = Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
101
+ builder.adapter :test, stubs
102
+ end
103
+
104
+ cli = Client.new(conn)
105
+ assert_equal 'shrimp', cli.sushi('ebi', params: { a: %w[x y z] })
106
+
107
+ # uncomment to raise Stubs::NotFound
108
+ # assert_equal 'shrimp', cli.sushi('ebi', params: { a: %w[x y] })
109
+ stubs.verify_stubbed_calls
110
+ end
111
+
112
+ def client(stubs)
113
+ conn = Faraday.new do |builder|
114
+ builder.adapter :test, stubs
115
+ end
116
+ Client.new(conn)
117
+ end
118
+ end
@@ -1,26 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
4
  class Adapter
3
- # test = Faraday::Connection.new do
4
- # use Faraday::Adapter::Test do |stub|
5
- # stub.get '/nigiri/sake.json' do
6
- # [200, {}, 'hi world']
5
+ # @example
6
+ # test = Faraday::Connection.new do
7
+ # use Faraday::Adapter::Test do |stub|
8
+ # # Define matcher to match the request
9
+ # stub.get '/resource.json' do
10
+ # # return static content
11
+ # [200, {'Content-Type' => 'application/json'}, 'hi world']
12
+ # end
13
+ #
14
+ # # response with content generated based on request
15
+ # stub.get '/showget' do |env|
16
+ # [200, {'Content-Type' => 'text/plain'}, env[:method].to_s]
17
+ # end
18
+ #
19
+ # # A regular expression can be used as matching filter
20
+ # stub.get /\A\/items\/(\d+)\z/ do |env, meta|
21
+ # # in case regular expression is used, an instance of MatchData
22
+ # # can be received
23
+ # [200,
24
+ # {'Content-Type' => 'text/plain'},
25
+ # "showing item: #{meta[:match_data][1]}"
26
+ # ]
27
+ # end
28
+ #
29
+ # # You can set strict_mode to exactly match the stubbed requests.
30
+ # stub.strict_mode = true
7
31
  # end
8
32
  # end
9
- # end
10
33
  #
11
- # resp = test.get '/nigiri/sake.json'
12
- # resp.body # => 'hi world'
34
+ # resp = test.get '/resource.json'
35
+ # resp.body # => 'hi world'
13
36
  #
37
+ # resp = test.get '/showget'
38
+ # resp.body # => 'get'
39
+ #
40
+ # resp = test.get '/items/1'
41
+ # resp.body # => 'showing item: 1'
42
+ #
43
+ # resp = test.get '/items/2'
44
+ # resp.body # => 'showing item: 2'
14
45
  class Test < Faraday::Adapter
15
46
  attr_accessor :stubs
16
47
 
48
+ # A stack of Stubs
17
49
  class Stubs
18
50
  class NotFound < StandardError
19
51
  end
20
52
 
21
- def initialize
22
- # {:get => [Stub, Stub]}
23
- @stack, @consumed = {}, {}
53
+ def initialize(strict_mode: false)
54
+ # { get: [Stub, Stub] }
55
+ @stack = {}
56
+ @consumed = {}
57
+ @strict_mode = strict_mode
24
58
  yield(self) if block_given?
25
59
  end
26
60
 
@@ -28,17 +62,20 @@ module Faraday
28
62
  @stack.empty?
29
63
  end
30
64
 
31
- def match(request_method, path, headers, body)
32
- return false if !@stack.key?(request_method)
65
+ # @param env [Faraday::Env]
66
+ def match(env)
67
+ request_method = env[:method]
68
+ return false unless @stack.key?(request_method)
69
+
33
70
  stack = @stack[request_method]
34
71
  consumed = (@consumed[request_method] ||= [])
35
72
 
36
- if stub = matches?(stack, path, headers, body)
73
+ stub, meta = matches?(stack, env)
74
+ if stub
37
75
  consumed << stack.delete(stub)
38
- stub
39
- else
40
- matches?(consumed, path, headers, body)
76
+ return stub, meta
41
77
  end
78
+ matches?(consumed, env)
42
79
  end
43
80
 
44
81
  def get(path, headers = {}, &block)
@@ -49,15 +86,15 @@ module Faraday
49
86
  new_stub(:head, path, headers, &block)
50
87
  end
51
88
 
52
- def post(path, body=nil, headers = {}, &block)
89
+ def post(path, body = nil, headers = {}, &block)
53
90
  new_stub(:post, path, headers, body, &block)
54
91
  end
55
92
 
56
- def put(path, body=nil, headers = {}, &block)
93
+ def put(path, body = nil, headers = {}, &block)
57
94
  new_stub(:put, path, headers, body, &block)
58
95
  end
59
96
 
60
- def patch(path, body=nil, headers = {}, &block)
97
+ def patch(path, body = nil, headers = {}, &block)
61
98
  new_stub(:patch, path, headers, body, &block)
62
99
  end
63
100
 
@@ -73,54 +110,109 @@ module Faraday
73
110
  def verify_stubbed_calls
74
111
  failed_stubs = []
75
112
  @stack.each do |method, stubs|
76
- unless stubs.size == 0
77
- failed_stubs.concat(stubs.map {|stub|
113
+ next if stubs.empty?
114
+
115
+ failed_stubs.concat(
116
+ stubs.map do |stub|
78
117
  "Expected #{method} #{stub}."
79
- })
118
+ end
119
+ )
120
+ end
121
+ raise failed_stubs.join(' ') unless failed_stubs.empty?
122
+ end
123
+
124
+ # Set strict_mode. If the value is true, this adapter tries to find matched requests strictly,
125
+ # which means that all of a path, parameters, and headers must be the same as an actual request.
126
+ def strict_mode=(value)
127
+ @strict_mode = value
128
+ @stack.each do |_method, stubs|
129
+ stubs.each do |stub|
130
+ stub.strict_mode = value
80
131
  end
81
132
  end
82
- raise failed_stubs.join(" ") unless failed_stubs.size == 0
83
133
  end
84
134
 
85
135
  protected
86
136
 
87
- def new_stub(request_method, path, headers = {}, body=nil, &block)
88
- normalized_path = Faraday::Utils.normalize_path(path)
89
- (@stack[request_method] ||= []) << Stub.new(normalized_path, headers, body, block)
137
+ def new_stub(request_method, path, headers = {}, body = nil, &block)
138
+ normalized_path, host =
139
+ if path.is_a?(Regexp)
140
+ path
141
+ else
142
+ [
143
+ Faraday::Utils.normalize_path(path),
144
+ Faraday::Utils.URI(path).host
145
+ ]
146
+ end
147
+ path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
148
+ headers = Utils::Headers.new(headers)
149
+
150
+ stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
151
+ (@stack[request_method] ||= []) << stub
90
152
  end
91
153
 
92
- def matches?(stack, path, headers, body)
93
- stack.detect { |stub| stub.matches?(path, headers, body) }
154
+ # @param stack [Hash]
155
+ # @param env [Faraday::Env]
156
+ def matches?(stack, env)
157
+ stack.each do |stub|
158
+ match_result, meta = stub.matches?(env)
159
+ return stub, meta if match_result
160
+ end
161
+ nil
94
162
  end
95
163
  end
96
164
 
97
- class Stub < Struct.new(:path, :params, :headers, :body, :block)
98
- def initialize(full, headers, body, block)
99
- path, query = full.split('?')
100
- params = query ?
101
- Faraday::Utils.parse_nested_query(query) :
102
- {}
103
- super(path, params, headers, body, block)
104
- end
105
-
106
- def matches?(request_uri, request_headers, request_body)
107
- request_path, request_query = request_uri.split('?')
108
- request_params = request_query ?
109
- Faraday::Utils.parse_nested_query(request_query) :
110
- {}
111
- request_path == path &&
112
- params_match?(request_params) &&
165
+ # Stub request
166
+ class Stub < Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) # rubocop:disable Style/StructInheritance
167
+ # @param env [Faraday::Env]
168
+ def matches?(env)
169
+ request_host = env[:url].host
170
+ request_path = Faraday::Utils.normalize_path(env[:url].path)
171
+ request_headers = env.request_headers
172
+ request_body = env[:body]
173
+
174
+ # meta is a hash used as carrier
175
+ # that will be yielded to consumer block
176
+ meta = {}
177
+ [(host.nil? || host == request_host) &&
178
+ path_match?(request_path, meta) &&
179
+ params_match?(env) &&
113
180
  (body.to_s.size.zero? || request_body == body) &&
114
- headers_match?(request_headers)
181
+ headers_match?(request_headers), meta]
115
182
  end
116
183
 
117
- def params_match?(request_params)
184
+ def path_match?(request_path, meta)
185
+ if path.is_a?(Regexp)
186
+ !!(meta[:match_data] = path.match(request_path))
187
+ else
188
+ path == request_path
189
+ end
190
+ end
191
+
192
+ # @param env [Faraday::Env]
193
+ def params_match?(env)
194
+ request_params = env[:params]
195
+ params = env.params_encoder.decode(query) || {}
196
+
197
+ if strict_mode
198
+ return Set.new(params) == Set.new(request_params)
199
+ end
200
+
118
201
  params.keys.all? do |key|
119
202
  request_params[key] == params[key]
120
203
  end
121
204
  end
122
205
 
123
206
  def headers_match?(request_headers)
207
+ if strict_mode
208
+ headers_with_user_agent = headers.dup.tap do |hs|
209
+ # NOTE: Set User-Agent in case it's not set when creating Stubs.
210
+ # Users would not want to set Faraday's User-Agent explicitly.
211
+ hs[:user_agent] ||= Connection::USER_AGENT
212
+ end
213
+ return Set.new(headers_with_user_agent) == Set.new(request_headers)
214
+ end
215
+
124
216
  headers.keys.all? do |key|
125
217
  request_headers[key] == headers[key]
126
218
  end
@@ -131,7 +223,7 @@ module Faraday
131
223
  end
132
224
  end
133
225
 
134
- def initialize(app, stubs=nil, &block)
226
+ def initialize(app, stubs = nil, &block)
135
227
  super(app)
136
228
  @stubs = stubs || Stubs.new
137
229
  configure(&block) if block
@@ -141,20 +233,28 @@ module Faraday
141
233
  yield(stubs)
142
234
  end
143
235
 
236
+ # @param env [Faraday::Env]
144
237
  def call(env)
145
238
  super
146
- normalized_path = Faraday::Utils.normalize_path(env[:url])
147
- params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
148
-
149
- if stub = stubs.match(env[:method], normalized_path, env.request_headers, env[:body])
150
- env[:params] = (query = env[:url].query) ?
151
- params_encoder.decode(query) :
152
- {}
153
- status, headers, body = stub.block.call(env)
154
- save_response(env, status, body, headers)
155
- else
156
- raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
239
+
240
+ env.request.params_encoder ||= Faraday::Utils.default_params_encoder
241
+ env[:params] = env.params_encoder.decode(env[:url].query) || {}
242
+ stub, meta = stubs.match(env)
243
+
244
+ unless stub
245
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
246
+ "#{env[:url]} #{env[:body]}"
157
247
  end
248
+
249
+ block_arity = stub.block.arity
250
+ status, headers, body =
251
+ if block_arity >= 0
252
+ stub.block.call(*[env, meta].take(block_arity))
253
+ else
254
+ stub.block.call(env, meta)
255
+ end
256
+ save_response(env, status, body, headers)
257
+
158
258
  @app.call(env)
159
259
  end
160
260
  end