faraday 0.17.6 → 1.0.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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -8
  3. data/LICENSE.md +1 -1
  4. data/README.md +18 -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/adapter/em_http.rb +142 -99
  9. data/lib/faraday/adapter/em_http_ssl_patch.rb +24 -18
  10. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +18 -15
  11. data/lib/faraday/adapter/em_synchrony.rb +104 -60
  12. data/lib/faraday/adapter/excon.rb +98 -56
  13. data/lib/faraday/adapter/httpclient.rb +83 -59
  14. data/lib/faraday/adapter/net_http.rb +129 -63
  15. data/lib/faraday/adapter/net_http_persistent.rb +50 -27
  16. data/lib/faraday/adapter/patron.rb +80 -43
  17. data/lib/faraday/adapter/rack.rb +30 -13
  18. data/lib/faraday/adapter/test.rb +86 -53
  19. data/lib/faraday/adapter/typhoeus.rb +4 -1
  20. data/lib/faraday/adapter.rb +82 -22
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +47 -36
  23. data/lib/faraday/connection.rb +312 -182
  24. data/lib/faraday/dependency_loader.rb +37 -0
  25. data/lib/faraday/encoders/flat_params_encoder.rb +98 -0
  26. data/lib/faraday/encoders/nested_params_encoder.rb +171 -0
  27. data/lib/faraday/error.rb +9 -35
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/middleware.rb +12 -28
  31. data/lib/faraday/middleware_registry.rb +129 -0
  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/options.rb +32 -183
  38. data/lib/faraday/param_part.rb +53 -0
  39. data/lib/faraday/parameters.rb +4 -197
  40. data/lib/faraday/rack_builder.rb +66 -55
  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 +79 -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/request.rb +68 -38
  49. data/lib/faraday/response/logger.rb +22 -69
  50. data/lib/faraday/response/raise_error.rb +38 -18
  51. data/lib/faraday/response.rb +24 -14
  52. data/lib/faraday/utils/headers.rb +139 -0
  53. data/lib/faraday/utils/params_hash.rb +61 -0
  54. data/lib/faraday/utils.rb +36 -245
  55. data/lib/faraday.rb +94 -175
  56. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  57. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  58. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  59. data/spec/faraday/adapter/excon_spec.rb +49 -0
  60. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  61. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  62. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  63. data/spec/faraday/adapter/patron_spec.rb +18 -0
  64. data/spec/faraday/adapter/rack_spec.rb +8 -0
  65. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  66. data/spec/faraday/adapter_registry_spec.rb +28 -0
  67. data/spec/faraday/adapter_spec.rb +55 -0
  68. data/spec/faraday/composite_read_io_spec.rb +80 -0
  69. data/spec/faraday/connection_spec.rb +691 -0
  70. data/spec/faraday/error_spec.rb +0 -57
  71. data/spec/faraday/middleware_spec.rb +26 -0
  72. data/spec/faraday/options/env_spec.rb +70 -0
  73. data/spec/faraday/options/options_spec.rb +297 -0
  74. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  75. data/spec/faraday/options/request_options_spec.rb +19 -0
  76. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  77. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  78. data/spec/faraday/rack_builder_spec.rb +196 -0
  79. data/spec/faraday/request/authorization_spec.rb +88 -0
  80. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  81. data/spec/faraday/request/multipart_spec.rb +274 -0
  82. data/spec/faraday/request/retry_spec.rb +242 -0
  83. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  84. data/spec/faraday/request_spec.rb +109 -0
  85. data/spec/faraday/response/logger_spec.rb +220 -0
  86. data/spec/faraday/response/middleware_spec.rb +68 -0
  87. data/spec/faraday/response/raise_error_spec.rb +15 -15
  88. data/spec/faraday/response_spec.rb +75 -0
  89. data/spec/faraday/utils/headers_spec.rb +82 -0
  90. data/spec/faraday/utils_spec.rb +56 -0
  91. data/spec/faraday_spec.rb +37 -0
  92. data/spec/spec_helper.rb +63 -36
  93. data/spec/support/disabling_stub.rb +14 -0
  94. data/spec/support/fake_safe_buffer.rb +15 -0
  95. data/spec/support/helper_methods.rb +133 -0
  96. data/spec/support/shared_examples/adapter.rb +104 -0
  97. data/spec/support/shared_examples/params_encoder.rb +18 -0
  98. data/spec/support/shared_examples/request_method.rb +234 -0
  99. data/spec/support/streaming_response_checker.rb +35 -0
  100. data/spec/support/webmock_rack_app.rb +68 -0
  101. metadata +66 -38
  102. data/lib/faraday/deprecate.rb +0 -109
  103. data/lib/faraday/upload_io.rb +0 -77
  104. data/spec/faraday/deprecate_spec.rb +0 -147
  105. data/test/adapters/default_test.rb +0 -14
  106. data/test/adapters/em_http_test.rb +0 -30
  107. data/test/adapters/em_synchrony_test.rb +0 -32
  108. data/test/adapters/excon_test.rb +0 -30
  109. data/test/adapters/httpclient_test.rb +0 -34
  110. data/test/adapters/integration.rb +0 -263
  111. data/test/adapters/logger_test.rb +0 -136
  112. data/test/adapters/net_http_persistent_test.rb +0 -114
  113. data/test/adapters/net_http_test.rb +0 -79
  114. data/test/adapters/patron_test.rb +0 -40
  115. data/test/adapters/rack_test.rb +0 -38
  116. data/test/adapters/test_middleware_test.rb +0 -157
  117. data/test/adapters/typhoeus_test.rb +0 -38
  118. data/test/authentication_middleware_test.rb +0 -65
  119. data/test/composite_read_io_test.rb +0 -109
  120. data/test/connection_test.rb +0 -738
  121. data/test/env_test.rb +0 -268
  122. data/test/helper.rb +0 -75
  123. data/test/live_server.rb +0 -67
  124. data/test/middleware/instrumentation_test.rb +0 -88
  125. data/test/middleware/retry_test.rb +0 -282
  126. data/test/middleware_stack_test.rb +0 -260
  127. data/test/multibyte.txt +0 -1
  128. data/test/options_test.rb +0 -333
  129. data/test/parameters_test.rb +0 -157
  130. data/test/request_middleware_test.rb +0 -126
  131. data/test/response_middleware_test.rb +0 -72
  132. data/test/strawberry.rb +0 -2
  133. data/test/utils_test.rb +0 -98
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::EMHttp, unless: defined?(JRUBY_VERSION) do
4
+ features :request_body_on_query_methods, :reason_phrase_parse, :trace_method,
5
+ :skip_response_body_on_head, :parallel, :local_socket_binding
6
+
7
+ it_behaves_like 'an adapter'
8
+
9
+ it 'allows to provide adapter specific configs' do
10
+ url = URI('https://example.com:1234')
11
+ adapter = described_class.new nil, inactivity_timeout: 20
12
+ req = adapter.create_request(url: url, request: {})
13
+
14
+ expect(req.connopts.inactivity_timeout).to eq(20)
15
+ end
16
+
17
+ context 'Options' do
18
+ let(:request) { Faraday::RequestOptions.new }
19
+ let(:env) { { request: request } }
20
+ let(:options) { {} }
21
+ let(:adapter) { Faraday::Adapter::EMHttp.new }
22
+
23
+ it 'configures timeout' do
24
+ request.timeout = 5
25
+ adapter.configure_timeout(options, env)
26
+ expect(options[:inactivity_timeout]).to eq(5)
27
+ expect(options[:connect_timeout]).to eq(5)
28
+ end
29
+
30
+ it 'configures timeout and open_timeout' do
31
+ request.timeout = 5
32
+ request.open_timeout = 1
33
+ adapter.configure_timeout(options, env)
34
+ expect(options[:inactivity_timeout]).to eq(5)
35
+ expect(options[:connect_timeout]).to eq(1)
36
+ end
37
+
38
+ it 'configures all timeout settings' do
39
+ request.timeout = 5
40
+ request.read_timeout = 3
41
+ request.open_timeout = 1
42
+ adapter.configure_timeout(options, env)
43
+ expect(options[:inactivity_timeout]).to eq(3)
44
+ expect(options[:connect_timeout]).to eq(1)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::EMSynchrony, unless: defined?(JRUBY_VERSION) do
4
+ features :request_body_on_query_methods, :reason_phrase_parse,
5
+ :skip_response_body_on_head, :parallel, :local_socket_binding
6
+
7
+ it_behaves_like 'an adapter'
8
+
9
+ it 'allows to provide adapter specific configs' do
10
+ url = URI('https://example.com:1234')
11
+ adapter = described_class.new nil, inactivity_timeout: 20
12
+ req = adapter.create_request(url: url, request: {})
13
+
14
+ expect(req.connopts.inactivity_timeout).to eq(20)
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::Excon do
4
+ features :request_body_on_query_methods, :reason_phrase_parse, :trace_method
5
+
6
+ it_behaves_like 'an adapter'
7
+
8
+ it 'allows to provide adapter specific configs' do
9
+ url = URI('https://example.com:1234')
10
+
11
+ adapter = described_class.new(nil, debug_request: true)
12
+
13
+ conn = adapter.build_connection(url: url)
14
+
15
+ expect(conn.data[:debug_request]).to be_truthy
16
+ end
17
+
18
+ context 'config' do
19
+ let(:adapter) { Faraday::Adapter::Excon.new }
20
+ let(:request) { Faraday::RequestOptions.new }
21
+ let(:uri) { URI.parse('https://example.com') }
22
+ let(:env) { { request: request, url: uri } }
23
+
24
+ it 'sets timeout' do
25
+ request.timeout = 5
26
+ options = adapter.send(:opts_from_env, env)
27
+ expect(options[:read_timeout]).to eq(5)
28
+ expect(options[:write_timeout]).to eq(5)
29
+ expect(options[:connect_timeout]).to eq(5)
30
+ end
31
+
32
+ it 'sets timeout and open_timeout' do
33
+ request.timeout = 5
34
+ request.open_timeout = 3
35
+ options = adapter.send(:opts_from_env, env)
36
+ expect(options[:read_timeout]).to eq(5)
37
+ expect(options[:write_timeout]).to eq(5)
38
+ expect(options[:connect_timeout]).to eq(3)
39
+ end
40
+
41
+ it 'sets open_timeout' do
42
+ request.open_timeout = 3
43
+ options = adapter.send(:opts_from_env, env)
44
+ expect(options[:read_timeout]).to eq(nil)
45
+ expect(options[:write_timeout]).to eq(nil)
46
+ expect(options[:connect_timeout]).to eq(3)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::HTTPClient do
4
+ # ruby gem defaults for testing purposes
5
+ HTTPCLIENT_OPEN = 60
6
+ HTTPCLIENT_READ = 60
7
+ HTTPCLIENT_WRITE = 120
8
+
9
+ features :request_body_on_query_methods, :reason_phrase_parse, :compression,
10
+ :trace_method, :local_socket_binding
11
+
12
+ it_behaves_like 'an adapter'
13
+
14
+ it 'allows to provide adapter specific configs' do
15
+ adapter = described_class.new do |client|
16
+ client.keep_alive_timeout = 20
17
+ client.ssl_config.timeout = 25
18
+ end
19
+
20
+ client = adapter.build_connection(url: URI.parse('https://example.com'))
21
+ expect(client.keep_alive_timeout).to eq(20)
22
+ expect(client.ssl_config.timeout).to eq(25)
23
+ end
24
+
25
+ context 'Options' do
26
+ let(:request) { Faraday::RequestOptions.new }
27
+ let(:env) { { request: request } }
28
+ let(:options) { {} }
29
+ let(:adapter) { Faraday::Adapter::HTTPClient.new }
30
+ let(:client) { adapter.connection(url: URI.parse('https://example.com')) }
31
+
32
+ it 'configures timeout' do
33
+ assert_default_timeouts!
34
+
35
+ request.timeout = 5
36
+ adapter.configure_timeouts(client, request)
37
+
38
+ expect(client.connect_timeout).to eq(5)
39
+ expect(client.send_timeout).to eq(5)
40
+ expect(client.receive_timeout).to eq(5)
41
+ end
42
+
43
+ it 'configures open timeout' do
44
+ assert_default_timeouts!
45
+
46
+ request.open_timeout = 1
47
+ adapter.configure_timeouts(client, request)
48
+
49
+ expect(client.connect_timeout).to eq(1)
50
+ expect(client.send_timeout).to eq(HTTPCLIENT_WRITE)
51
+ expect(client.receive_timeout).to eq(HTTPCLIENT_READ)
52
+ end
53
+
54
+ it 'configures multiple timeouts' do
55
+ assert_default_timeouts!
56
+
57
+ request.open_timeout = 1
58
+ request.write_timeout = 10
59
+ request.read_timeout = 5
60
+ adapter.configure_timeouts(client, request)
61
+
62
+ expect(client.connect_timeout).to eq(1)
63
+ expect(client.send_timeout).to eq(10)
64
+ expect(client.receive_timeout).to eq(5)
65
+ end
66
+
67
+ def assert_default_timeouts!
68
+ expect(client.connect_timeout).to eq(HTTPCLIENT_OPEN)
69
+ expect(client.send_timeout).to eq(HTTPCLIENT_WRITE)
70
+ expect(client.receive_timeout).to eq(HTTPCLIENT_READ)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::NetHttpPersistent do
4
+ features :request_body_on_query_methods, :reason_phrase_parse, :compression, :trace_method
5
+
6
+ it_behaves_like 'an adapter'
7
+
8
+ it 'allows to provide adapter specific configs' do
9
+ url = URI('https://example.com')
10
+
11
+ adapter = described_class.new do |http|
12
+ http.idle_timeout = 123
13
+ end
14
+
15
+ http = adapter.send(:connection, url: url, request: {})
16
+ adapter.send(:configure_request, http, {})
17
+
18
+ expect(http.idle_timeout).to eq(123)
19
+ end
20
+
21
+ it 'sets max_retries to 0' do
22
+ url = URI('http://example.com')
23
+
24
+ adapter = described_class.new
25
+
26
+ http = adapter.send(:connection, url: url, request: {})
27
+ adapter.send(:configure_request, http, {})
28
+
29
+ # `max_retries=` is only present in Ruby 2.5
30
+ expect(http.max_retries).to eq(0) if http.respond_to?(:max_retries=)
31
+ end
32
+
33
+ it 'allows to set pool_size on initialize' do
34
+ url = URI('https://example.com')
35
+
36
+ adapter = described_class.new(nil, pool_size: 5)
37
+
38
+ http = adapter.send(:connection, url: url, request: {})
39
+
40
+ # `pool` is only present in net_http_persistent >= 3.0
41
+ expect(http.pool.size).to eq(5) if http.respond_to?(:pool)
42
+ end
43
+
44
+ context 'min_version' do
45
+ it 'allows to set min_version in SSL settings' do
46
+ url = URI('https://example.com')
47
+
48
+ adapter = described_class.new(nil)
49
+
50
+ http = adapter.send(:connection, url: url, request: {})
51
+ adapter.send(:configure_ssl, http, min_version: :TLS1_2)
52
+
53
+ # `min_version` is only present in net_http_persistent >= 3.1 (UNRELEASED)
54
+ expect(http.min_version).to eq(:TLS1_2) if http.respond_to?(:min_version)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::NetHttp do
4
+ features :request_body_on_query_methods, :reason_phrase_parse, :compression, :streaming, :trace_method
5
+
6
+ it_behaves_like 'an adapter'
7
+
8
+ context 'checking http' do
9
+ let(:url) { URI('http://example.com') }
10
+ let(:adapter) { described_class.new }
11
+ let(:http) { adapter.send(:connection, url: url, request: {}) }
12
+
13
+ it { expect(http.port).to eq(80) }
14
+
15
+ it 'sets max_retries to 0' do
16
+ adapter.send(:configure_request, http, {})
17
+
18
+ expect(http.max_retries).to eq(0) if http.respond_to?(:max_retries=)
19
+ end
20
+
21
+ it 'supports write_timeout' do
22
+ adapter.send(:configure_request, http, write_timeout: 10)
23
+
24
+ expect(http.write_timeout).to eq(10) if http.respond_to?(:write_timeout=)
25
+ end
26
+
27
+ it 'supports open_timeout' do
28
+ adapter.send(:configure_request, http, open_timeout: 10)
29
+
30
+ expect(http.open_timeout).to eq(10)
31
+ end
32
+
33
+ it 'supports read_timeout' do
34
+ adapter.send(:configure_request, http, read_timeout: 10)
35
+
36
+ expect(http.read_timeout).to eq(10)
37
+ end
38
+
39
+ context 'with https url' do
40
+ let(:url) { URI('https://example.com') }
41
+
42
+ it { expect(http.port).to eq(443) }
43
+ end
44
+
45
+ context 'with http url including port' do
46
+ let(:url) { URI('https://example.com:1234') }
47
+
48
+ it { expect(http.port).to eq(1234) }
49
+ end
50
+
51
+ context 'with custom adapter config' do
52
+ let(:adapter) do
53
+ described_class.new do |http|
54
+ http.continue_timeout = 123
55
+ end
56
+ end
57
+
58
+ it do
59
+ adapter.send(:configure_request, http, {})
60
+ expect(http.continue_timeout).to eq(123)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::Patron, unless: defined?(JRUBY_VERSION) do
4
+ features :reason_phrase_parse
5
+
6
+ it_behaves_like 'an adapter'
7
+
8
+ it 'allows to provide adapter specific configs' do
9
+ conn = Faraday.new do |f|
10
+ f.adapter :patron do |session|
11
+ session.max_redirects = 10
12
+ raise 'Configuration block called'
13
+ end
14
+ end
15
+
16
+ expect { conn.get('/') }.to raise_error(RuntimeError, 'Configuration block called')
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::Rack do
4
+ features :request_body_on_query_methods, :trace_method,
5
+ :skip_response_body_on_head
6
+
7
+ it_behaves_like 'an adapter', adapter_options: WebmockRackApp.new
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter::Typhoeus do
4
+ features :request_body_on_query_methods, :parallel, :trace_method
5
+
6
+ it_behaves_like 'an adapter'
7
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::AdapterRegistry do
4
+ describe '#initialize' do
5
+ subject(:registry) { described_class.new }
6
+
7
+ it { expect { registry.get(:FinFangFoom) }.to raise_error(NameError) }
8
+ it { expect { registry.get('FinFangFoom') }.to raise_error(NameError) }
9
+
10
+ it 'looks up class by string name' do
11
+ expect(registry.get('Faraday::Connection')).to eq(Faraday::Connection)
12
+ end
13
+
14
+ it 'looks up class by symbol name' do
15
+ expect(registry.get(:Faraday)).to eq(Faraday)
16
+ end
17
+
18
+ it 'caches lookups with implicit name' do
19
+ registry.set :symbol
20
+ expect(registry.get('symbol')).to eq(:symbol)
21
+ end
22
+
23
+ it 'caches lookups with explicit name' do
24
+ registry.set 'string', :name
25
+ expect(registry.get(:name)).to eq('string')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Adapter do
4
+ let(:adapter) { Faraday::Adapter.new }
5
+ let(:request) { {} }
6
+
7
+ context '#request_timeout' do
8
+ it 'gets :read timeout' do
9
+ expect(timeout(:read)).to eq(nil)
10
+
11
+ request[:timeout] = 5
12
+ request[:write_timeout] = 1
13
+
14
+ expect(timeout(:read)).to eq(5)
15
+
16
+ request[:read_timeout] = 2
17
+
18
+ expect(timeout(:read)).to eq(2)
19
+ end
20
+
21
+ it 'gets :open timeout' do
22
+ expect(timeout(:open)).to eq(nil)
23
+
24
+ request[:timeout] = 5
25
+ request[:write_timeout] = 1
26
+
27
+ expect(timeout(:open)).to eq(5)
28
+
29
+ request[:open_timeout] = 2
30
+
31
+ expect(timeout(:open)).to eq(2)
32
+ end
33
+
34
+ it 'gets :write timeout' do
35
+ expect(timeout(:write)).to eq(nil)
36
+
37
+ request[:timeout] = 5
38
+ request[:read_timeout] = 1
39
+
40
+ expect(timeout(:write)).to eq(5)
41
+
42
+ request[:write_timeout] = 2
43
+
44
+ expect(timeout(:write)).to eq(2)
45
+ end
46
+
47
+ it 'attempts unknown timeout type' do
48
+ expect { timeout(:unknown) }.to raise_error(ArgumentError)
49
+ end
50
+
51
+ def timeout(type)
52
+ adapter.send(:request_timeout, type, request)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ RSpec.describe Faraday::CompositeReadIO do
6
+ Part = Struct.new(:to_io) do
7
+ def length
8
+ to_io.string.length
9
+ end
10
+ end
11
+
12
+ def part(str)
13
+ Part.new StringIO.new(str)
14
+ end
15
+
16
+ def composite_io(*parts)
17
+ Faraday::CompositeReadIO.new(*parts)
18
+ end
19
+
20
+ context 'with empty composite_io' do
21
+ subject { composite_io }
22
+
23
+ it { expect(subject.length).to eq(0) }
24
+ it { expect(subject.read).to eq('') }
25
+ it { expect(subject.read(1)).to be_nil }
26
+ end
27
+
28
+ context 'with empty parts' do
29
+ subject { composite_io(part(''), part('')) }
30
+
31
+ it { expect(subject.length).to eq(0) }
32
+ it { expect(subject.read).to eq('') }
33
+ it { expect(subject.read(1)).to be_nil }
34
+ end
35
+
36
+ context 'with 2 parts' do
37
+ subject { composite_io(part('abcd'), part('1234')) }
38
+
39
+ it { expect(subject.length).to eq(8) }
40
+ it { expect(subject.read).to eq('abcd1234') }
41
+ it 'allows to read in chunks' do
42
+ expect(subject.read(3)).to eq('abc')
43
+ expect(subject.read(3)).to eq('d12')
44
+ expect(subject.read(3)).to eq('34')
45
+ expect(subject.read(3)).to be_nil
46
+ end
47
+ it 'allows to rewind while reading in chunks' do
48
+ expect(subject.read(3)).to eq('abc')
49
+ expect(subject.read(3)).to eq('d12')
50
+ subject.rewind
51
+ expect(subject.read(3)).to eq('abc')
52
+ expect(subject.read(5)).to eq('d1234')
53
+ expect(subject.read(3)).to be_nil
54
+ subject.rewind
55
+ expect(subject.read(2)).to eq('ab')
56
+ end
57
+ end
58
+
59
+ context 'with mix of empty and non-empty parts' do
60
+ subject { composite_io(part(''), part('abcd'), part(''), part('1234'), part('')) }
61
+
62
+ it 'allows to read in chunks' do
63
+ expect(subject.read(6)).to eq('abcd12')
64
+ expect(subject.read(6)).to eq('34')
65
+ expect(subject.read(6)).to be_nil
66
+ end
67
+ end
68
+
69
+ context 'with utf8 multibyte part' do
70
+ subject { composite_io(part("\x86"), part('ファイル')) }
71
+
72
+ it { expect(subject.read).to eq(String.new("\x86\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB", encoding: 'BINARY')) }
73
+ it 'allows to read in chunks' do
74
+ expect(subject.read(3)).to eq(String.new("\x86\xE3\x83", encoding: 'BINARY'))
75
+ expect(subject.read(3)).to eq(String.new("\x95\xE3\x82", encoding: 'BINARY'))
76
+ expect(subject.read(8)).to eq(String.new("\xA1\xE3\x82\xA4\xE3\x83\xAB", encoding: 'BINARY'))
77
+ expect(subject.read(3)).to be_nil
78
+ end
79
+ end
80
+ end