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,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::RackBuilder do
4
+ # mock handler classes
5
+ (Handler = Struct.new(:app)).class_eval do
6
+ def call(env)
7
+ env[:request_headers]['X-Middleware'] ||= ''
8
+ env[:request_headers]['X-Middleware'] += ":#{self.class.name.split('::').last}"
9
+ app.call(env)
10
+ end
11
+ end
12
+
13
+ class Apple < Handler
14
+ end
15
+ class Orange < Handler
16
+ end
17
+ class Banana < Handler
18
+ end
19
+
20
+ class Broken < Faraday::Middleware
21
+ dependency 'zomg/i_dont/exist'
22
+ end
23
+
24
+ subject { conn.builder }
25
+
26
+ context 'with default stack' do
27
+ let(:conn) { Faraday::Connection.new }
28
+
29
+ it { expect(subject[0]).to eq(Faraday::Request.lookup_middleware(:url_encoded)) }
30
+ it { expect(subject.adapter).to eq(Faraday::Adapter.lookup_middleware(Faraday.default_adapter)) }
31
+ end
32
+
33
+ context 'with custom empty block' do
34
+ let(:conn) { Faraday::Connection.new {} }
35
+
36
+ it { expect(subject[0]).to be_nil }
37
+ it { expect(subject.adapter).to eq(Faraday::Adapter.lookup_middleware(Faraday.default_adapter)) }
38
+ end
39
+
40
+ context 'with custom adapter only' do
41
+ let(:conn) do
42
+ Faraday::Connection.new do |builder|
43
+ builder.adapter :test do |stub|
44
+ stub.get('/') { |_| [200, {}, ''] }
45
+ end
46
+ end
47
+ end
48
+
49
+ it { expect(subject[0]).to be_nil }
50
+ it { expect(subject.adapter).to eq(Faraday::Adapter.lookup_middleware(:test)) }
51
+ end
52
+
53
+ context 'with custom handler and adapter' do
54
+ let(:conn) do
55
+ Faraday::Connection.new do |builder|
56
+ builder.use Apple
57
+ builder.adapter :test do |stub|
58
+ stub.get('/') { |_| [200, {}, ''] }
59
+ end
60
+ end
61
+ end
62
+
63
+ it 'locks the stack after making a request' do
64
+ expect(subject.locked?).to be_falsey
65
+ conn.get('/')
66
+ expect(subject.locked?).to be_truthy
67
+ expect { subject.use(Orange) }.to raise_error(Faraday::RackBuilder::StackLocked)
68
+ end
69
+
70
+ it 'dup stack is unlocked' do
71
+ expect(subject.locked?).to be_falsey
72
+ subject.lock!
73
+ expect(subject.locked?).to be_truthy
74
+ dup = subject.dup
75
+ expect(dup).to eq(subject)
76
+ expect(dup.locked?).to be_falsey
77
+ end
78
+
79
+ it 'allows to compare handlers' do
80
+ expect(subject.handlers.first).to eq(Faraday::RackBuilder::Handler.new(Apple))
81
+ end
82
+ end
83
+
84
+ context 'when having a single handler' do
85
+ let(:conn) { Faraday::Connection.new {} }
86
+
87
+ before { subject.use(Apple) }
88
+
89
+ it { expect(subject.handlers).to eq([Apple]) }
90
+
91
+ it 'allows rebuilding' do
92
+ subject.build do |builder|
93
+ builder.use(Orange)
94
+ end
95
+ expect(subject.handlers).to eq([Orange])
96
+ end
97
+
98
+ it 'allows use' do
99
+ subject.use(Orange)
100
+ expect(subject.handlers).to eq([Apple, Orange])
101
+ end
102
+
103
+ it 'allows insert_before' do
104
+ subject.insert_before(Apple, Orange)
105
+ expect(subject.handlers).to eq([Orange, Apple])
106
+ end
107
+
108
+ it 'allows insert_after' do
109
+ subject.insert_after(Apple, Orange)
110
+ expect(subject.handlers).to eq([Apple, Orange])
111
+ end
112
+
113
+ it 'raises an error trying to use an unregistered symbol' do
114
+ expect { subject.use(:apple) }.to raise_error(Faraday::Error) do |err|
115
+ expect(err.message).to eq(':apple is not registered on Faraday::Middleware')
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'with custom registered middleware' do
121
+ let(:conn) { Faraday::Connection.new {} }
122
+
123
+ after { Faraday::Middleware.unregister_middleware(:apple) }
124
+
125
+ it 'allows to register with constant' do
126
+ Faraday::Middleware.register_middleware(apple: Apple)
127
+ subject.use(:apple)
128
+ expect(subject.handlers).to eq([Apple])
129
+ end
130
+
131
+ it 'allows to register with symbol' do
132
+ Faraday::Middleware.register_middleware(apple: :Apple)
133
+ subject.use(:apple)
134
+ expect(subject.handlers).to eq([Apple])
135
+ end
136
+
137
+ it 'allows to register with string' do
138
+ Faraday::Middleware.register_middleware(apple: 'Apple')
139
+ subject.use(:apple)
140
+ expect(subject.handlers).to eq([Apple])
141
+ end
142
+
143
+ it 'allows to register with Proc' do
144
+ Faraday::Middleware.register_middleware(apple: -> { Apple })
145
+ subject.use(:apple)
146
+ expect(subject.handlers).to eq([Apple])
147
+ end
148
+ end
149
+
150
+ context 'when having two handlers' do
151
+ let(:conn) { Faraday::Connection.new {} }
152
+
153
+ before do
154
+ subject.use(Apple)
155
+ subject.use(Orange)
156
+ end
157
+
158
+ it 'allows insert_before' do
159
+ subject.insert_before(Orange, Banana)
160
+ expect(subject.handlers).to eq([Apple, Banana, Orange])
161
+ end
162
+
163
+ it 'allows insert_after' do
164
+ subject.insert_after(Apple, Banana)
165
+ expect(subject.handlers).to eq([Apple, Banana, Orange])
166
+ end
167
+
168
+ it 'allows to swap handlers' do
169
+ subject.swap(Apple, Banana)
170
+ expect(subject.handlers).to eq([Banana, Orange])
171
+ end
172
+
173
+ it 'allows to delete a handler' do
174
+ subject.delete(Apple)
175
+ expect(subject.handlers).to eq([Orange])
176
+ end
177
+ end
178
+
179
+ context 'when having a handler with broken dependency' do
180
+ let(:conn) do
181
+ Faraday::Connection.new do |builder|
182
+ builder.adapter :test do |stub|
183
+ stub.get('/') { |_| [200, {}, ''] }
184
+ end
185
+ end
186
+ end
187
+
188
+ before { subject.use(Broken) }
189
+
190
+ it 'raises an error while making a request' do
191
+ expect { conn.get('/') }.to raise_error(RuntimeError) do |err|
192
+ expect(err.message).to match(%r{missing dependency for Broken: .+ -- zomg/i_dont/exist})
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Request::Authorization do
4
+ let(:conn) do
5
+ Faraday.new do |b|
6
+ b.request auth_type, *auth_config
7
+ b.adapter :test do |stub|
8
+ stub.get('/auth-echo') do |env|
9
+ [200, {}, env[:request_headers]['Authorization']]
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ shared_examples 'does not interfere with existing authentication' do
16
+ context 'and request already has an authentication header' do
17
+ let(:response) { conn.get('/auth-echo', nil, authorization: 'Token token="bar"') }
18
+
19
+ it 'does not interfere with existing authorization' do
20
+ expect(response.body).to eq('Token token="bar"')
21
+ end
22
+ end
23
+ end
24
+
25
+ let(:response) { conn.get('/auth-echo') }
26
+
27
+ describe 'basic_auth' do
28
+ let(:auth_type) { :basic_auth }
29
+
30
+ context 'when passed correct params' do
31
+ let(:auth_config) { %w[aladdin opensesame] }
32
+
33
+ it { expect(response.body).to eq('Basic YWxhZGRpbjpvcGVuc2VzYW1l') }
34
+
35
+ include_examples 'does not interfere with existing authentication'
36
+ end
37
+
38
+ context 'when passed very long values' do
39
+ let(:auth_config) { ['A' * 255, ''] }
40
+
41
+ it { expect(response.body).to eq("Basic #{'QUFB' * 85}Og==") }
42
+
43
+ include_examples 'does not interfere with existing authentication'
44
+ end
45
+ end
46
+
47
+ describe 'token_auth' do
48
+ let(:auth_type) { :token_auth }
49
+
50
+ context 'when passed correct params' do
51
+ let(:auth_config) { 'quux' }
52
+
53
+ it { expect(response.body).to eq('Token token="quux"') }
54
+
55
+ include_examples 'does not interfere with existing authentication'
56
+ end
57
+
58
+ context 'when other values are provided' do
59
+ let(:auth_config) { ['baz', foo: 42] }
60
+
61
+ it { expect(response.body).to match(/^Token /) }
62
+ it { expect(response.body).to match(/token="baz"/) }
63
+ it { expect(response.body).to match(/foo="42"/) }
64
+
65
+ include_examples 'does not interfere with existing authentication'
66
+ end
67
+ end
68
+
69
+ describe 'authorization' do
70
+ let(:auth_type) { :authorization }
71
+
72
+ context 'when passed two strings' do
73
+ let(:auth_config) { ['custom', 'abc def'] }
74
+
75
+ it { expect(response.body).to eq('custom abc def') }
76
+
77
+ include_examples 'does not interfere with existing authentication'
78
+ end
79
+
80
+ context 'when passed a string and a hash' do
81
+ let(:auth_config) { ['baz', foo: 42] }
82
+
83
+ it { expect(response.body).to eq('baz foo="42"') }
84
+
85
+ include_examples 'does not interfere with existing authentication'
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Request::Instrumentation do
4
+ class FakeInstrumenter
5
+ attr_reader :instrumentations
6
+
7
+ def initialize
8
+ @instrumentations = []
9
+ end
10
+
11
+ def instrument(name, env)
12
+ @instrumentations << [name, env]
13
+ yield
14
+ end
15
+ end
16
+
17
+ let(:config) { {} }
18
+ let(:options) { Faraday::Request::Instrumentation::Options.from config }
19
+ let(:instrumenter) { FakeInstrumenter.new }
20
+ let(:conn) do
21
+ Faraday.new do |f|
22
+ f.request :instrumentation, config.merge(instrumenter: instrumenter)
23
+ f.adapter :test do |stub|
24
+ stub.get '/' do
25
+ [200, {}, 'ok']
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ it { expect(options.name).to eq('request.faraday') }
32
+ it 'defaults to ActiveSupport::Notifications' do
33
+ begin
34
+ res = options.instrumenter
35
+ rescue NameError => e
36
+ expect(e.to_s).to match('ActiveSupport')
37
+ else
38
+ expect(res).to eq(ActiveSupport::Notifications)
39
+ end
40
+ end
41
+
42
+ it 'instruments with default name' do
43
+ expect(instrumenter.instrumentations.size).to eq(0)
44
+
45
+ res = conn.get '/'
46
+ expect(res.body).to eq('ok')
47
+ expect(instrumenter.instrumentations.size).to eq(1)
48
+
49
+ name, env = instrumenter.instrumentations.first
50
+ expect(name).to eq('request.faraday')
51
+ expect(env[:url].path).to eq('/')
52
+ end
53
+
54
+ context 'with custom name' do
55
+ let(:config) { { name: 'custom' } }
56
+
57
+ it { expect(options.name).to eq('custom') }
58
+ it 'instruments with custom name' do
59
+ expect(instrumenter.instrumentations.size).to eq(0)
60
+
61
+ res = conn.get '/'
62
+ expect(res.body).to eq('ok')
63
+ expect(instrumenter.instrumentations.size).to eq(1)
64
+
65
+ name, env = instrumenter.instrumentations.first
66
+ expect(name).to eq('custom')
67
+ expect(env[:url].path).to eq('/')
68
+ end
69
+ end
70
+
71
+ context 'with custom instrumenter' do
72
+ let(:config) { { instrumenter: :custom } }
73
+
74
+ it { expect(options.instrumenter).to eq(:custom) }
75
+ end
76
+ end
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Request::Multipart do
4
+ let(:conn) do
5
+ Faraday.new do |b|
6
+ b.request :multipart
7
+ b.request :url_encoded
8
+ b.adapter :test do |stub|
9
+ stub.post('/echo') do |env|
10
+ posted_as = env[:request_headers]['Content-Type']
11
+ expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
12
+ [200, { 'Content-Type' => posted_as }, env[:body].read]
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ shared_examples 'a multipart request' do
19
+ it 'generates a unique boundary for each request' do
20
+ response1 = conn.post('/echo', payload)
21
+ response2 = conn.post('/echo', payload)
22
+
23
+ b1 = parse_multipart_boundary(response1.headers['Content-Type'])
24
+ b2 = parse_multipart_boundary(response2.headers['Content-Type'])
25
+ expect(b1).to_not eq(b2)
26
+ end
27
+ end
28
+
29
+ context 'FilePart: when multipart objects in param' do
30
+ let(:payload) do
31
+ {
32
+ a: 1,
33
+ b: {
34
+ c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
35
+ 'Content-Disposition' => 'form-data; foo=1'),
36
+ d: 2
37
+ }
38
+ }
39
+ end
40
+ it_behaves_like 'a multipart request'
41
+
42
+ it 'forms a multipart request' do
43
+ response = conn.post('/echo', payload)
44
+
45
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
46
+ result = parse_multipart(boundary, response.body)
47
+ expect(result[:errors]).to be_empty
48
+
49
+ part_a, body_a = result.part('a')
50
+ expect(part_a).to_not be_nil
51
+ expect(part_a.filename).to be_nil
52
+ expect(body_a).to eq('1')
53
+
54
+ part_bc, body_bc = result.part('b[c]')
55
+ expect(part_bc).to_not be_nil
56
+ expect(part_bc.filename).to eq('multipart_spec.rb')
57
+ expect(part_bc.headers['content-disposition']).to eq(
58
+ 'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
59
+ )
60
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
61
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
62
+ expect(body_bc).to eq(File.read(__FILE__))
63
+
64
+ part_bd, body_bd = result.part('b[d]')
65
+ expect(part_bd).to_not be_nil
66
+ expect(part_bd.filename).to be_nil
67
+ expect(body_bd).to eq('2')
68
+ end
69
+ end
70
+
71
+ context 'FilePart: when providing json and IO content in the same payload' do
72
+ let(:io) { StringIO.new('io-content') }
73
+ let(:json) do
74
+ {
75
+ b: 1,
76
+ c: 2
77
+ }.to_json
78
+ end
79
+
80
+ let(:payload) do
81
+ {
82
+ json: Faraday::ParamPart.new(json, 'application/json'),
83
+ io: Faraday::FilePart.new(io, 'application/pdf')
84
+ }
85
+ end
86
+
87
+ it_behaves_like 'a multipart request'
88
+
89
+ it 'forms a multipart request' do
90
+ response = conn.post('/echo', payload)
91
+
92
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
93
+ result = parse_multipart(boundary, response.body)
94
+ expect(result[:errors]).to be_empty
95
+
96
+ part_json, body_json = result.part('json')
97
+ expect(part_json).to_not be_nil
98
+ expect(part_json.mime).to eq('application/json')
99
+ expect(part_json.filename).to be_nil
100
+ expect(body_json).to eq(json)
101
+
102
+ part_io, body_io = result.part('io')
103
+ expect(part_io).to_not be_nil
104
+ expect(part_io.mime).to eq('application/pdf')
105
+ expect(part_io.filename).to eq('local.path')
106
+ expect(body_io).to eq(io.string)
107
+ end
108
+ end
109
+
110
+ context 'FilePart: when multipart objects in array param' do
111
+ let(:payload) do
112
+ {
113
+ a: 1,
114
+ b: [{
115
+ c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
116
+ d: 2
117
+ }]
118
+ }
119
+ end
120
+
121
+ it_behaves_like 'a multipart request'
122
+
123
+ it 'forms a multipart request' do
124
+ response = conn.post('/echo', payload)
125
+
126
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
127
+ result = parse_multipart(boundary, response.body)
128
+ expect(result[:errors]).to be_empty
129
+
130
+ part_a, body_a = result.part('a')
131
+ expect(part_a).to_not be_nil
132
+ expect(part_a.filename).to be_nil
133
+ expect(body_a).to eq('1')
134
+
135
+ part_bc, body_bc = result.part('b[][c]')
136
+ expect(part_bc).to_not be_nil
137
+ expect(part_bc.filename).to eq('multipart_spec.rb')
138
+ expect(part_bc.headers['content-disposition']).to eq(
139
+ 'form-data; name="b[][c]"; filename="multipart_spec.rb"'
140
+ )
141
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
142
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
143
+ expect(body_bc).to eq(File.read(__FILE__))
144
+
145
+ part_bd, body_bd = result.part('b[][d]')
146
+ expect(part_bd).to_not be_nil
147
+ expect(part_bd.filename).to be_nil
148
+ expect(body_bd).to eq('2')
149
+ end
150
+ end
151
+
152
+ context 'UploadIO: when multipart objects in param' do
153
+ let(:payload) do
154
+ {
155
+ a: 1,
156
+ b: {
157
+ c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil,
158
+ 'Content-Disposition' => 'form-data; foo=1'),
159
+ d: 2
160
+ }
161
+ }
162
+ end
163
+ it_behaves_like 'a multipart request'
164
+
165
+ it 'forms a multipart request' do
166
+ response = conn.post('/echo', payload)
167
+
168
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
169
+ result = parse_multipart(boundary, response.body)
170
+ expect(result[:errors]).to be_empty
171
+
172
+ part_a, body_a = result.part('a')
173
+ expect(part_a).to_not be_nil
174
+ expect(part_a.filename).to be_nil
175
+ expect(body_a).to eq('1')
176
+
177
+ part_bc, body_bc = result.part('b[c]')
178
+ expect(part_bc).to_not be_nil
179
+ expect(part_bc.filename).to eq('multipart_spec.rb')
180
+ expect(part_bc.headers['content-disposition']).to eq(
181
+ 'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
182
+ )
183
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
184
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
185
+ expect(body_bc).to eq(File.read(__FILE__))
186
+
187
+ part_bd, body_bd = result.part('b[d]')
188
+ expect(part_bd).to_not be_nil
189
+ expect(part_bd.filename).to be_nil
190
+ expect(body_bd).to eq('2')
191
+ end
192
+ end
193
+
194
+ context 'UploadIO: when providing json and IO content in the same payload' do
195
+ let(:io) { StringIO.new('io-content') }
196
+ let(:json) do
197
+ {
198
+ b: 1,
199
+ c: 2
200
+ }.to_json
201
+ end
202
+
203
+ let(:payload) do
204
+ {
205
+ json: Faraday::ParamPart.new(json, 'application/json'),
206
+ io: Faraday::UploadIO.new(io, 'application/pdf')
207
+ }
208
+ end
209
+
210
+ it_behaves_like 'a multipart request'
211
+
212
+ it 'forms a multipart request' do
213
+ response = conn.post('/echo', payload)
214
+
215
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
216
+ result = parse_multipart(boundary, response.body)
217
+ expect(result[:errors]).to be_empty
218
+
219
+ part_json, body_json = result.part('json')
220
+ expect(part_json).to_not be_nil
221
+ expect(part_json.mime).to eq('application/json')
222
+ expect(part_json.filename).to be_nil
223
+ expect(body_json).to eq(json)
224
+
225
+ part_io, body_io = result.part('io')
226
+ expect(part_io).to_not be_nil
227
+ expect(part_io.mime).to eq('application/pdf')
228
+ expect(part_io.filename).to eq('local.path')
229
+ expect(body_io).to eq(io.string)
230
+ end
231
+ end
232
+
233
+ context 'UploadIO: when multipart objects in array param' do
234
+ let(:payload) do
235
+ {
236
+ a: 1,
237
+ b: [{
238
+ c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
239
+ d: 2
240
+ }]
241
+ }
242
+ end
243
+
244
+ it_behaves_like 'a multipart request'
245
+
246
+ it 'forms a multipart request' do
247
+ response = conn.post('/echo', payload)
248
+
249
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
250
+ result = parse_multipart(boundary, response.body)
251
+ expect(result[:errors]).to be_empty
252
+
253
+ part_a, body_a = result.part('a')
254
+ expect(part_a).to_not be_nil
255
+ expect(part_a.filename).to be_nil
256
+ expect(body_a).to eq('1')
257
+
258
+ part_bc, body_bc = result.part('b[][c]')
259
+ expect(part_bc).to_not be_nil
260
+ expect(part_bc.filename).to eq('multipart_spec.rb')
261
+ expect(part_bc.headers['content-disposition']).to eq(
262
+ 'form-data; name="b[][c]"; filename="multipart_spec.rb"'
263
+ )
264
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
265
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
266
+ expect(body_bc).to eq(File.read(__FILE__))
267
+
268
+ part_bd, body_bd = result.part('b[][d]')
269
+ expect(part_bd).to_not be_nil
270
+ expect(part_bd.filename).to be_nil
271
+ expect(body_bd).to eq('2')
272
+ end
273
+ end
274
+ end