faraday 0.17.3 → 1.4.1

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +156 -8
  3. data/LICENSE.md +1 -1
  4. data/README.md +16 -358
  5. data/Rakefile +1 -7
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday.rb +120 -189
  9. data/lib/faraday/adapter.rb +77 -22
  10. data/lib/faraday/adapter/em_http.rb +148 -102
  11. data/lib/faraday/adapter/em_http_ssl_patch.rb +24 -18
  12. data/lib/faraday/adapter/em_synchrony.rb +110 -63
  13. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +18 -15
  14. data/lib/faraday/adapter/httpclient.rb +83 -59
  15. data/lib/faraday/adapter/patron.rb +80 -43
  16. data/lib/faraday/adapter/rack.rb +30 -13
  17. data/lib/faraday/adapter/test.rb +86 -53
  18. data/lib/faraday/adapter/typhoeus.rb +4 -1
  19. data/lib/faraday/adapter_registry.rb +30 -0
  20. data/lib/faraday/autoload.rb +44 -36
  21. data/lib/faraday/connection.rb +313 -182
  22. data/lib/faraday/dependency_loader.rb +37 -0
  23. data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  24. data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
  25. data/lib/faraday/error.rb +29 -35
  26. data/lib/faraday/file_part.rb +128 -0
  27. data/lib/faraday/logging/formatter.rb +105 -0
  28. data/lib/faraday/methods.rb +6 -0
  29. data/lib/faraday/middleware.rb +19 -25
  30. data/lib/faraday/middleware_registry.rb +129 -0
  31. data/lib/faraday/options.rb +36 -191
  32. data/lib/faraday/options/connection_options.rb +22 -0
  33. data/lib/faraday/options/env.rb +181 -0
  34. data/lib/faraday/options/proxy_options.rb +28 -0
  35. data/lib/faraday/options/request_options.rb +22 -0
  36. data/lib/faraday/options/ssl_options.rb +59 -0
  37. data/lib/faraday/param_part.rb +53 -0
  38. data/lib/faraday/parameters.rb +4 -197
  39. data/lib/faraday/rack_builder.rb +76 -64
  40. data/lib/faraday/request.rb +86 -44
  41. data/lib/faraday/request/authorization.rb +44 -30
  42. data/lib/faraday/request/basic_authentication.rb +14 -7
  43. data/lib/faraday/request/instrumentation.rb +45 -27
  44. data/lib/faraday/request/multipart.rb +86 -48
  45. data/lib/faraday/request/retry.rb +197 -171
  46. data/lib/faraday/request/token_authentication.rb +15 -10
  47. data/lib/faraday/request/url_encoded.rb +43 -23
  48. data/lib/faraday/response.rb +24 -20
  49. data/lib/faraday/response/logger.rb +22 -69
  50. data/lib/faraday/response/raise_error.rb +49 -18
  51. data/lib/faraday/utils.rb +38 -247
  52. data/lib/faraday/utils/headers.rb +139 -0
  53. data/lib/faraday/utils/params_hash.rb +61 -0
  54. data/lib/faraday/version.rb +5 -0
  55. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  56. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  57. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  58. data/spec/faraday/adapter/excon_spec.rb +49 -0
  59. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  60. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  61. data/spec/faraday/adapter/patron_spec.rb +18 -0
  62. data/spec/faraday/adapter/rack_spec.rb +8 -0
  63. data/spec/faraday/adapter/test_spec.rb +260 -0
  64. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  65. data/spec/faraday/adapter_registry_spec.rb +28 -0
  66. data/spec/faraday/adapter_spec.rb +55 -0
  67. data/spec/faraday/composite_read_io_spec.rb +80 -0
  68. data/spec/faraday/connection_spec.rb +721 -0
  69. data/spec/faraday/error_spec.rb +12 -54
  70. data/spec/faraday/middleware_spec.rb +52 -0
  71. data/spec/faraday/options/env_spec.rb +70 -0
  72. data/spec/faraday/options/options_spec.rb +297 -0
  73. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  74. data/spec/faraday/options/request_options_spec.rb +19 -0
  75. data/spec/faraday/params_encoders/flat_spec.rb +42 -0
  76. data/spec/faraday/params_encoders/nested_spec.rb +142 -0
  77. data/spec/faraday/rack_builder_spec.rb +345 -0
  78. data/spec/faraday/request/authorization_spec.rb +88 -0
  79. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  80. data/spec/faraday/request/multipart_spec.rb +302 -0
  81. data/spec/faraday/request/retry_spec.rb +242 -0
  82. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  83. data/spec/faraday/request_spec.rb +120 -0
  84. data/spec/faraday/response/logger_spec.rb +220 -0
  85. data/spec/faraday/response/middleware_spec.rb +68 -0
  86. data/spec/faraday/response/raise_error_spec.rb +78 -15
  87. data/spec/faraday/response_spec.rb +75 -0
  88. data/spec/faraday/utils/headers_spec.rb +82 -0
  89. data/spec/faraday/utils_spec.rb +56 -0
  90. data/spec/faraday_spec.rb +37 -0
  91. data/spec/spec_helper.rb +63 -36
  92. data/spec/support/disabling_stub.rb +14 -0
  93. data/spec/support/fake_safe_buffer.rb +15 -0
  94. data/spec/support/helper_methods.rb +133 -0
  95. data/spec/support/shared_examples/adapter.rb +105 -0
  96. data/spec/support/shared_examples/params_encoder.rb +18 -0
  97. data/spec/support/shared_examples/request_method.rb +262 -0
  98. data/spec/support/streaming_response_checker.rb +35 -0
  99. data/spec/support/webmock_rack_app.rb +68 -0
  100. metadata +124 -41
  101. data/lib/faraday/adapter/excon.rb +0 -82
  102. data/lib/faraday/adapter/net_http.rb +0 -152
  103. data/lib/faraday/adapter/net_http_persistent.rb +0 -68
  104. data/lib/faraday/deprecate.rb +0 -107
  105. data/lib/faraday/upload_io.rb +0 -67
  106. data/spec/faraday/deprecate_spec.rb +0 -69
  107. data/test/adapters/default_test.rb +0 -14
  108. data/test/adapters/em_http_test.rb +0 -30
  109. data/test/adapters/em_synchrony_test.rb +0 -32
  110. data/test/adapters/excon_test.rb +0 -30
  111. data/test/adapters/httpclient_test.rb +0 -34
  112. data/test/adapters/integration.rb +0 -263
  113. data/test/adapters/logger_test.rb +0 -136
  114. data/test/adapters/net_http_persistent_test.rb +0 -114
  115. data/test/adapters/net_http_test.rb +0 -79
  116. data/test/adapters/patron_test.rb +0 -40
  117. data/test/adapters/rack_test.rb +0 -38
  118. data/test/adapters/test_middleware_test.rb +0 -157
  119. data/test/adapters/typhoeus_test.rb +0 -38
  120. data/test/authentication_middleware_test.rb +0 -65
  121. data/test/composite_read_io_test.rb +0 -109
  122. data/test/connection_test.rb +0 -738
  123. data/test/env_test.rb +0 -268
  124. data/test/helper.rb +0 -75
  125. data/test/live_server.rb +0 -67
  126. data/test/middleware/instrumentation_test.rb +0 -88
  127. data/test/middleware/retry_test.rb +0 -282
  128. data/test/middleware_stack_test.rb +0 -260
  129. data/test/multibyte.txt +0 -1
  130. data/test/options_test.rb +0 -333
  131. data/test/parameters_test.rb +0 -157
  132. data/test/request_middleware_test.rb +0 -126
  133. data/test/response_middleware_test.rb +0 -72
  134. data/test/strawberry.rb +0 -2
  135. data/test/utils_test.rb +0 -98
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday'
4
- Faraday::Deprecate.skip = false
5
-
6
3
  # This file was generated by the `rspec --init` command. Conventionally, all
7
4
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
8
5
  # The generated `.rspec` file contains `--require spec_helper` which will cause
@@ -18,6 +15,29 @@ Faraday::Deprecate.skip = false
18
15
  # it.
19
16
  #
20
17
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
18
+
19
+ require 'simplecov'
20
+ require 'coveralls'
21
+ require 'webmock/rspec'
22
+ WebMock.disable_net_connect!(allow_localhost: true)
23
+
24
+ SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
25
+
26
+ SimpleCov.start do
27
+ add_filter '/spec/'
28
+ minimum_coverage 84
29
+ minimum_coverage_by_file 26
30
+ end
31
+
32
+ # Ensure all /lib files are loaded
33
+ # so they will be included in the test coverage report.
34
+ Dir['./lib/**/*.rb'].sort.each { |file| require file }
35
+
36
+ require 'faraday'
37
+ require 'pry'
38
+
39
+ Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
40
+
21
41
  RSpec.configure do |config|
22
42
  # rspec-expectations config goes here. You can use an alternate
23
43
  # assertion/expectation library such as wrong or the stdlib/minitest
@@ -49,57 +69,64 @@ RSpec.configure do |config|
49
69
  # triggering implicit auto-inclusion in groups with matching metadata.
50
70
  config.shared_context_metadata_behavior = :apply_to_host_groups
51
71
 
52
- # Run specs in random order to surface order dependencies. If you find an
53
- # order dependency and want to debug it, you can fix the order by providing
54
- # the seed, which is printed after each run.
55
- # --seed 1234
56
- config.order = :random
57
-
58
- # Seed global randomization in this process using the `--seed` CLI option.
59
- # Setting this allows you to use `--seed` to deterministically reproduce
60
- # test failures related to randomization by passing the same `--seed` value
61
- # as the one that triggered the failure.
62
- Kernel.srand config.seed
63
-
64
- # Limits the available syntax to the non-monkey patched syntax that is
65
- # recommended. For more details, see:
66
- # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
67
- # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
68
- # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
69
- config.disable_monkey_patching!
70
-
71
- # The settings below are suggested to provide a good initial experience
72
- # with RSpec, but feel free to customize to your heart's content.
73
- =begin
74
72
  # This allows you to limit a spec run to individual examples or groups
75
73
  # you care about by tagging them with `:focus` metadata. When nothing
76
74
  # is tagged with `:focus`, all examples get run. RSpec also provides
77
75
  # aliases for `it`, `describe`, and `context` that include `:focus`
78
76
  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
79
- config.filter_run_when_matching :focus
77
+ # config.filter_run_when_matching :focus
80
78
 
81
79
  # Allows RSpec to persist some state between runs in order to support
82
80
  # the `--only-failures` and `--next-failure` CLI options. We recommend
83
81
  # you configure your source control system to ignore this file.
84
- config.example_status_persistence_file_path = "spec/examples.txt"
82
+ # config.example_status_persistence_file_path = "spec/examples.txt"
83
+
84
+ # Limits the available syntax to the non-monkey patched syntax that is
85
+ # recommended. For more details, see:
86
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
87
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
88
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
89
+ # config.disable_monkey_patching!
85
90
 
86
91
  # This setting enables warnings. It's recommended, but in some cases may
87
92
  # be too noisy due to issues in dependencies.
88
- config.warnings = true
93
+ # config.warnings = true
89
94
 
90
95
  # Many RSpec users commonly either run the entire suite or an individual
91
96
  # file, and it's useful to allow more verbose output when running an
92
97
  # individual spec file.
93
- if config.files_to_run.one?
94
- # Use the documentation formatter for detailed output,
95
- # unless a formatter has already been configured
96
- # (e.g. via a command-line flag).
97
- config.default_formatter = "doc"
98
- end
98
+ # if config.files_to_run.one?
99
+ # # Use the documentation formatter for detailed output,
100
+ # # unless a formatter has already been configured
101
+ # # (e.g. via a command-line flag).
102
+ # config.default_formatter = "doc"
103
+ # end
99
104
 
100
105
  # Print the 10 slowest examples and example groups at the
101
106
  # end of the spec run, to help surface which specs are running
102
107
  # particularly slow.
103
- config.profile_examples = 10
104
- =end
108
+ # config.profile_examples = 10
109
+
110
+ # Run specs in random order to surface order dependencies. If you find an
111
+ # order dependency and want to debug it, you can fix the order by providing
112
+ # the seed, which is printed after each run.
113
+ # --seed 1234
114
+ config.order = :random
115
+
116
+ # Seed global randomization in this process using the `--seed` CLI option.
117
+ # Setting this allows you to use `--seed` to deterministically reproduce
118
+ # test failures related to randomization by passing the same `--seed` value
119
+ # as the one that triggered the failure.
120
+ Kernel.srand config.seed
121
+
122
+ config.include Faraday::HelperMethods
123
+ end
124
+
125
+ # Extends RSpec DocumentationFormatter to hide skipped tests.
126
+ module FormatterOverrides
127
+ def example_pending(_arg); end
128
+
129
+ def dump_pending(_arg); end
130
+
131
+ RSpec::Core::Formatters::DocumentationFormatter.prepend self
105
132
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Allows to disable WebMock stubs
4
+ module DisablingStub
5
+ def disable
6
+ @disabled = true
7
+ end
8
+
9
+ def disabled?
10
+ @disabled
11
+ end
12
+
13
+ WebMock::RequestStub.prepend self
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # emulates ActiveSupport::SafeBuffer#gsub
4
+ FakeSafeBuffer = Struct.new(:string) do
5
+ def to_s
6
+ self
7
+ end
8
+
9
+ def gsub(regex)
10
+ string.gsub(regex) do
11
+ match, = $&, '' =~ /a/
12
+ yield(match)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'multipart_parser/reader'
4
+
5
+ module Faraday
6
+ module HelperMethods
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def features(*features)
13
+ @features = features
14
+ end
15
+
16
+ def on_feature(name)
17
+ yield if block_given? && feature?(name)
18
+ end
19
+
20
+ def feature?(name)
21
+ if @features.nil?
22
+ superclass.feature?(name) if superclass.respond_to?(:feature?)
23
+ elsif @features.include?(name)
24
+ true
25
+ end
26
+ end
27
+
28
+ def method_with_body?(method)
29
+ METHODS_WITH_BODY.include?(method.to_s)
30
+ end
31
+ end
32
+
33
+ def ssl_mode?
34
+ ENV['SSL'] == 'yes'
35
+ end
36
+
37
+ def normalize(url)
38
+ Faraday::Utils::URI(url)
39
+ end
40
+
41
+ def with_default_uri_parser(parser)
42
+ old_parser = Faraday::Utils.default_uri_parser
43
+ begin
44
+ Faraday::Utils.default_uri_parser = parser
45
+ yield
46
+ ensure
47
+ Faraday::Utils.default_uri_parser = old_parser
48
+ end
49
+ end
50
+
51
+ def with_env(new_env)
52
+ old_env = {}
53
+
54
+ new_env.each do |key, value|
55
+ old_env[key] = ENV.fetch(key, false)
56
+ ENV[key] = value
57
+ end
58
+
59
+ begin
60
+ yield
61
+ ensure
62
+ old_env.each do |key, value|
63
+ value == false ? ENV.delete(key) : ENV[key] = value
64
+ end
65
+ end
66
+ end
67
+
68
+ def with_env_proxy_disabled
69
+ Faraday.ignore_env_proxy = true
70
+
71
+ begin
72
+ yield
73
+ ensure
74
+ Faraday.ignore_env_proxy = false
75
+ end
76
+ end
77
+
78
+ def capture_warnings
79
+ old = $stderr
80
+ $stderr = StringIO.new
81
+ begin
82
+ yield
83
+ $stderr.string
84
+ ensure
85
+ $stderr = old
86
+ end
87
+ end
88
+
89
+ def multipart_file
90
+ Faraday::FilePart.new(__FILE__, 'text/x-ruby')
91
+ end
92
+
93
+ # parse boundary out of a Content-Type header like:
94
+ # Content-Type: multipart/form-data; boundary=gc0p4Jq0M2Yt08jU534c0p
95
+ def parse_multipart_boundary(ctype)
96
+ MultipartParser::Reader.extract_boundary_value(ctype)
97
+ end
98
+
99
+ # parse a multipart MIME message, returning a hash of any multipart errors
100
+ def parse_multipart(boundary, body)
101
+ reader = MultipartParser::Reader.new(boundary)
102
+ result = { errors: [], parts: [] }
103
+ def result.part(name)
104
+ hash = self[:parts].detect { |h| h[:part].name == name }
105
+ [hash[:part], hash[:body].join]
106
+ end
107
+
108
+ reader.on_part do |part|
109
+ result[:parts] << thispart = {
110
+ part: part,
111
+ body: []
112
+ }
113
+ part.on_data do |chunk|
114
+ thispart[:body] << chunk
115
+ end
116
+ end
117
+ reader.on_error do |msg|
118
+ result[:errors] << msg
119
+ end
120
+ reader.write(body)
121
+ result
122
+ end
123
+
124
+ def method_with_body?(method)
125
+ self.class.method_with_body?(method)
126
+ end
127
+
128
+ def big_string
129
+ kb = 1024
130
+ (32..126).map(&:chr).cycle.take(50 * kb).join
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'an adapter' do |**options|
4
+ before { skip } if options[:skip]
5
+
6
+ context 'with SSL enabled' do
7
+ before { ENV['SSL'] = 'yes' }
8
+ include_examples 'adapter examples', options
9
+ end
10
+
11
+ context 'with SSL disabled' do
12
+ before { ENV['SSL'] = 'no' }
13
+ include_examples 'adapter examples', options
14
+ end
15
+ end
16
+
17
+ shared_examples 'adapter examples' do |**options|
18
+ include Faraday::StreamingResponseChecker
19
+
20
+ let(:adapter) { described_class.name.split('::').last }
21
+
22
+ let(:conn_options) { { headers: { 'X-Faraday-Adapter' => adapter } }.merge(options[:conn_options] || {}) }
23
+
24
+ let(:adapter_options) do
25
+ return [] unless options[:adapter_options]
26
+
27
+ if options[:adapter_options].is_a?(Array)
28
+ options[:adapter_options]
29
+ else
30
+ [options[:adapter_options]]
31
+ end
32
+ end
33
+
34
+ let(:protocol) { ssl_mode? ? 'https' : 'http' }
35
+ let(:remote) { "#{protocol}://example.com" }
36
+ let(:stub_remote) { remote }
37
+
38
+ let(:conn) do
39
+ conn_options[:ssl] ||= {}
40
+ conn_options[:ssl][:ca_file] ||= ENV['SSL_FILE']
41
+
42
+ Faraday.new(remote, conn_options) do |conn|
43
+ conn.request :multipart
44
+ conn.request :url_encoded
45
+ conn.response :raise_error
46
+ conn.adapter described_class, *adapter_options
47
+ end
48
+ end
49
+
50
+ let!(:request_stub) { stub_request(http_method, stub_remote) }
51
+
52
+ after do
53
+ expect(request_stub).to have_been_requested unless request_stub.disabled?
54
+ end
55
+
56
+ describe '#delete' do
57
+ let(:http_method) { :delete }
58
+
59
+ it_behaves_like 'a request method', :delete
60
+ end
61
+
62
+ describe '#get' do
63
+ let(:http_method) { :get }
64
+
65
+ it_behaves_like 'a request method', :get
66
+ end
67
+
68
+ describe '#head' do
69
+ let(:http_method) { :head }
70
+
71
+ it_behaves_like 'a request method', :head
72
+ end
73
+
74
+ describe '#options' do
75
+ let(:http_method) { :options }
76
+
77
+ it_behaves_like 'a request method', :options
78
+ end
79
+
80
+ describe '#patch' do
81
+ let(:http_method) { :patch }
82
+
83
+ it_behaves_like 'a request method', :patch
84
+ end
85
+
86
+ describe '#post' do
87
+ let(:http_method) { :post }
88
+
89
+ it_behaves_like 'a request method', :post
90
+ end
91
+
92
+ describe '#put' do
93
+ let(:http_method) { :put }
94
+
95
+ it_behaves_like 'a request method', :put
96
+ end
97
+
98
+ on_feature :trace_method do
99
+ describe '#trace' do
100
+ let(:http_method) { :trace }
101
+
102
+ it_behaves_like 'a request method', :trace
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'a params encoder' do
4
+ it 'escapes safe buffer' do
5
+ monies = FakeSafeBuffer.new('$32,000.00')
6
+ expect(subject.encode('a' => monies)).to eq('a=%2432%2C000.00')
7
+ end
8
+
9
+ it 'raises type error for empty string' do
10
+ expect { subject.encode('') }.to raise_error(TypeError) do |error|
11
+ expect(error.message).to eq("Can't convert String into Hash.")
12
+ end
13
+ end
14
+
15
+ it 'encodes nil' do
16
+ expect(subject.encode('a' => nil)).to eq('a')
17
+ end
18
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'proxy examples' do
4
+ it 'handles requests with proxy' do
5
+ res = conn.public_send(http_method, '/')
6
+
7
+ expect(res.status).to eq(200)
8
+ end
9
+
10
+ it 'handles proxy failures' do
11
+ request_stub.to_return(status: 407)
12
+
13
+ expect { conn.public_send(http_method, '/') }.to raise_error(Faraday::ProxyAuthError)
14
+ end
15
+ end
16
+
17
+ shared_examples 'a request method' do |http_method|
18
+ let(:query_or_body) { method_with_body?(http_method) ? :body : :query }
19
+ let(:response) { conn.public_send(http_method, '/') }
20
+
21
+ unless http_method == :head && feature?(:skip_response_body_on_head)
22
+ it 'retrieves the response body' do
23
+ res_body = 'test'
24
+ request_stub.to_return(body: res_body)
25
+ expect(conn.public_send(http_method, '/').body).to eq(res_body)
26
+ end
27
+ end
28
+
29
+ it 'handles headers with multiple values' do
30
+ request_stub.to_return(headers: { 'Set-Cookie' => 'name=value' })
31
+ expect(response.headers['set-cookie']).to eq('name=value')
32
+ end
33
+
34
+ it 'retrieves the response headers' do
35
+ request_stub.to_return(headers: { 'Content-Type' => 'text/plain' })
36
+ expect(response.headers['Content-Type']).to match(%r{text/plain})
37
+ expect(response.headers['content-type']).to match(%r{text/plain})
38
+ end
39
+
40
+ it 'sends user agent' do
41
+ request_stub.with(headers: { 'User-Agent' => 'Agent Faraday' })
42
+ conn.public_send(http_method, '/', nil, user_agent: 'Agent Faraday')
43
+ end
44
+
45
+ it 'represents empty body response as blank string' do
46
+ expect(response.body).to eq('')
47
+ end
48
+
49
+ it 'handles connection error' do
50
+ request_stub.disable
51
+ expect { conn.public_send(http_method, 'http://localhost:4') }.to raise_error(Faraday::ConnectionFailed)
52
+ end
53
+
54
+ on_feature :local_socket_binding do
55
+ it 'binds local socket' do
56
+ stub_request(http_method, 'http://example.com')
57
+
58
+ host = '1.2.3.4'
59
+ port = 1234
60
+ conn_options[:request] = { bind: { host: host, port: port } }
61
+
62
+ conn.public_send(http_method, '/')
63
+
64
+ expect(conn.options[:bind][:host]).to eq(host)
65
+ expect(conn.options[:bind][:port]).to eq(port)
66
+ end
67
+ end
68
+
69
+ # context 'when wrong ssl certificate is provided' do
70
+ # let(:ca_file_path) { 'tmp/faraday-different-ca-cert.crt' }
71
+ # before { conn_options.merge!(ssl: { ca_file: ca_file_path }) }
72
+ #
73
+ # it do
74
+ # expect { conn.public_send(http_method, '/') }.to raise_error(Faraday::SSLError) # do |ex|
75
+ # expect(ex.message).to include?('certificate')
76
+ # end
77
+ # end
78
+ # end
79
+
80
+ on_feature :request_body_on_query_methods do
81
+ it 'sends request body' do
82
+ request_stub.with(Hash[:body, 'test'])
83
+ res = if query_or_body == :body
84
+ conn.public_send(http_method, '/', 'test')
85
+ else
86
+ conn.public_send(http_method, '/') do |req|
87
+ req.body = 'test'
88
+ end
89
+ end
90
+ expect(res.env.request_body).to eq('test')
91
+ end
92
+ end
93
+
94
+ it 'sends url encoded parameters' do
95
+ payload = { name: 'zack' }
96
+ request_stub.with(Hash[query_or_body, payload])
97
+ res = conn.public_send(http_method, '/', payload)
98
+ if query_or_body == :query
99
+ expect(res.env.request_body).to be_nil
100
+ else
101
+ expect(res.env.request_body).to eq('name=zack')
102
+ end
103
+ end
104
+
105
+ it 'sends url encoded nested parameters' do
106
+ payload = { name: { first: 'zack' } }
107
+ request_stub.with(Hash[query_or_body, payload])
108
+ conn.public_send(http_method, '/', payload)
109
+ end
110
+
111
+ # TODO: This needs reimplementation: see https://github.com/lostisland/faraday/issues/718
112
+ # Should raise Faraday::TimeoutError
113
+ it 'supports timeout option' do
114
+ conn_options[:request] = { timeout: 1 }
115
+ request_stub.to_timeout
116
+ exc = adapter == 'NetHttp' ? Faraday::ConnectionFailed : Faraday::TimeoutError
117
+ expect { conn.public_send(http_method, '/') }.to raise_error(exc)
118
+ end
119
+
120
+ # TODO: This needs reimplementation: see https://github.com/lostisland/faraday/issues/718
121
+ # Should raise Faraday::ConnectionFailed
122
+ it 'supports open_timeout option' do
123
+ conn_options[:request] = { open_timeout: 1 }
124
+ request_stub.to_timeout
125
+ exc = adapter == 'NetHttp' ? Faraday::ConnectionFailed : Faraday::TimeoutError
126
+ expect { conn.public_send(http_method, '/') }.to raise_error(exc)
127
+ end
128
+
129
+ # Can't send files on get, head and delete methods
130
+ if method_with_body?(http_method)
131
+ it 'sends files' do
132
+ payload = { uploaded_file: multipart_file }
133
+ request_stub.with(headers: { 'Content-Type' => %r{\Amultipart/form-data} }) do |request|
134
+ # WebMock does not support matching body for multipart/form-data requests yet :(
135
+ # https://github.com/bblimke/webmock/issues/623
136
+ request.body.include?('RubyMultipartPost')
137
+ end
138
+ conn.public_send(http_method, '/', payload)
139
+ end
140
+ end
141
+
142
+ on_feature :reason_phrase_parse do
143
+ it 'parses the reason phrase' do
144
+ request_stub.to_return(status: [200, 'OK'])
145
+ expect(response.reason_phrase).to eq('OK')
146
+ end
147
+ end
148
+
149
+ on_feature :compression do
150
+ # Accept-Encoding header not sent for HEAD requests as body is not expected in the response.
151
+ unless http_method == :head
152
+ it 'handles gzip compression' do
153
+ request_stub.with(headers: { 'Accept-Encoding' => /\bgzip\b/ })
154
+ conn.public_send(http_method, '/')
155
+ end
156
+
157
+ it 'handles deflate compression' do
158
+ request_stub.with(headers: { 'Accept-Encoding' => /\bdeflate\b/ })
159
+ conn.public_send(http_method, '/')
160
+ end
161
+ end
162
+ end
163
+
164
+ on_feature :streaming do
165
+ describe 'streaming' do
166
+ let(:streamed) { [] }
167
+
168
+ context 'when response is empty' do
169
+ it do
170
+ conn.public_send(http_method, '/') do |req|
171
+ req.options.on_data = proc { |*args| streamed << args }
172
+ end
173
+
174
+ expect(streamed).to eq([['', 0]])
175
+ end
176
+ end
177
+
178
+ context 'when response contains big data' do
179
+ before { request_stub.to_return(body: big_string) }
180
+
181
+ it 'handles streaming' do
182
+ response = conn.public_send(http_method, '/') do |req|
183
+ req.options.on_data = proc { |*args| streamed << args }
184
+ end
185
+
186
+ expect(response.body).to eq('')
187
+ check_streaming_response(streamed, chunk_size: 16 * 1024)
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ on_feature :parallel do
194
+ context 'with parallel setup' do
195
+ before do
196
+ @resp1 = nil
197
+ @resp2 = nil
198
+ @payload1 = { a: '1' }
199
+ @payload2 = { b: '2' }
200
+
201
+ request_stub
202
+ .with(Hash[query_or_body, @payload1])
203
+ .to_return(body: @payload1.to_json)
204
+
205
+ stub_request(http_method, remote)
206
+ .with(Hash[query_or_body, @payload2])
207
+ .to_return(body: @payload2.to_json)
208
+
209
+ conn.in_parallel do
210
+ @resp1 = conn.public_send(http_method, '/', @payload1)
211
+ @resp2 = conn.public_send(http_method, '/', @payload2)
212
+
213
+ expect(conn.in_parallel?).to be_truthy
214
+ expect(@resp1.body).to be_nil
215
+ expect(@resp2.body).to be_nil
216
+ end
217
+
218
+ expect(conn.in_parallel?).to be_falsey
219
+ end
220
+
221
+ it 'handles parallel requests status' do
222
+ expect(@resp1&.status).to eq(200)
223
+ expect(@resp2&.status).to eq(200)
224
+ end
225
+
226
+ unless http_method == :head && feature?(:skip_response_body_on_head)
227
+ it 'handles parallel requests body' do
228
+ expect(@resp1&.body).to eq(@payload1.to_json)
229
+ expect(@resp2&.body).to eq(@payload2.to_json)
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ context 'when a proxy is provided as option' do
236
+ before do
237
+ conn_options[:proxy] = 'http://env-proxy.com:80'
238
+ end
239
+
240
+ include_examples 'proxy examples'
241
+ end
242
+
243
+ context 'when http_proxy env variable is set' do
244
+ let(:proxy_url) { 'http://env-proxy.com:80' }
245
+
246
+ around do |example|
247
+ with_env 'http_proxy' => proxy_url do
248
+ example.run
249
+ end
250
+ end
251
+
252
+ include_examples 'proxy examples'
253
+
254
+ context 'when the env proxy is ignored' do
255
+ around do |example|
256
+ with_env_proxy_disabled(&example)
257
+ end
258
+
259
+ include_examples 'proxy examples'
260
+ end
261
+ end
262
+ end