gocardless_pro 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -4
  3. data/lib/gocardless_pro.rb +1 -0
  4. data/lib/gocardless_pro/api_service.rb +2 -0
  5. data/lib/gocardless_pro/client.rb +4 -3
  6. data/lib/gocardless_pro/error/invalid_state_error.rb +17 -0
  7. data/lib/gocardless_pro/middlewares/raise_gocardless_errors.rb +50 -0
  8. data/lib/gocardless_pro/request.rb +38 -1
  9. data/lib/gocardless_pro/resources/creditor.rb +2 -2
  10. data/lib/gocardless_pro/resources/creditor_bank_account.rb +11 -11
  11. data/lib/gocardless_pro/resources/customer_bank_account.rb +2 -2
  12. data/lib/gocardless_pro/resources/event.rb +2 -2
  13. data/lib/gocardless_pro/resources/mandate.rb +2 -2
  14. data/lib/gocardless_pro/resources/payment.rb +7 -7
  15. data/lib/gocardless_pro/resources/payout.rb +7 -6
  16. data/lib/gocardless_pro/resources/redirect_flow.rb +2 -2
  17. data/lib/gocardless_pro/resources/refund.rb +2 -2
  18. data/lib/gocardless_pro/resources/subscription.rb +2 -2
  19. data/lib/gocardless_pro/response.rb +2 -54
  20. data/lib/gocardless_pro/services/bank_details_lookups_service.rb +3 -0
  21. data/lib/gocardless_pro/services/creditor_bank_accounts_service.rb +31 -2
  22. data/lib/gocardless_pro/services/creditors_service.rb +21 -1
  23. data/lib/gocardless_pro/services/customer_bank_accounts_service.rb +34 -2
  24. data/lib/gocardless_pro/services/customers_service.rb +21 -1
  25. data/lib/gocardless_pro/services/events_service.rb +5 -0
  26. data/lib/gocardless_pro/services/mandate_pdfs_service.rb +3 -0
  27. data/lib/gocardless_pro/services/mandates_service.rb +47 -3
  28. data/lib/gocardless_pro/services/payments_service.rb +47 -3
  29. data/lib/gocardless_pro/services/payouts_service.rb +5 -0
  30. data/lib/gocardless_pro/services/redirect_flows_service.rb +28 -2
  31. data/lib/gocardless_pro/services/refunds_service.rb +21 -1
  32. data/lib/gocardless_pro/services/subscriptions_service.rb +34 -2
  33. data/lib/gocardless_pro/version.rb +1 -1
  34. data/spec/api_service_spec.rb +106 -0
  35. data/spec/middlewares/raise_gocardless_errors_spec.rb +98 -0
  36. data/spec/resources/bank_details_lookup_spec.rb +102 -19
  37. data/spec/resources/creditor_bank_account_spec.rb +416 -40
  38. data/spec/resources/creditor_spec.rb +414 -53
  39. data/spec/resources/customer_bank_account_spec.rb +452 -40
  40. data/spec/resources/customer_spec.rb +457 -45
  41. data/spec/resources/event_spec.rb +171 -72
  42. data/spec/resources/mandate_pdf_spec.rb +100 -17
  43. data/spec/resources/mandate_spec.rb +501 -44
  44. data/spec/resources/payment_spec.rb +531 -48
  45. data/spec/resources/payout_spec.rb +189 -45
  46. data/spec/resources/redirect_flow_spec.rb +277 -43
  47. data/spec/resources/refund_spec.rb +349 -34
  48. data/spec/resources/subscription_spec.rb +531 -53
  49. data/spec/response_spec.rb +12 -79
  50. data/spec/services/bank_details_lookups_service_spec.rb +67 -2
  51. data/spec/services/creditor_bank_accounts_service_spec.rb +309 -31
  52. data/spec/services/creditors_service_spec.rb +343 -33
  53. data/spec/services/customer_bank_accounts_service_spec.rb +335 -32
  54. data/spec/services/customers_service_spec.rb +364 -36
  55. data/spec/services/events_service_spec.rb +185 -24
  56. data/spec/services/mandate_pdfs_service_spec.rb +66 -2
  57. data/spec/services/mandates_service_spec.rb +341 -33
  58. data/spec/services/payments_service_spec.rb +355 -35
  59. data/spec/services/payouts_service_spec.rb +206 -26
  60. data/spec/services/redirect_flows_service_spec.rb +137 -7
  61. data/spec/services/refunds_service_spec.rb +301 -27
  62. data/spec/services/subscriptions_service_spec.rb +377 -38
  63. metadata +6 -3
@@ -7,31 +7,37 @@ describe GoCardlessPro::Services::EventsService do
7
7
  )
8
8
  end
9
9
 
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
10
12
  describe '#list' do
11
13
  describe 'with no filters' do
12
14
  subject(:get_list_response) { client.events.list }
13
15
 
14
- before do
15
- stub_request(:get, %r{.*api.gocardless.com/events}).to_return(
16
- body: {
17
- 'events' => [{
16
+ let(:body) do
17
+ {
18
+ 'events' => [{
18
19
 
19
- 'action' => 'action-input',
20
- 'created_at' => 'created_at-input',
21
- 'details' => 'details-input',
22
- 'id' => 'id-input',
23
- 'links' => 'links-input',
24
- 'metadata' => 'metadata-input',
25
- 'resource_type' => 'resource_type-input'
26
- }],
27
- meta: {
28
- cursors: {
29
- before: nil,
30
- after: 'ABC123'
31
- }
20
+ 'action' => 'action-input',
21
+ 'created_at' => 'created_at-input',
22
+ 'details' => 'details-input',
23
+ 'id' => 'id-input',
24
+ 'links' => 'links-input',
25
+ 'metadata' => 'metadata-input',
26
+ 'resource_type' => 'resource_type-input'
27
+ }],
28
+ meta: {
29
+ cursors: {
30
+ before: nil,
31
+ after: 'ABC123'
32
32
  }
33
- }.to_json,
34
- headers: { 'Content-Type' => 'application/json' }
33
+ }
34
+ }.to_json
35
+ end
36
+
37
+ before do
38
+ stub_request(:get, %r{.*api.gocardless.com/events}).to_return(
39
+ body: body,
40
+ headers: response_headers
35
41
  )
36
42
  end
37
43
 
@@ -57,6 +63,29 @@ describe GoCardlessPro::Services::EventsService do
57
63
  end
58
64
 
59
65
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
66
+
67
+ describe 'retry behaviour' do
68
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
69
+
70
+ it 'retries timeouts' do
71
+ stub = stub_request(:get, %r{.*api.gocardless.com/events})
72
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
73
+
74
+ get_list_response
75
+ expect(stub).to have_been_requested.twice
76
+ end
77
+
78
+ it 'retries 5XX errors' do
79
+ stub = stub_request(:get, %r{.*api.gocardless.com/events})
80
+ .to_return(status: 502,
81
+ headers: { 'Content-Type' => 'text/html' },
82
+ body: '<html><body>Response from Cloudflare</body></html>')
83
+ .then.to_return(status: 200, headers: response_headers, body: body)
84
+
85
+ get_list_response
86
+ expect(stub).to have_been_requested.twice
87
+ end
88
+ end
60
89
  end
61
90
  end
62
91
 
@@ -79,7 +108,7 @@ describe GoCardlessPro::Services::EventsService do
79
108
  limit: 1
80
109
  }
81
110
  }.to_json,
82
- headers: { 'Content-Type' => 'application/json' }
111
+ headers: response_headers
83
112
  )
84
113
  end
85
114
 
@@ -101,7 +130,7 @@ describe GoCardlessPro::Services::EventsService do
101
130
  cursors: {}
102
131
  }
103
132
  }.to_json,
104
- headers: { 'Content-Type' => 'application/json' }
133
+ headers: response_headers
105
134
  )
106
135
  end
107
136
 
@@ -110,6 +139,111 @@ describe GoCardlessPro::Services::EventsService do
110
139
  expect(first_response_stub).to have_been_requested
111
140
  expect(second_response_stub).to have_been_requested
112
141
  end
142
+
143
+ describe 'retry behaviour' do
144
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
145
+
146
+ it 'retries timeouts' do
147
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/events$}).to_return(
148
+ body: {
149
+ 'events' => [{
150
+
151
+ 'action' => 'action-input',
152
+ 'created_at' => 'created_at-input',
153
+ 'details' => 'details-input',
154
+ 'id' => 'id-input',
155
+ 'links' => 'links-input',
156
+ 'metadata' => 'metadata-input',
157
+ 'resource_type' => 'resource_type-input'
158
+ }],
159
+ meta: {
160
+ cursors: { after: 'AB345' },
161
+ limit: 1
162
+ }
163
+ }.to_json,
164
+ headers: response_headers
165
+ )
166
+
167
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/events\?after=AB345})
168
+ .to_timeout.then
169
+ .to_return(
170
+ body: {
171
+ 'events' => [{
172
+
173
+ 'action' => 'action-input',
174
+ 'created_at' => 'created_at-input',
175
+ 'details' => 'details-input',
176
+ 'id' => 'id-input',
177
+ 'links' => 'links-input',
178
+ 'metadata' => 'metadata-input',
179
+ 'resource_type' => 'resource_type-input'
180
+ }],
181
+ meta: {
182
+ limit: 2,
183
+ cursors: {}
184
+ }
185
+ }.to_json,
186
+ headers: response_headers
187
+ )
188
+
189
+ client.events.all.to_a
190
+
191
+ expect(first_response_stub).to have_been_requested
192
+ expect(second_response_stub).to have_been_requested.twice
193
+ end
194
+
195
+ it 'retries 5XX errors' do
196
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/events$}).to_return(
197
+ body: {
198
+ 'events' => [{
199
+
200
+ 'action' => 'action-input',
201
+ 'created_at' => 'created_at-input',
202
+ 'details' => 'details-input',
203
+ 'id' => 'id-input',
204
+ 'links' => 'links-input',
205
+ 'metadata' => 'metadata-input',
206
+ 'resource_type' => 'resource_type-input'
207
+ }],
208
+ meta: {
209
+ cursors: { after: 'AB345' },
210
+ limit: 1
211
+ }
212
+ }.to_json,
213
+ headers: response_headers
214
+ )
215
+
216
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/events\?after=AB345})
217
+ .to_return(
218
+ status: 502,
219
+ body: '<html><body>Response from Cloudflare</body></html>',
220
+ headers: { 'Content-Type' => 'text/html' }
221
+ ).then.to_return(
222
+ body: {
223
+ 'events' => [{
224
+
225
+ 'action' => 'action-input',
226
+ 'created_at' => 'created_at-input',
227
+ 'details' => 'details-input',
228
+ 'id' => 'id-input',
229
+ 'links' => 'links-input',
230
+ 'metadata' => 'metadata-input',
231
+ 'resource_type' => 'resource_type-input'
232
+ }],
233
+ meta: {
234
+ limit: 2,
235
+ cursors: {}
236
+ }
237
+ }.to_json,
238
+ headers: response_headers
239
+ )
240
+
241
+ client.events.all.to_a
242
+
243
+ expect(first_response_stub).to have_been_requested
244
+ expect(second_response_stub).to have_been_requested.twice
245
+ end
246
+ end
113
247
  end
114
248
 
115
249
  describe '#get' do
@@ -135,7 +269,7 @@ describe GoCardlessPro::Services::EventsService do
135
269
  'resource_type' => 'resource_type-input'
136
270
  }
137
271
  }.to_json,
138
- headers: { 'Content-Type' => 'application/json' }
272
+ headers: response_headers
139
273
  )
140
274
  end
141
275
 
@@ -167,7 +301,7 @@ describe GoCardlessPro::Services::EventsService do
167
301
  'resource_type' => 'resource_type-input'
168
302
  }
169
303
  }.to_json,
170
- headers: { 'Content-Type' => 'application/json' }
304
+ headers: response_headers
171
305
  )
172
306
  end
173
307
 
@@ -181,7 +315,7 @@ describe GoCardlessPro::Services::EventsService do
181
315
  stub_url = '/events/:identity'.gsub(':identity', id)
182
316
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
183
317
  body: '',
184
- headers: { 'Content-Type' => 'application/json' }
318
+ headers: response_headers
185
319
  )
186
320
  end
187
321
 
@@ -197,5 +331,32 @@ describe GoCardlessPro::Services::EventsService do
197
331
  expect { get_response }.to_not raise_error(/bad URI/)
198
332
  end
199
333
  end
334
+
335
+ describe 'retry behaviour' do
336
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
337
+
338
+ it 'retries timeouts' do
339
+ stub_url = '/events/:identity'.gsub(':identity', id)
340
+
341
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
342
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
343
+
344
+ get_response
345
+ expect(stub).to have_been_requested.twice
346
+ end
347
+
348
+ it 'retries 5XX errors' do
349
+ stub_url = '/events/:identity'.gsub(':identity', id)
350
+
351
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
352
+ .to_return(status: 502,
353
+ headers: { 'Content-Type' => 'text/html' },
354
+ body: '<html><body>Response from Cloudflare</body></html>')
355
+ .then.to_return(status: 200, headers: response_headers)
356
+
357
+ get_response
358
+ expect(stub).to have_been_requested.twice
359
+ end
360
+ end
200
361
  end
201
362
  end
@@ -7,6 +7,8 @@ describe GoCardlessPro::Services::MandatePdfsService do
7
7
  )
8
8
  end
9
9
 
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
10
12
  describe '#create' do
11
13
  subject(:post_create_response) { client.mandate_pdfs.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -40,13 +42,36 @@ describe GoCardlessPro::Services::MandatePdfsService do
40
42
  }
41
43
 
42
44
  }.to_json,
43
- headers: { 'Content-Type' => 'application/json' }
45
+ headers: response_headers
44
46
  )
45
47
  end
46
48
 
47
49
  it 'creates and returns the resource' do
48
50
  expect(post_create_response).to be_a(GoCardlessPro::Resources::MandatePdf)
49
51
  end
52
+
53
+ describe 'retry behaviour' do
54
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
55
+
56
+ it 'retries timeouts' do
57
+ stub = stub_request(:post, %r{.*api.gocardless.com/mandate_pdfs})
58
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
59
+
60
+ post_create_response
61
+ expect(stub).to have_been_requested.twice
62
+ end
63
+
64
+ it 'retries 5XX errors' do
65
+ stub = stub_request(:post, %r{.*api.gocardless.com/mandate_pdfs})
66
+ .to_return(status: 502,
67
+ headers: { 'Content-Type' => 'text/html' },
68
+ body: '<html><body>Response from Cloudflare</body></html>')
69
+ .then.to_return(status: 200, headers: response_headers)
70
+
71
+ post_create_response
72
+ expect(stub).to have_been_requested.twice
73
+ end
74
+ end
50
75
  end
51
76
 
52
77
  context 'with a request that returns a validation error' do
@@ -63,7 +88,7 @@ describe GoCardlessPro::Services::MandatePdfsService do
63
88
  ]
64
89
  }
65
90
  }.to_json,
66
- headers: { 'Content-Type' => 'application/json' },
91
+ headers: response_headers,
67
92
  status: 422
68
93
  )
69
94
  end
@@ -72,5 +97,44 @@ describe GoCardlessPro::Services::MandatePdfsService do
72
97
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
73
98
  end
74
99
  end
100
+
101
+ context 'with a request that returns an idempotent creation conflict error' do
102
+ let(:id) { 'ID123' }
103
+
104
+ let(:new_resource) do
105
+ {
106
+
107
+ 'expires_at' => 'expires_at-input',
108
+ 'url' => 'url-input'
109
+ }
110
+ end
111
+
112
+ let!(:post_stub) do
113
+ stub_request(:post, %r{.*api.gocardless.com/mandate_pdfs}).to_return(
114
+ body: {
115
+ error: {
116
+ type: 'invalid_state',
117
+ code: 409,
118
+ errors: [
119
+ {
120
+ message: 'A resource has already been created with this idempotency key',
121
+ reason: 'idempotent_creation_conflict',
122
+ links: {
123
+ conflicting_resource_id: id
124
+ }
125
+ }
126
+ ]
127
+ }
128
+ }.to_json,
129
+ headers: response_headers,
130
+ status: 409
131
+ )
132
+ end
133
+
134
+ it 'raises an InvalidStateError' do
135
+ expect { post_create_response }.to raise_error(GoCardlessPro::InvalidStateError)
136
+ expect(post_stub).to have_been_requested
137
+ end
138
+ end
75
139
  end
76
140
  end
@@ -7,6 +7,8 @@ describe GoCardlessPro::Services::MandatesService do
7
7
  )
8
8
  end
9
9
 
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
10
12
  describe '#create' do
11
13
  subject(:post_create_response) { client.mandates.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -61,13 +63,36 @@ describe GoCardlessPro::Services::MandatesService do
61
63
  }
62
64
 
63
65
  }.to_json,
64
- headers: { 'Content-Type' => 'application/json' }
66
+ headers: response_headers
65
67
  )
66
68
  end
67
69
 
68
70
  it 'creates and returns the resource' do
69
71
  expect(post_create_response).to be_a(GoCardlessPro::Resources::Mandate)
70
72
  end
73
+
74
+ describe 'retry behaviour' do
75
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
76
+
77
+ it 'retries timeouts' do
78
+ stub = stub_request(:post, %r{.*api.gocardless.com/mandates})
79
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
80
+
81
+ post_create_response
82
+ expect(stub).to have_been_requested.twice
83
+ end
84
+
85
+ it 'retries 5XX errors' do
86
+ stub = stub_request(:post, %r{.*api.gocardless.com/mandates})
87
+ .to_return(status: 502,
88
+ headers: { 'Content-Type' => 'text/html' },
89
+ body: '<html><body>Response from Cloudflare</body></html>')
90
+ .then.to_return(status: 200, headers: response_headers)
91
+
92
+ post_create_response
93
+ expect(stub).to have_been_requested.twice
94
+ end
95
+ end
71
96
  end
72
97
 
73
98
  context 'with a request that returns a validation error' do
@@ -84,7 +109,7 @@ describe GoCardlessPro::Services::MandatesService do
84
109
  ]
85
110
  }
86
111
  }.to_json,
87
- headers: { 'Content-Type' => 'application/json' },
112
+ headers: response_headers,
88
113
  status: 422
89
114
  )
90
115
  end
@@ -93,35 +118,108 @@ describe GoCardlessPro::Services::MandatesService do
93
118
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
94
119
  end
95
120
  end
121
+
122
+ context 'with a request that returns an idempotent creation conflict error' do
123
+ let(:id) { 'ID123' }
124
+
125
+ let(:new_resource) do
126
+ {
127
+
128
+ 'created_at' => 'created_at-input',
129
+ 'id' => 'id-input',
130
+ 'links' => 'links-input',
131
+ 'metadata' => 'metadata-input',
132
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
133
+ 'payments_require_approval' => 'payments_require_approval-input',
134
+ 'reference' => 'reference-input',
135
+ 'scheme' => 'scheme-input',
136
+ 'status' => 'status-input'
137
+ }
138
+ end
139
+
140
+ let!(:post_stub) do
141
+ stub_request(:post, %r{.*api.gocardless.com/mandates}).to_return(
142
+ body: {
143
+ error: {
144
+ type: 'invalid_state',
145
+ code: 409,
146
+ errors: [
147
+ {
148
+ message: 'A resource has already been created with this idempotency key',
149
+ reason: 'idempotent_creation_conflict',
150
+ links: {
151
+ conflicting_resource_id: id
152
+ }
153
+ }
154
+ ]
155
+ }
156
+ }.to_json,
157
+ headers: response_headers,
158
+ status: 409
159
+ )
160
+ end
161
+
162
+ let!(:get_stub) do
163
+ stub_url = "/mandates/#{id}"
164
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
165
+ .to_return(
166
+ body: {
167
+ 'mandates' => {
168
+
169
+ 'created_at' => 'created_at-input',
170
+ 'id' => 'id-input',
171
+ 'links' => 'links-input',
172
+ 'metadata' => 'metadata-input',
173
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
174
+ 'payments_require_approval' => 'payments_require_approval-input',
175
+ 'reference' => 'reference-input',
176
+ 'scheme' => 'scheme-input',
177
+ 'status' => 'status-input'
178
+ }
179
+ }.to_json,
180
+ headers: response_headers
181
+ )
182
+ end
183
+
184
+ it 'fetches the already-created resource' do
185
+ post_create_response
186
+ expect(post_stub).to have_been_requested
187
+ expect(get_stub).to have_been_requested
188
+ end
189
+ end
96
190
  end
97
191
 
98
192
  describe '#list' do
99
193
  describe 'with no filters' do
100
194
  subject(:get_list_response) { client.mandates.list }
101
195
 
102
- before do
103
- stub_request(:get, %r{.*api.gocardless.com/mandates}).to_return(
104
- body: {
105
- 'mandates' => [{
196
+ let(:body) do
197
+ {
198
+ 'mandates' => [{
106
199
 
107
- 'created_at' => 'created_at-input',
108
- 'id' => 'id-input',
109
- 'links' => 'links-input',
110
- 'metadata' => 'metadata-input',
111
- 'next_possible_charge_date' => 'next_possible_charge_date-input',
112
- 'payments_require_approval' => 'payments_require_approval-input',
113
- 'reference' => 'reference-input',
114
- 'scheme' => 'scheme-input',
115
- 'status' => 'status-input'
116
- }],
117
- meta: {
118
- cursors: {
119
- before: nil,
120
- after: 'ABC123'
121
- }
200
+ 'created_at' => 'created_at-input',
201
+ 'id' => 'id-input',
202
+ 'links' => 'links-input',
203
+ 'metadata' => 'metadata-input',
204
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
205
+ 'payments_require_approval' => 'payments_require_approval-input',
206
+ 'reference' => 'reference-input',
207
+ 'scheme' => 'scheme-input',
208
+ 'status' => 'status-input'
209
+ }],
210
+ meta: {
211
+ cursors: {
212
+ before: nil,
213
+ after: 'ABC123'
122
214
  }
123
- }.to_json,
124
- headers: { 'Content-Type' => 'application/json' }
215
+ }
216
+ }.to_json
217
+ end
218
+
219
+ before do
220
+ stub_request(:get, %r{.*api.gocardless.com/mandates}).to_return(
221
+ body: body,
222
+ headers: response_headers
125
223
  )
126
224
  end
127
225
 
@@ -151,6 +249,29 @@ describe GoCardlessPro::Services::MandatesService do
151
249
  end
152
250
 
153
251
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
252
+
253
+ describe 'retry behaviour' do
254
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
255
+
256
+ it 'retries timeouts' do
257
+ stub = stub_request(:get, %r{.*api.gocardless.com/mandates})
258
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
259
+
260
+ get_list_response
261
+ expect(stub).to have_been_requested.twice
262
+ end
263
+
264
+ it 'retries 5XX errors' do
265
+ stub = stub_request(:get, %r{.*api.gocardless.com/mandates})
266
+ .to_return(status: 502,
267
+ headers: { 'Content-Type' => 'text/html' },
268
+ body: '<html><body>Response from Cloudflare</body></html>')
269
+ .then.to_return(status: 200, headers: response_headers, body: body)
270
+
271
+ get_list_response
272
+ expect(stub).to have_been_requested.twice
273
+ end
274
+ end
154
275
  end
155
276
  end
156
277
 
@@ -175,7 +296,7 @@ describe GoCardlessPro::Services::MandatesService do
175
296
  limit: 1
176
297
  }
177
298
  }.to_json,
178
- headers: { 'Content-Type' => 'application/json' }
299
+ headers: response_headers
179
300
  )
180
301
  end
181
302
 
@@ -199,7 +320,7 @@ describe GoCardlessPro::Services::MandatesService do
199
320
  cursors: {}
200
321
  }
201
322
  }.to_json,
202
- headers: { 'Content-Type' => 'application/json' }
323
+ headers: response_headers
203
324
  )
204
325
  end
205
326
 
@@ -208,6 +329,119 @@ describe GoCardlessPro::Services::MandatesService do
208
329
  expect(first_response_stub).to have_been_requested
209
330
  expect(second_response_stub).to have_been_requested
210
331
  end
332
+
333
+ describe 'retry behaviour' do
334
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
335
+
336
+ it 'retries timeouts' do
337
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/mandates$}).to_return(
338
+ body: {
339
+ 'mandates' => [{
340
+
341
+ 'created_at' => 'created_at-input',
342
+ 'id' => 'id-input',
343
+ 'links' => 'links-input',
344
+ 'metadata' => 'metadata-input',
345
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
346
+ 'payments_require_approval' => 'payments_require_approval-input',
347
+ 'reference' => 'reference-input',
348
+ 'scheme' => 'scheme-input',
349
+ 'status' => 'status-input'
350
+ }],
351
+ meta: {
352
+ cursors: { after: 'AB345' },
353
+ limit: 1
354
+ }
355
+ }.to_json,
356
+ headers: response_headers
357
+ )
358
+
359
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/mandates\?after=AB345})
360
+ .to_timeout.then
361
+ .to_return(
362
+ body: {
363
+ 'mandates' => [{
364
+
365
+ 'created_at' => 'created_at-input',
366
+ 'id' => 'id-input',
367
+ 'links' => 'links-input',
368
+ 'metadata' => 'metadata-input',
369
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
370
+ 'payments_require_approval' => 'payments_require_approval-input',
371
+ 'reference' => 'reference-input',
372
+ 'scheme' => 'scheme-input',
373
+ 'status' => 'status-input'
374
+ }],
375
+ meta: {
376
+ limit: 2,
377
+ cursors: {}
378
+ }
379
+ }.to_json,
380
+ headers: response_headers
381
+ )
382
+
383
+ client.mandates.all.to_a
384
+
385
+ expect(first_response_stub).to have_been_requested
386
+ expect(second_response_stub).to have_been_requested.twice
387
+ end
388
+
389
+ it 'retries 5XX errors' do
390
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/mandates$}).to_return(
391
+ body: {
392
+ 'mandates' => [{
393
+
394
+ 'created_at' => 'created_at-input',
395
+ 'id' => 'id-input',
396
+ 'links' => 'links-input',
397
+ 'metadata' => 'metadata-input',
398
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
399
+ 'payments_require_approval' => 'payments_require_approval-input',
400
+ 'reference' => 'reference-input',
401
+ 'scheme' => 'scheme-input',
402
+ 'status' => 'status-input'
403
+ }],
404
+ meta: {
405
+ cursors: { after: 'AB345' },
406
+ limit: 1
407
+ }
408
+ }.to_json,
409
+ headers: response_headers
410
+ )
411
+
412
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/mandates\?after=AB345})
413
+ .to_return(
414
+ status: 502,
415
+ body: '<html><body>Response from Cloudflare</body></html>',
416
+ headers: { 'Content-Type' => 'text/html' }
417
+ ).then.to_return(
418
+ body: {
419
+ 'mandates' => [{
420
+
421
+ 'created_at' => 'created_at-input',
422
+ 'id' => 'id-input',
423
+ 'links' => 'links-input',
424
+ 'metadata' => 'metadata-input',
425
+ 'next_possible_charge_date' => 'next_possible_charge_date-input',
426
+ 'payments_require_approval' => 'payments_require_approval-input',
427
+ 'reference' => 'reference-input',
428
+ 'scheme' => 'scheme-input',
429
+ 'status' => 'status-input'
430
+ }],
431
+ meta: {
432
+ limit: 2,
433
+ cursors: {}
434
+ }
435
+ }.to_json,
436
+ headers: response_headers
437
+ )
438
+
439
+ client.mandates.all.to_a
440
+
441
+ expect(first_response_stub).to have_been_requested
442
+ expect(second_response_stub).to have_been_requested.twice
443
+ end
444
+ end
211
445
  end
212
446
 
213
447
  describe '#get' do
@@ -235,7 +469,7 @@ describe GoCardlessPro::Services::MandatesService do
235
469
  'status' => 'status-input'
236
470
  }
237
471
  }.to_json,
238
- headers: { 'Content-Type' => 'application/json' }
472
+ headers: response_headers
239
473
  )
240
474
  end
241
475
 
@@ -269,7 +503,7 @@ describe GoCardlessPro::Services::MandatesService do
269
503
  'status' => 'status-input'
270
504
  }
271
505
  }.to_json,
272
- headers: { 'Content-Type' => 'application/json' }
506
+ headers: response_headers
273
507
  )
274
508
  end
275
509
 
@@ -283,7 +517,7 @@ describe GoCardlessPro::Services::MandatesService do
283
517
  stub_url = '/mandates/:identity'.gsub(':identity', id)
284
518
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
285
519
  body: '',
286
- headers: { 'Content-Type' => 'application/json' }
520
+ headers: response_headers
287
521
  )
288
522
  end
289
523
 
@@ -299,6 +533,33 @@ describe GoCardlessPro::Services::MandatesService do
299
533
  expect { get_response }.to_not raise_error(/bad URI/)
300
534
  end
301
535
  end
536
+
537
+ describe 'retry behaviour' do
538
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
539
+
540
+ it 'retries timeouts' do
541
+ stub_url = '/mandates/:identity'.gsub(':identity', id)
542
+
543
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
544
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
545
+
546
+ get_response
547
+ expect(stub).to have_been_requested.twice
548
+ end
549
+
550
+ it 'retries 5XX errors' do
551
+ stub_url = '/mandates/:identity'.gsub(':identity', id)
552
+
553
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
554
+ .to_return(status: 502,
555
+ headers: { 'Content-Type' => 'text/html' },
556
+ body: '<html><body>Response from Cloudflare</body></html>')
557
+ .then.to_return(status: 200, headers: response_headers)
558
+
559
+ get_response
560
+ expect(stub).to have_been_requested.twice
561
+ end
562
+ end
302
563
  end
303
564
 
304
565
  describe '#update' do
@@ -325,7 +586,7 @@ describe GoCardlessPro::Services::MandatesService do
325
586
  'status' => 'status-input'
326
587
  }
327
588
  }.to_json,
328
- headers: { 'Content-Type' => 'application/json' }
589
+ headers: response_headers
329
590
  )
330
591
  end
331
592
 
@@ -333,6 +594,31 @@ describe GoCardlessPro::Services::MandatesService do
333
594
  expect(put_update_response).to be_a(GoCardlessPro::Resources::Mandate)
334
595
  expect(stub).to have_been_requested
335
596
  end
597
+
598
+ describe 'retry behaviour' do
599
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
600
+
601
+ it 'retries timeouts' do
602
+ stub_url = '/mandates/:identity'.gsub(':identity', id)
603
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
604
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
605
+
606
+ put_update_response
607
+ expect(stub).to have_been_requested.twice
608
+ end
609
+
610
+ it 'retries 5XX errors' do
611
+ stub_url = '/mandates/:identity'.gsub(':identity', id)
612
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
613
+ .to_return(status: 502,
614
+ headers: { 'Content-Type' => 'text/html' },
615
+ body: '<html><body>Response from Cloudflare</body></html>')
616
+ .then.to_return(status: 200, headers: response_headers)
617
+
618
+ put_update_response
619
+ expect(stub).to have_been_requested.twice
620
+ end
621
+ end
336
622
  end
337
623
  end
338
624
 
@@ -359,7 +645,7 @@ describe GoCardlessPro::Services::MandatesService do
359
645
  'status' => 'status-input'
360
646
  }
361
647
  }.to_json,
362
- headers: { 'Content-Type' => 'application/json' }
648
+ headers: response_headers
363
649
  )
364
650
  end
365
651
 
@@ -369,6 +655,17 @@ describe GoCardlessPro::Services::MandatesService do
369
655
  expect(stub).to have_been_requested
370
656
  end
371
657
 
658
+ describe 'retry behaviour' do
659
+ it "doesn't retry errors" do
660
+ stub_url = '/mandates/:identity/actions/cancel'.gsub(':identity', resource_id)
661
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/)
662
+ .to_timeout
663
+
664
+ expect { post_response }.to raise_error(Faraday::TimeoutError)
665
+ expect(stub).to have_been_requested
666
+ end
667
+ end
668
+
372
669
  context 'when the request needs a body and custom header' do
373
670
  let(:body) { { foo: 'bar' } }
374
671
  let(:headers) { { 'Foo' => 'Bar' } }
@@ -398,7 +695,7 @@ describe GoCardlessPro::Services::MandatesService do
398
695
  'status' => 'status-input'
399
696
  }
400
697
  }.to_json,
401
- headers: { 'Content-Type' => 'application/json' }
698
+ headers: response_headers
402
699
  )
403
700
  end
404
701
  end
@@ -427,7 +724,7 @@ describe GoCardlessPro::Services::MandatesService do
427
724
  'status' => 'status-input'
428
725
  }
429
726
  }.to_json,
430
- headers: { 'Content-Type' => 'application/json' }
727
+ headers: response_headers
431
728
  )
432
729
  end
433
730
 
@@ -437,6 +734,17 @@ describe GoCardlessPro::Services::MandatesService do
437
734
  expect(stub).to have_been_requested
438
735
  end
439
736
 
737
+ describe 'retry behaviour' do
738
+ it "doesn't retry errors" do
739
+ stub_url = '/mandates/:identity/actions/reinstate'.gsub(':identity', resource_id)
740
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/)
741
+ .to_timeout
742
+
743
+ expect { post_response }.to raise_error(Faraday::TimeoutError)
744
+ expect(stub).to have_been_requested
745
+ end
746
+ end
747
+
440
748
  context 'when the request needs a body and custom header' do
441
749
  let(:body) { { foo: 'bar' } }
442
750
  let(:headers) { { 'Foo' => 'Bar' } }
@@ -466,7 +774,7 @@ describe GoCardlessPro::Services::MandatesService do
466
774
  'status' => 'status-input'
467
775
  }
468
776
  }.to_json,
469
- headers: { 'Content-Type' => 'application/json' }
777
+ headers: response_headers
470
778
  )
471
779
  end
472
780
  end