faraday 2.15.0 → 2.15.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +11 -5
  3. data/lib/faraday/connection.rb +13 -6
  4. data/lib/faraday/encoders/nested_params_encoder.rb +1 -1
  5. data/lib/faraday/error.rb +29 -2
  6. data/lib/faraday/logging/formatter.rb +1 -1
  7. data/lib/faraday/options.rb +2 -1
  8. data/lib/faraday/rack_builder.rb +3 -0
  9. data/lib/faraday/response/raise_error.rb +7 -1
  10. data/lib/faraday/response.rb +5 -2
  11. data/lib/faraday/utils/headers.rb +41 -7
  12. data/lib/faraday/utils.rb +15 -3
  13. data/lib/faraday/version.rb +1 -1
  14. data/spec/external_adapters/faraday_specs_setup.rb +2 -0
  15. data/spec/support/integration/test_pki.rb +77 -0
  16. data/spec/support/integration/tls_server.rb +92 -0
  17. data/spec/support/integration_helper.rb +71 -0
  18. metadata +5 -29
  19. data/spec/faraday/adapter/test_spec.rb +0 -460
  20. data/spec/faraday/adapter_registry_spec.rb +0 -28
  21. data/spec/faraday/adapter_spec.rb +0 -55
  22. data/spec/faraday/connection_spec.rb +0 -897
  23. data/spec/faraday/error_spec.rb +0 -175
  24. data/spec/faraday/middleware_registry_spec.rb +0 -31
  25. data/spec/faraday/middleware_spec.rb +0 -213
  26. data/spec/faraday/options/env_spec.rb +0 -76
  27. data/spec/faraday/options/options_spec.rb +0 -331
  28. data/spec/faraday/options/proxy_options_spec.rb +0 -85
  29. data/spec/faraday/options/request_options_spec.rb +0 -19
  30. data/spec/faraday/params_encoders/flat_spec.rb +0 -42
  31. data/spec/faraday/params_encoders/nested_spec.rb +0 -179
  32. data/spec/faraday/rack_builder_spec.rb +0 -323
  33. data/spec/faraday/request/authorization_spec.rb +0 -118
  34. data/spec/faraday/request/instrumentation_spec.rb +0 -74
  35. data/spec/faraday/request/json_spec.rb +0 -199
  36. data/spec/faraday/request/url_encoded_spec.rb +0 -93
  37. data/spec/faraday/request_spec.rb +0 -119
  38. data/spec/faraday/response/json_spec.rb +0 -206
  39. data/spec/faraday/response/logger_spec.rb +0 -299
  40. data/spec/faraday/response/raise_error_spec.rb +0 -286
  41. data/spec/faraday/response_spec.rb +0 -84
  42. data/spec/faraday/utils/headers_spec.rb +0 -109
  43. data/spec/faraday/utils_spec.rb +0 -120
  44. data/spec/faraday_spec.rb +0 -69
  45. data/spec/spec_helper.rb +0 -142
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c06dbc8aa2e38f23986579d5521da86fb335aaf0f5aa83db6fefb530e645b9f
4
- data.tar.gz: 84db1e0a961b108e53db54277bc22f533a19f1abf3475384e09a396bf98969d1
3
+ metadata.gz: a700341d7d0844ede1248c479375ec0b3cb7fd51526c7c4abbaf9ce3db6c8a44
4
+ data.tar.gz: 76de6bace7c8995002b54ce7f19610a311418ef253c19a2fb354dee8ea986866
5
5
  SHA512:
6
- metadata.gz: dd4be1a725b5f287c0812ec1caa909c5338e938b697581d1bf585460fa47332d4dc840581edd726ff73fe2f4380fe65c45ba05fc92c98816954fe9ac356e0b23
7
- data.tar.gz: a508f88684b8f60c9655ebec4acfc9e7cebe25982e708fc98be02d46357304c73ef408de7bba4e2c52b12229c76168c51d3cb9cc1e5728901d580bd6e986dca0
6
+ metadata.gz: 5bc51dff2b4420115e15f36532fa2f9c2a31aa60d28d9acd864dcd4a50185e28fe5ac4dbeab648915d6a605e3606d06e207c083b3387a3253040bc9c108d6264
7
+ data.tar.gz: 82893f3f9d2152459d2a7b9b271601926e723a0723b7c3d20dc615795ce3d210473f2b6f359ec68fb4cd0f0a590ecae3121c325a257040c33701094cf56966fb
data/Rakefile CHANGED
@@ -1,12 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rspec/core/rake_task'
4
3
  require 'bundler'
5
4
 
6
5
  Bundler::GemHelper.install_tasks
7
6
 
8
- RSpec::Core::RakeTask.new(:spec) do |task|
9
- task.ruby_opts = %w[-W]
10
- end
7
+ # The publish workflow sets FARADAY_RELEASE because its bundle (release/Gemfile) doesn't include RSpec.
8
+ unless ENV['FARADAY_RELEASE']
9
+ require 'rspec/core/rake_task'
10
+
11
+ RSpec::Core::RakeTask.new(:spec) do |task|
12
+ task.ruby_opts = %w[-W]
13
+ # Integration specs need service containers, see .github/workflows/integration-tests.yml
14
+ task.exclude_pattern = 'spec/integration/**/*_spec.rb'
15
+ end
11
16
 
12
- task default: :spec
17
+ task default: :spec
18
+ end
@@ -300,7 +300,10 @@ module Faraday
300
300
  end
301
301
  end
302
302
 
303
- # Determine if this Faraday::Connection can make parallel requests.
303
+ # Determine if this Faraday::Connection is currently making parallel
304
+ # requests, i.e. if it is called inside an {#in_parallel} block.
305
+ # Use {#support_parallel?} to check if the adapter can make parallel
306
+ # requests at all.
304
307
  #
305
308
  # @return [Boolean]
306
309
  def in_parallel?
@@ -362,7 +365,7 @@ module Faraday
362
365
  #
363
366
  # conn.get("nigiri?page=2") # accesses https://httpbingo.org/api/nigiri
364
367
  def url_prefix=(url, encoder = nil)
365
- uri = @url_prefix = Utils.URI(url)
368
+ uri = @url_prefix = Utils.URI(url.to_s)
366
369
  self.path_prefix = uri.path
367
370
 
368
371
  params.merge_query(uri.query, encoder)
@@ -445,7 +448,7 @@ module Faraday
445
448
  req.options.proxy = proxy_for_request(url)
446
449
  req.url(url) if url
447
450
  req.headers.update(headers) if headers
448
- req.body = body if body
451
+ req.body = body unless body.nil?
449
452
  yield(req) if block_given?
450
453
  end
451
454
 
@@ -496,8 +499,6 @@ module Faraday
496
499
 
497
500
  # Creates a duplicate of this Faraday::Connection.
498
501
  #
499
- # @api private
500
- #
501
502
  # @return [Faraday::Connection]
502
503
  def dup
503
504
  self.class.new(build_exclusive_url,
@@ -560,7 +561,13 @@ module Faraday
560
561
  end
561
562
  end
562
563
 
563
- def support_parallel?(adapter)
564
+ # Determine if an adapter can make parallel requests.
565
+ #
566
+ # @param adapter [Class, nil] the adapter class to check. Defaults to the
567
+ # adapter of this Faraday::Connection.
568
+ #
569
+ # @return [Boolean]
570
+ def support_parallel?(adapter = @builder.adapter&.klass)
564
571
  adapter.respond_to?(:supports_parallel?) && adapter&.supports_parallel?
565
572
  end
566
573
  end
@@ -162,7 +162,7 @@ module Faraday
162
162
  end
163
163
 
164
164
  if depth.positive? && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
165
- hash.sort.map(&:last)
165
+ hash.sort_by { |key, _| key.to_i }.map(&:last)
166
166
  else
167
167
  hash
168
168
  end
data/lib/faraday/error.rb CHANGED
@@ -70,7 +70,7 @@ module Faraday
70
70
  # to `super`. See NilStatusError.
71
71
  def exc_msg_and_response!(exc, response = nil)
72
72
  if @response.nil? && @wrapped_exception.nil?
73
- @wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
73
+ @wrapped_exception, msg, @response = exc_msg_and_response(exc, omit_request_data(response))
74
74
  return msg
75
75
  end
76
76
 
@@ -85,7 +85,14 @@ module Faraday
85
85
  when Hash
86
86
  [nil, build_error_message_from_hash(exc), exc]
87
87
  when Faraday::Env
88
- [nil, build_error_message_from_env(exc), exc]
88
+ sanitized = omit_request_data(exc)
89
+ message =
90
+ if sanitized.equal?(exc)
91
+ build_error_message_from_env(exc)
92
+ else
93
+ build_error_message_from_hash(sanitized)
94
+ end
95
+ [nil, message, sanitized]
89
96
  else
90
97
  [nil, exc.to_s, response]
91
98
  end
@@ -93,6 +100,26 @@ module Faraday
93
100
 
94
101
  private
95
102
 
103
+ # When the RaiseError middleware opted this request out of request data
104
+ # (`include_request: false`), it marks the Env. Replace env-carrying
105
+ # responses (the Response object raised by the Json middleware, or an Env
106
+ # passed directly) with a request-less hash so that errors raised by other
107
+ # middlewares (e.g. ParsingError) cannot expose request headers and body.
108
+ # The body reads the explicit `response_body` member because `Env#body`
109
+ # aliases to the request body while the status is nil.
110
+ def omit_request_data(response)
111
+ env =
112
+ case response
113
+ when Faraday::Response
114
+ response.env if response.finished?
115
+ when Faraday::Env
116
+ response
117
+ end
118
+ return response unless env.is_a?(Faraday::Env) && env[:include_request] == false
119
+
120
+ { status: env.status, headers: env.response_headers, body: env.response_body }
121
+ end
122
+
96
123
  def build_error_message_from_hash(hash)
97
124
  # Be defensive with external Hash objects - they might be missing keys
98
125
  status = hash.fetch(:status, nil)
@@ -41,7 +41,7 @@ module Faraday
41
41
  def exception(exc)
42
42
  return unless log_errors?
43
43
 
44
- public_send(log_level) { "error: #{exc.full_message}" }
44
+ public_send(log_level) { "error: #{apply_filters(exc.full_message)}" }
45
45
 
46
46
  log_headers('error', exc.response_headers) if exc.respond_to?(:response_headers) && log_headers?(:error)
47
47
  return unless exc.respond_to?(:response_body) && exc.response_body && log_body?(:error)
@@ -186,7 +186,8 @@ module Faraday
186
186
  def [](key)
187
187
  key = key.to_sym
188
188
  if (method = self.class.memoized_attributes[key])
189
- super || (self[key] = instance_eval(&method))
189
+ value = super
190
+ value.nil? ? (self[key] = instance_eval(&method)) : value
190
191
  else
191
192
  super
192
193
  end
@@ -73,6 +73,9 @@ module Faraday
73
73
  super
74
74
  @adapter = original.adapter
75
75
  @handlers = original.handlers.dup
76
+ # @app is memoized on the first request. Drop it so the duplicate
77
+ # rebuilds from the copied handlers and middleware added after dup runs.
78
+ @app = nil
76
79
  end
77
80
 
78
81
  def build
@@ -20,7 +20,13 @@ module Faraday
20
20
  }.freeze
21
21
  # rubocop:enable Naming/ConstantName
22
22
 
23
- DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [] }.freeze
23
+ DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [].freeze }.freeze
24
+
25
+ def on_request(env)
26
+ # Announce the opt-out on the shared Env so that errors raised by other
27
+ # middlewares (e.g. ParsingError from the Json middleware) honor it too.
28
+ env[:include_request] = false unless options[:include_request]
29
+ end
24
30
 
25
31
  def on_complete(env)
26
32
  return if Array(options[:allowed_statuses]).include?(env[:status])
@@ -54,7 +54,9 @@ module Faraday
54
54
  raise 'response already finished' if finished?
55
55
 
56
56
  @env = env.is_a?(Env) ? env : Env.from(env)
57
- @on_complete_callbacks.each { |callback| callback.call(@env) }
57
+ callbacks = @on_complete_callbacks
58
+ @on_complete_callbacks = []
59
+ callbacks.each { |callback| callback.call(@env) }
58
60
  self
59
61
  end
60
62
 
@@ -76,7 +78,8 @@ module Faraday
76
78
  end
77
79
 
78
80
  def marshal_load(env)
79
- @env = Env.from(env)
81
+ @env = Env.from(env) if env
82
+ @on_complete_callbacks = []
80
83
  end
81
84
 
82
85
  # Expand the env with more properties, without overriding existing ones.
@@ -38,13 +38,11 @@ module Faraday
38
38
 
39
39
  # symbol -> string mapper + cache
40
40
  KeyMap = Hash.new do |map, key|
41
- value = if key.respond_to?(:to_str)
42
- key
43
- else
44
- key.to_s.split('_') # user_agent: %w(user agent)
45
- .each(&:capitalize!) # => %w(User Agent)
46
- .join('-') # => "User-Agent"
47
- end
41
+ next key if key.respond_to?(:to_str)
42
+
43
+ value = key.to_s.split('_') # user_agent: %w(user agent)
44
+ .each(&:capitalize!) # => %w(User Agent)
45
+ .join('-') # => "User-Agent"
48
46
  keymap_mutex.synchronize { map[key] = value }
49
47
  end
50
48
  KeyMap[:etag] = 'ETag'
@@ -62,6 +60,8 @@ module Faraday
62
60
  super
63
61
  end
64
62
 
63
+ alias store []=
64
+
65
65
  def fetch(key, ...)
66
66
  key = KeyMap[key]
67
67
  key = @names.fetch(key.downcase, key)
@@ -77,6 +77,28 @@ module Faraday
77
77
  super
78
78
  end
79
79
 
80
+ def clear
81
+ @names.clear
82
+ super
83
+ end
84
+
85
+ def shift = rebuilding_names { super }
86
+ def compact! = rebuilding_names { super }
87
+ def rehash = rebuilding_names { super }
88
+
89
+ # Without a block these return an enumerator that calls back into the
90
+ # method once iterated, so the index only needs rebuilding in that call.
91
+ def delete_if = block_given? ? rebuilding_names { super } : super
92
+ def keep_if = block_given? ? rebuilding_names { super } : super
93
+ def reject! = block_given? ? rebuilding_names { super } : super
94
+ def select! = block_given? ? rebuilding_names { super } : super
95
+ alias filter! select!
96
+
97
+ # A mapping hash is a valid alternative to a block.
98
+ def transform_keys!(*args, &block)
99
+ args.empty? && !block ? super : rebuilding_names { super }
100
+ end
101
+
80
102
  def dig(key, *rest)
81
103
  key = KeyMap[key]
82
104
  key = @names.fetch(key.downcase, key)
@@ -136,6 +158,18 @@ module Faraday
136
158
 
137
159
  private
138
160
 
161
+ # Runs the block, then rebuilds the name index even if the block raised
162
+ # or exited early, as the underlying Hash may already be partially mutated.
163
+ def rebuilding_names
164
+ yield
165
+ ensure
166
+ rebuild_names
167
+ end
168
+
169
+ def rebuild_names
170
+ @names = each_key.to_h { |key| [KeyMap[key].downcase, key] }
171
+ end
172
+
139
173
  # Join multiple values with a comma.
140
174
  def add_parsed(key, value)
141
175
  if key?(key)
data/lib/faraday/utils.rb CHANGED
@@ -27,10 +27,22 @@ module Faraday
27
27
 
28
28
  ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
29
29
 
30
+ # CGI.escape works on raw bytes: it matches ESCAPE_RE only where no multibyte character
31
+ # hides an ASCII byte (Shift_JIS 'ア' is "\x83A"), unlike ESCAPE_RE it doesn't raise on
32
+ # invalid bytes, and it returns String subclasses (SafeBuffer) as is when nothing is escaped.
33
+ CGI_ESCAPE_ENCODINGS = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::BINARY].freeze
34
+ private_constant :CGI_ESCAPE_ENCODINGS
35
+
30
36
  def escape(str)
31
- str.to_s.gsub(ESCAPE_RE) do |match|
32
- "%#{match.unpack('H2' * match.bytesize).join('%').upcase}"
33
- end.gsub(' ', default_space_encoding)
37
+ str = str.to_s
38
+ if str.instance_of?(String) && CGI_ESCAPE_ENCODINGS.include?(str.encoding) && str.valid_encoding?
39
+ escaped = CGI.escape(str)
40
+ default_space_encoding == '+' ? escaped : escaped.gsub('+', default_space_encoding)
41
+ else
42
+ str.gsub(ESCAPE_RE) do |match|
43
+ "%#{match.unpack('H2' * match.bytesize).join('%').upcase}"
44
+ end.gsub(' ', default_space_encoding)
45
+ end
34
46
  end
35
47
 
36
48
  def unescape(str)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- VERSION = '2.15.0'
4
+ VERSION = '2.15.1'
5
5
  end
@@ -3,6 +3,8 @@
3
3
  require 'webmock/rspec'
4
4
  WebMock.disable_net_connect!(allow_localhost: true)
5
5
 
6
+ # The gem ships only `spec/external_adapters` and `spec/support` (see `spec.files` in
7
+ # faraday.gemspec). Requiring a file from anywhere else means updating the gemspec too.
6
8
  require_relative '../support/helper_methods'
7
9
  require_relative '../support/disabling_stub'
8
10
  require_relative '../support/streaming_response_checker'
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Faraday
6
+ module IntegrationHelpers
7
+ # Throwaway certificate authority used by the SSL and proxy integration
8
+ # specs. Everything is generated in memory, so no certificate files need to
9
+ # be committed or generated by the CI workflow.
10
+ class TestPKI
11
+ Credentials = Struct.new(:cert, :key)
12
+
13
+ attr_reader :ca_cert, :ca_key
14
+
15
+ def initialize
16
+ @ca_key = OpenSSL::PKey::RSA.new(2048)
17
+ @ca_cert = build_certificate('Faraday Test CA', ca_key, ca_key, nil) do |cert, factory|
18
+ [
19
+ factory.create_extension('basicConstraints', 'CA:TRUE', true),
20
+ factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true),
21
+ factory.create_extension('subjectKeyIdentifier', 'hash', false)
22
+ ].each { |ext| cert.add_extension(ext) }
23
+ end
24
+ end
25
+
26
+ # Issues a certificate signed by this CA.
27
+ #
28
+ # @param common_name [String] certificate subject CN
29
+ # @param dns_names [Array<String>] DNS subjectAltNames
30
+ # @param ip_addresses [Array<String>] IP subjectAltNames
31
+ # @return [Credentials]
32
+ def issue(common_name, dns_names: [], ip_addresses: [])
33
+ key = OpenSSL::PKey::RSA.new(2048)
34
+ sans = dns_names.map { |name| "DNS:#{name}" } + ip_addresses.map { |ip| "IP:#{ip}" }
35
+
36
+ cert = build_certificate(common_name, key, ca_key, ca_cert) do |built, factory|
37
+ built.add_extension(factory.create_extension('basicConstraints', 'CA:FALSE', true))
38
+ built.add_extension(factory.create_extension('subjectAltName', sans.join(','), false)) if sans.any?
39
+ end
40
+
41
+ Credentials.new(cert, key)
42
+ end
43
+
44
+ # Writes the CA certificate to disk, as expected by the `ca_file` option.
45
+ def write_ca_file(path)
46
+ File.write(path, ca_cert.to_pem)
47
+ path
48
+ end
49
+
50
+ # @return [OpenSSL::X509::Store] a store that only trusts this CA
51
+ def cert_store
52
+ OpenSSL::X509::Store.new.tap { |store| store.add_cert(ca_cert) }
53
+ end
54
+
55
+ private
56
+
57
+ def build_certificate(common_name, key, signing_key, issuer_cert)
58
+ cert = OpenSSL::X509::Certificate.new
59
+ cert.version = 2
60
+ cert.serial = rand(1..(2**32))
61
+ cert.subject = OpenSSL::X509::Name.parse("/CN=#{common_name}")
62
+ cert.issuer = issuer_cert ? issuer_cert.subject : cert.subject
63
+ cert.public_key = key.public_key
64
+ cert.not_before = Time.now - 60
65
+ cert.not_after = Time.now + 3600
66
+
67
+ factory = OpenSSL::X509::ExtensionFactory.new
68
+ factory.subject_certificate = cert
69
+ factory.issuer_certificate = issuer_cert || cert
70
+ yield cert, factory
71
+
72
+ cert.sign(signing_key, OpenSSL::Digest.new('SHA256'))
73
+ cert
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'openssl'
5
+ require 'socket'
6
+
7
+ module Faraday
8
+ module IntegrationHelpers
9
+ # Minimal HTTPS server running in a background thread, used to exercise
10
+ # SSLOptions and ProxyOptions against real TLS handshakes.
11
+ #
12
+ # Every request is answered with a JSON document describing what the server
13
+ # saw, e.g. `{"path":"/","client_cert_subject":null,"remote_ip":"127.0.0.1"}`.
14
+ class TLSServer
15
+ attr_reader :port
16
+
17
+ # @param credentials [TestPKI::Credentials] server certificate and key
18
+ # @param client_ca [TestPKI, nil] when given, clients must present a
19
+ # certificate signed by this CA (mutual TLS)
20
+ # @param host [String] address to listen on; '0.0.0.0' makes the server
21
+ # reachable from service containers, such as the proxies
22
+ def initialize(credentials, client_ca: nil, host: '127.0.0.1')
23
+ @tcp_server = TCPServer.new(host, 0)
24
+ @port = @tcp_server.addr[1]
25
+ @ssl_server = OpenSSL::SSL::SSLServer.new(@tcp_server, ssl_context(credentials, client_ca))
26
+ @ssl_server.start_immediately = false
27
+ @threads = []
28
+ end
29
+
30
+ def self.start(...)
31
+ new(...).start
32
+ end
33
+
34
+ def start
35
+ @acceptor = Thread.new do
36
+ loop do
37
+ socket = @ssl_server.accept
38
+ @threads << Thread.new { handle(socket) }
39
+ end
40
+ rescue IOError, Errno::EBADF
41
+ nil # server stopped
42
+ end
43
+ self
44
+ end
45
+
46
+ def stop
47
+ @ssl_server.close
48
+ @acceptor&.join(1)
49
+ @threads.each { |thread| thread.join(1) }
50
+ end
51
+
52
+ def url(host = 'localhost')
53
+ "https://#{host}:#{port}"
54
+ end
55
+
56
+ private
57
+
58
+ def ssl_context(credentials, client_ca)
59
+ OpenSSL::SSL::SSLContext.new.tap do |ctx|
60
+ ctx.cert = credentials.cert
61
+ ctx.key = credentials.key
62
+ if client_ca
63
+ ctx.cert_store = client_ca.cert_store
64
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
65
+ end
66
+ end
67
+ end
68
+
69
+ def handle(socket)
70
+ socket.accept
71
+ request_line = socket.gets
72
+ while (line = socket.gets)
73
+ break if line.strip.empty?
74
+ end
75
+ respond(socket, request_line.to_s.split[1])
76
+ rescue OpenSSL::SSL::SSLError, SystemCallError, IOError
77
+ nil # the client rejected the handshake or went away
78
+ ensure
79
+ socket.close unless socket.closed?
80
+ end
81
+
82
+ def respond(socket, path)
83
+ body = JSON.generate(path: path, client_cert_subject: socket.peer_cert&.subject&.to_s,
84
+ remote_ip: socket.io.remote_address.ip_address)
85
+ socket.write("HTTP/1.1 200 OK\r\n" \
86
+ "Content-Type: application/json\r\n" \
87
+ "Content-Length: #{body.bytesize}\r\n" \
88
+ "Connection: close\r\n\r\n#{body}")
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Integration test configuration and helpers
4
+ #
5
+ # This module provides configuration and helpers for integration tests that run
6
+ # against real HTTP services in GitHub Actions service containers.
7
+ #
8
+ # Service URLs are accessed by service name in container jobs (no port mapping needed).
9
+ # See .github/workflows/integration-tests.yml for service configuration.
10
+
11
+ module Faraday
12
+ module IntegrationHelpers
13
+ # Service URLs for GitHub Actions service containers
14
+ # These are accessed by service name when running in container jobs
15
+ HTTPBIN_URL = ENV.fetch('HTTPBIN_URL', 'http://httpbin')
16
+ # Forward proxies (mitmdump); the second one requires Basic credentials
17
+ PROXY_URL = ENV.fetch('PROXY_URL', 'http://proxy:8080')
18
+ AUTH_PROXY_URL = ENV.fetch('AUTH_PROXY_URL', 'http://auth-proxy:8080')
19
+
20
+ # Create a direct Faraday connection to httpbin (no proxy)
21
+ #
22
+ # @param options [Hash] Connection options (merged with defaults)
23
+ # @yield [builder] Middleware stack configuration
24
+ # @return [Faraday::Connection]
25
+ def httpbin_connection(options = {}, &block)
26
+ Faraday.new(HTTPBIN_URL, options, &block)
27
+ end
28
+
29
+ # Generic helper to create a Faraday connection for integration tests
30
+ #
31
+ # @param url [String] Base URL for the connection
32
+ # @param options [Hash] Connection options
33
+ # @yield [builder] Middleware stack configuration
34
+ # @return [Faraday::Connection]
35
+ def create_integration_connection(url, options = {}, &block)
36
+ Faraday.new(url, options, &block)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Configure RSpec for integration tests
42
+ RSpec.configure do |config|
43
+ # Include integration helpers in integration tests
44
+ config.include Faraday::IntegrationHelpers, type: :integration
45
+
46
+ # Tag all integration tests for easy filtering
47
+ # Run with: bundle exec rspec --tag integration
48
+ # Skip with: bundle exec rspec --tag ~integration
49
+ config.define_derived_metadata(file_path: %r{/spec/integration/}) do |metadata|
50
+ metadata[:type] = :integration
51
+ metadata[:integration] = true
52
+ end
53
+
54
+ # Integration tests may take longer than unit tests
55
+ config.around(:each, type: :integration) do |example|
56
+ # Completely disable WebMock for integration tests
57
+ # (WebMock can interfere with streaming even when "allowed")
58
+ WebMock.disable!
59
+
60
+ begin
61
+ # Set a reasonable timeout for integration tests (30 seconds)
62
+ Timeout.timeout(30) do
63
+ example.run
64
+ end
65
+ ensure
66
+ # Re-enable WebMock for unit tests
67
+ WebMock.enable!
68
+ WebMock.disable_net_connect!(allow_localhost: true)
69
+ end
70
+ end
71
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faraday
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.0
4
+ version: 2.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@technoweenie"
@@ -104,37 +104,13 @@ files:
104
104
  - lib/faraday/utils/params_hash.rb
105
105
  - lib/faraday/version.rb
106
106
  - spec/external_adapters/faraday_specs_setup.rb
107
- - spec/faraday/adapter/test_spec.rb
108
- - spec/faraday/adapter_registry_spec.rb
109
- - spec/faraday/adapter_spec.rb
110
- - spec/faraday/connection_spec.rb
111
- - spec/faraday/error_spec.rb
112
- - spec/faraday/middleware_registry_spec.rb
113
- - spec/faraday/middleware_spec.rb
114
- - spec/faraday/options/env_spec.rb
115
- - spec/faraday/options/options_spec.rb
116
- - spec/faraday/options/proxy_options_spec.rb
117
- - spec/faraday/options/request_options_spec.rb
118
- - spec/faraday/params_encoders/flat_spec.rb
119
- - spec/faraday/params_encoders/nested_spec.rb
120
- - spec/faraday/rack_builder_spec.rb
121
- - spec/faraday/request/authorization_spec.rb
122
- - spec/faraday/request/instrumentation_spec.rb
123
- - spec/faraday/request/json_spec.rb
124
- - spec/faraday/request/url_encoded_spec.rb
125
- - spec/faraday/request_spec.rb
126
- - spec/faraday/response/json_spec.rb
127
- - spec/faraday/response/logger_spec.rb
128
- - spec/faraday/response/raise_error_spec.rb
129
- - spec/faraday/response_spec.rb
130
- - spec/faraday/utils/headers_spec.rb
131
- - spec/faraday/utils_spec.rb
132
- - spec/faraday_spec.rb
133
- - spec/spec_helper.rb
134
107
  - spec/support/disabling_stub.rb
135
108
  - spec/support/fake_safe_buffer.rb
136
109
  - spec/support/faraday_middleware_subclasses.rb
137
110
  - spec/support/helper_methods.rb
111
+ - spec/support/integration/test_pki.rb
112
+ - spec/support/integration/tls_server.rb
113
+ - spec/support/integration_helper.rb
138
114
  - spec/support/shared_examples/adapter.rb
139
115
  - spec/support/shared_examples/params_encoder.rb
140
116
  - spec/support/shared_examples/request_method.rb
@@ -144,7 +120,7 @@ licenses:
144
120
  - MIT
145
121
  metadata:
146
122
  homepage_uri: https://lostisland.github.io/faraday
147
- changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.15.0
123
+ changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.15.1
148
124
  source_code_uri: https://github.com/lostisland/faraday
149
125
  bug_tracker_uri: https://github.com/lostisland/faraday/issues
150
126
  rubygems_mfa_required: 'true'