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,6 +7,8 @@ describe GoCardlessPro::Services::RefundsService 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.refunds.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -55,13 +57,36 @@ describe GoCardlessPro::Services::RefundsService do
55
57
  }
56
58
 
57
59
  }.to_json,
58
- headers: { 'Content-Type' => 'application/json' }
60
+ headers: response_headers
59
61
  )
60
62
  end
61
63
 
62
64
  it 'creates and returns the resource' do
63
65
  expect(post_create_response).to be_a(GoCardlessPro::Resources::Refund)
64
66
  end
67
+
68
+ describe 'retry behaviour' do
69
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
70
+
71
+ it 'retries timeouts' do
72
+ stub = stub_request(:post, %r{.*api.gocardless.com/refunds})
73
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
74
+
75
+ post_create_response
76
+ expect(stub).to have_been_requested.twice
77
+ end
78
+
79
+ it 'retries 5XX errors' do
80
+ stub = stub_request(:post, %r{.*api.gocardless.com/refunds})
81
+ .to_return(status: 502,
82
+ headers: { 'Content-Type' => 'text/html' },
83
+ body: '<html><body>Response from Cloudflare</body></html>')
84
+ .then.to_return(status: 200, headers: response_headers)
85
+
86
+ post_create_response
87
+ expect(stub).to have_been_requested.twice
88
+ end
89
+ end
65
90
  end
66
91
 
67
92
  context 'with a request that returns a validation error' do
@@ -78,7 +103,7 @@ describe GoCardlessPro::Services::RefundsService do
78
103
  ]
79
104
  }
80
105
  }.to_json,
81
- headers: { 'Content-Type' => 'application/json' },
106
+ headers: response_headers,
82
107
  status: 422
83
108
  )
84
109
  end
@@ -87,33 +112,102 @@ describe GoCardlessPro::Services::RefundsService do
87
112
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
88
113
  end
89
114
  end
115
+
116
+ context 'with a request that returns an idempotent creation conflict error' do
117
+ let(:id) { 'ID123' }
118
+
119
+ let(:new_resource) do
120
+ {
121
+
122
+ 'amount' => 'amount-input',
123
+ 'created_at' => 'created_at-input',
124
+ 'currency' => 'currency-input',
125
+ 'id' => 'id-input',
126
+ 'links' => 'links-input',
127
+ 'metadata' => 'metadata-input',
128
+ 'reference' => 'reference-input'
129
+ }
130
+ end
131
+
132
+ let!(:post_stub) do
133
+ stub_request(:post, %r{.*api.gocardless.com/refunds}).to_return(
134
+ body: {
135
+ error: {
136
+ type: 'invalid_state',
137
+ code: 409,
138
+ errors: [
139
+ {
140
+ message: 'A resource has already been created with this idempotency key',
141
+ reason: 'idempotent_creation_conflict',
142
+ links: {
143
+ conflicting_resource_id: id
144
+ }
145
+ }
146
+ ]
147
+ }
148
+ }.to_json,
149
+ headers: response_headers,
150
+ status: 409
151
+ )
152
+ end
153
+
154
+ let!(:get_stub) do
155
+ stub_url = "/refunds/#{id}"
156
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
157
+ .to_return(
158
+ body: {
159
+ 'refunds' => {
160
+
161
+ 'amount' => 'amount-input',
162
+ 'created_at' => 'created_at-input',
163
+ 'currency' => 'currency-input',
164
+ 'id' => 'id-input',
165
+ 'links' => 'links-input',
166
+ 'metadata' => 'metadata-input',
167
+ 'reference' => 'reference-input'
168
+ }
169
+ }.to_json,
170
+ headers: response_headers
171
+ )
172
+ end
173
+
174
+ it 'fetches the already-created resource' do
175
+ post_create_response
176
+ expect(post_stub).to have_been_requested
177
+ expect(get_stub).to have_been_requested
178
+ end
179
+ end
90
180
  end
91
181
 
92
182
  describe '#list' do
93
183
  describe 'with no filters' do
94
184
  subject(:get_list_response) { client.refunds.list }
95
185
 
96
- before do
97
- stub_request(:get, %r{.*api.gocardless.com/refunds}).to_return(
98
- body: {
99
- 'refunds' => [{
186
+ let(:body) do
187
+ {
188
+ 'refunds' => [{
100
189
 
101
- 'amount' => 'amount-input',
102
- 'created_at' => 'created_at-input',
103
- 'currency' => 'currency-input',
104
- 'id' => 'id-input',
105
- 'links' => 'links-input',
106
- 'metadata' => 'metadata-input',
107
- 'reference' => 'reference-input'
108
- }],
109
- meta: {
110
- cursors: {
111
- before: nil,
112
- after: 'ABC123'
113
- }
190
+ 'amount' => 'amount-input',
191
+ 'created_at' => 'created_at-input',
192
+ 'currency' => 'currency-input',
193
+ 'id' => 'id-input',
194
+ 'links' => 'links-input',
195
+ 'metadata' => 'metadata-input',
196
+ 'reference' => 'reference-input'
197
+ }],
198
+ meta: {
199
+ cursors: {
200
+ before: nil,
201
+ after: 'ABC123'
114
202
  }
115
- }.to_json,
116
- headers: { 'Content-Type' => 'application/json' }
203
+ }
204
+ }.to_json
205
+ end
206
+
207
+ before do
208
+ stub_request(:get, %r{.*api.gocardless.com/refunds}).to_return(
209
+ body: body,
210
+ headers: response_headers
117
211
  )
118
212
  end
119
213
 
@@ -139,6 +233,29 @@ describe GoCardlessPro::Services::RefundsService do
139
233
  end
140
234
 
141
235
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
236
+
237
+ describe 'retry behaviour' do
238
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
239
+
240
+ it 'retries timeouts' do
241
+ stub = stub_request(:get, %r{.*api.gocardless.com/refunds})
242
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
243
+
244
+ get_list_response
245
+ expect(stub).to have_been_requested.twice
246
+ end
247
+
248
+ it 'retries 5XX errors' do
249
+ stub = stub_request(:get, %r{.*api.gocardless.com/refunds})
250
+ .to_return(status: 502,
251
+ headers: { 'Content-Type' => 'text/html' },
252
+ body: '<html><body>Response from Cloudflare</body></html>')
253
+ .then.to_return(status: 200, headers: response_headers, body: body)
254
+
255
+ get_list_response
256
+ expect(stub).to have_been_requested.twice
257
+ end
258
+ end
142
259
  end
143
260
  end
144
261
 
@@ -161,7 +278,7 @@ describe GoCardlessPro::Services::RefundsService do
161
278
  limit: 1
162
279
  }
163
280
  }.to_json,
164
- headers: { 'Content-Type' => 'application/json' }
281
+ headers: response_headers
165
282
  )
166
283
  end
167
284
 
@@ -183,7 +300,7 @@ describe GoCardlessPro::Services::RefundsService do
183
300
  cursors: {}
184
301
  }
185
302
  }.to_json,
186
- headers: { 'Content-Type' => 'application/json' }
303
+ headers: response_headers
187
304
  )
188
305
  end
189
306
 
@@ -192,6 +309,111 @@ describe GoCardlessPro::Services::RefundsService do
192
309
  expect(first_response_stub).to have_been_requested
193
310
  expect(second_response_stub).to have_been_requested
194
311
  end
312
+
313
+ describe 'retry behaviour' do
314
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
315
+
316
+ it 'retries timeouts' do
317
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds$}).to_return(
318
+ body: {
319
+ 'refunds' => [{
320
+
321
+ 'amount' => 'amount-input',
322
+ 'created_at' => 'created_at-input',
323
+ 'currency' => 'currency-input',
324
+ 'id' => 'id-input',
325
+ 'links' => 'links-input',
326
+ 'metadata' => 'metadata-input',
327
+ 'reference' => 'reference-input'
328
+ }],
329
+ meta: {
330
+ cursors: { after: 'AB345' },
331
+ limit: 1
332
+ }
333
+ }.to_json,
334
+ headers: response_headers
335
+ )
336
+
337
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds\?after=AB345})
338
+ .to_timeout.then
339
+ .to_return(
340
+ body: {
341
+ 'refunds' => [{
342
+
343
+ 'amount' => 'amount-input',
344
+ 'created_at' => 'created_at-input',
345
+ 'currency' => 'currency-input',
346
+ 'id' => 'id-input',
347
+ 'links' => 'links-input',
348
+ 'metadata' => 'metadata-input',
349
+ 'reference' => 'reference-input'
350
+ }],
351
+ meta: {
352
+ limit: 2,
353
+ cursors: {}
354
+ }
355
+ }.to_json,
356
+ headers: response_headers
357
+ )
358
+
359
+ client.refunds.all.to_a
360
+
361
+ expect(first_response_stub).to have_been_requested
362
+ expect(second_response_stub).to have_been_requested.twice
363
+ end
364
+
365
+ it 'retries 5XX errors' do
366
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds$}).to_return(
367
+ body: {
368
+ 'refunds' => [{
369
+
370
+ 'amount' => 'amount-input',
371
+ 'created_at' => 'created_at-input',
372
+ 'currency' => 'currency-input',
373
+ 'id' => 'id-input',
374
+ 'links' => 'links-input',
375
+ 'metadata' => 'metadata-input',
376
+ 'reference' => 'reference-input'
377
+ }],
378
+ meta: {
379
+ cursors: { after: 'AB345' },
380
+ limit: 1
381
+ }
382
+ }.to_json,
383
+ headers: response_headers
384
+ )
385
+
386
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds\?after=AB345})
387
+ .to_return(
388
+ status: 502,
389
+ body: '<html><body>Response from Cloudflare</body></html>',
390
+ headers: { 'Content-Type' => 'text/html' }
391
+ ).then.to_return(
392
+ body: {
393
+ 'refunds' => [{
394
+
395
+ 'amount' => 'amount-input',
396
+ 'created_at' => 'created_at-input',
397
+ 'currency' => 'currency-input',
398
+ 'id' => 'id-input',
399
+ 'links' => 'links-input',
400
+ 'metadata' => 'metadata-input',
401
+ 'reference' => 'reference-input'
402
+ }],
403
+ meta: {
404
+ limit: 2,
405
+ cursors: {}
406
+ }
407
+ }.to_json,
408
+ headers: response_headers
409
+ )
410
+
411
+ client.refunds.all.to_a
412
+
413
+ expect(first_response_stub).to have_been_requested
414
+ expect(second_response_stub).to have_been_requested.twice
415
+ end
416
+ end
195
417
  end
196
418
 
197
419
  describe '#get' do
@@ -217,7 +439,7 @@ describe GoCardlessPro::Services::RefundsService do
217
439
  'reference' => 'reference-input'
218
440
  }
219
441
  }.to_json,
220
- headers: { 'Content-Type' => 'application/json' }
442
+ headers: response_headers
221
443
  )
222
444
  end
223
445
 
@@ -249,7 +471,7 @@ describe GoCardlessPro::Services::RefundsService do
249
471
  'reference' => 'reference-input'
250
472
  }
251
473
  }.to_json,
252
- headers: { 'Content-Type' => 'application/json' }
474
+ headers: response_headers
253
475
  )
254
476
  end
255
477
 
@@ -263,7 +485,7 @@ describe GoCardlessPro::Services::RefundsService do
263
485
  stub_url = '/refunds/:identity'.gsub(':identity', id)
264
486
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
265
487
  body: '',
266
- headers: { 'Content-Type' => 'application/json' }
488
+ headers: response_headers
267
489
  )
268
490
  end
269
491
 
@@ -279,6 +501,33 @@ describe GoCardlessPro::Services::RefundsService do
279
501
  expect { get_response }.to_not raise_error(/bad URI/)
280
502
  end
281
503
  end
504
+
505
+ describe 'retry behaviour' do
506
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
507
+
508
+ it 'retries timeouts' do
509
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
510
+
511
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
512
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
513
+
514
+ get_response
515
+ expect(stub).to have_been_requested.twice
516
+ end
517
+
518
+ it 'retries 5XX errors' do
519
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
520
+
521
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
522
+ .to_return(status: 502,
523
+ headers: { 'Content-Type' => 'text/html' },
524
+ body: '<html><body>Response from Cloudflare</body></html>')
525
+ .then.to_return(status: 200, headers: response_headers)
526
+
527
+ get_response
528
+ expect(stub).to have_been_requested.twice
529
+ end
530
+ end
282
531
  end
283
532
 
284
533
  describe '#update' do
@@ -303,7 +552,7 @@ describe GoCardlessPro::Services::RefundsService do
303
552
  'reference' => 'reference-input'
304
553
  }
305
554
  }.to_json,
306
- headers: { 'Content-Type' => 'application/json' }
555
+ headers: response_headers
307
556
  )
308
557
  end
309
558
 
@@ -311,6 +560,31 @@ describe GoCardlessPro::Services::RefundsService do
311
560
  expect(put_update_response).to be_a(GoCardlessPro::Resources::Refund)
312
561
  expect(stub).to have_been_requested
313
562
  end
563
+
564
+ describe 'retry behaviour' do
565
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
566
+
567
+ it 'retries timeouts' do
568
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
569
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
570
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
571
+
572
+ put_update_response
573
+ expect(stub).to have_been_requested.twice
574
+ end
575
+
576
+ it 'retries 5XX errors' do
577
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
578
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
579
+ .to_return(status: 502,
580
+ headers: { 'Content-Type' => 'text/html' },
581
+ body: '<html><body>Response from Cloudflare</body></html>')
582
+ .then.to_return(status: 200, headers: response_headers)
583
+
584
+ put_update_response
585
+ expect(stub).to have_been_requested.twice
586
+ end
587
+ end
314
588
  end
315
589
  end
316
590
  end
@@ -7,6 +7,8 @@ describe GoCardlessPro::Services::SubscriptionsService 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.subscriptions.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -82,13 +84,36 @@ describe GoCardlessPro::Services::SubscriptionsService do
82
84
  }
83
85
 
84
86
  }.to_json,
85
- headers: { 'Content-Type' => 'application/json' }
87
+ headers: response_headers
86
88
  )
87
89
  end
88
90
 
89
91
  it 'creates and returns the resource' do
90
92
  expect(post_create_response).to be_a(GoCardlessPro::Resources::Subscription)
91
93
  end
94
+
95
+ describe 'retry behaviour' do
96
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
97
+
98
+ it 'retries timeouts' do
99
+ stub = stub_request(:post, %r{.*api.gocardless.com/subscriptions})
100
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
101
+
102
+ post_create_response
103
+ expect(stub).to have_been_requested.twice
104
+ end
105
+
106
+ it 'retries 5XX errors' do
107
+ stub = stub_request(:post, %r{.*api.gocardless.com/subscriptions})
108
+ .to_return(status: 502,
109
+ headers: { 'Content-Type' => 'text/html' },
110
+ body: '<html><body>Response from Cloudflare</body></html>')
111
+ .then.to_return(status: 200, headers: response_headers)
112
+
113
+ post_create_response
114
+ expect(stub).to have_been_requested.twice
115
+ end
116
+ end
92
117
  end
93
118
 
94
119
  context 'with a request that returns a validation error' do
@@ -105,7 +130,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
105
130
  ]
106
131
  }
107
132
  }.to_json,
108
- headers: { 'Content-Type' => 'application/json' },
133
+ headers: response_headers,
109
134
  status: 422
110
135
  )
111
136
  end
@@ -114,42 +139,129 @@ describe GoCardlessPro::Services::SubscriptionsService do
114
139
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
115
140
  end
116
141
  end
142
+
143
+ context 'with a request that returns an idempotent creation conflict error' do
144
+ let(:id) { 'ID123' }
145
+
146
+ let(:new_resource) do
147
+ {
148
+
149
+ 'amount' => 'amount-input',
150
+ 'created_at' => 'created_at-input',
151
+ 'currency' => 'currency-input',
152
+ 'day_of_month' => 'day_of_month-input',
153
+ 'end_date' => 'end_date-input',
154
+ 'id' => 'id-input',
155
+ 'interval' => 'interval-input',
156
+ 'interval_unit' => 'interval_unit-input',
157
+ 'links' => 'links-input',
158
+ 'metadata' => 'metadata-input',
159
+ 'month' => 'month-input',
160
+ 'name' => 'name-input',
161
+ 'payment_reference' => 'payment_reference-input',
162
+ 'start_date' => 'start_date-input',
163
+ 'status' => 'status-input',
164
+ 'upcoming_payments' => 'upcoming_payments-input'
165
+ }
166
+ end
167
+
168
+ let!(:post_stub) do
169
+ stub_request(:post, %r{.*api.gocardless.com/subscriptions}).to_return(
170
+ body: {
171
+ error: {
172
+ type: 'invalid_state',
173
+ code: 409,
174
+ errors: [
175
+ {
176
+ message: 'A resource has already been created with this idempotency key',
177
+ reason: 'idempotent_creation_conflict',
178
+ links: {
179
+ conflicting_resource_id: id
180
+ }
181
+ }
182
+ ]
183
+ }
184
+ }.to_json,
185
+ headers: response_headers,
186
+ status: 409
187
+ )
188
+ end
189
+
190
+ let!(:get_stub) do
191
+ stub_url = "/subscriptions/#{id}"
192
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
193
+ .to_return(
194
+ body: {
195
+ 'subscriptions' => {
196
+
197
+ 'amount' => 'amount-input',
198
+ 'created_at' => 'created_at-input',
199
+ 'currency' => 'currency-input',
200
+ 'day_of_month' => 'day_of_month-input',
201
+ 'end_date' => 'end_date-input',
202
+ 'id' => 'id-input',
203
+ 'interval' => 'interval-input',
204
+ 'interval_unit' => 'interval_unit-input',
205
+ 'links' => 'links-input',
206
+ 'metadata' => 'metadata-input',
207
+ 'month' => 'month-input',
208
+ 'name' => 'name-input',
209
+ 'payment_reference' => 'payment_reference-input',
210
+ 'start_date' => 'start_date-input',
211
+ 'status' => 'status-input',
212
+ 'upcoming_payments' => 'upcoming_payments-input'
213
+ }
214
+ }.to_json,
215
+ headers: response_headers
216
+ )
217
+ end
218
+
219
+ it 'fetches the already-created resource' do
220
+ post_create_response
221
+ expect(post_stub).to have_been_requested
222
+ expect(get_stub).to have_been_requested
223
+ end
224
+ end
117
225
  end
118
226
 
119
227
  describe '#list' do
120
228
  describe 'with no filters' do
121
229
  subject(:get_list_response) { client.subscriptions.list }
122
230
 
123
- before do
124
- stub_request(:get, %r{.*api.gocardless.com/subscriptions}).to_return(
125
- body: {
126
- 'subscriptions' => [{
231
+ let(:body) do
232
+ {
233
+ 'subscriptions' => [{
127
234
 
128
- 'amount' => 'amount-input',
129
- 'created_at' => 'created_at-input',
130
- 'currency' => 'currency-input',
131
- 'day_of_month' => 'day_of_month-input',
132
- 'end_date' => 'end_date-input',
133
- 'id' => 'id-input',
134
- 'interval' => 'interval-input',
135
- 'interval_unit' => 'interval_unit-input',
136
- 'links' => 'links-input',
137
- 'metadata' => 'metadata-input',
138
- 'month' => 'month-input',
139
- 'name' => 'name-input',
140
- 'payment_reference' => 'payment_reference-input',
141
- 'start_date' => 'start_date-input',
142
- 'status' => 'status-input',
143
- 'upcoming_payments' => 'upcoming_payments-input'
144
- }],
145
- meta: {
146
- cursors: {
147
- before: nil,
148
- after: 'ABC123'
149
- }
235
+ 'amount' => 'amount-input',
236
+ 'created_at' => 'created_at-input',
237
+ 'currency' => 'currency-input',
238
+ 'day_of_month' => 'day_of_month-input',
239
+ 'end_date' => 'end_date-input',
240
+ 'id' => 'id-input',
241
+ 'interval' => 'interval-input',
242
+ 'interval_unit' => 'interval_unit-input',
243
+ 'links' => 'links-input',
244
+ 'metadata' => 'metadata-input',
245
+ 'month' => 'month-input',
246
+ 'name' => 'name-input',
247
+ 'payment_reference' => 'payment_reference-input',
248
+ 'start_date' => 'start_date-input',
249
+ 'status' => 'status-input',
250
+ 'upcoming_payments' => 'upcoming_payments-input'
251
+ }],
252
+ meta: {
253
+ cursors: {
254
+ before: nil,
255
+ after: 'ABC123'
150
256
  }
151
- }.to_json,
152
- headers: { 'Content-Type' => 'application/json' }
257
+ }
258
+ }.to_json
259
+ end
260
+
261
+ before do
262
+ stub_request(:get, %r{.*api.gocardless.com/subscriptions}).to_return(
263
+ body: body,
264
+ headers: response_headers
153
265
  )
154
266
  end
155
267
 
@@ -193,6 +305,29 @@ describe GoCardlessPro::Services::SubscriptionsService do
193
305
  end
194
306
 
195
307
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
308
+
309
+ describe 'retry behaviour' do
310
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
311
+
312
+ it 'retries timeouts' do
313
+ stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions})
314
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
315
+
316
+ get_list_response
317
+ expect(stub).to have_been_requested.twice
318
+ end
319
+
320
+ it 'retries 5XX errors' do
321
+ stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions})
322
+ .to_return(status: 502,
323
+ headers: { 'Content-Type' => 'text/html' },
324
+ body: '<html><body>Response from Cloudflare</body></html>')
325
+ .then.to_return(status: 200, headers: response_headers, body: body)
326
+
327
+ get_list_response
328
+ expect(stub).to have_been_requested.twice
329
+ end
330
+ end
196
331
  end
197
332
  end
198
333
 
@@ -224,7 +359,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
224
359
  limit: 1
225
360
  }
226
361
  }.to_json,
227
- headers: { 'Content-Type' => 'application/json' }
362
+ headers: response_headers
228
363
  )
229
364
  end
230
365
 
@@ -255,7 +390,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
255
390
  cursors: {}
256
391
  }
257
392
  }.to_json,
258
- headers: { 'Content-Type' => 'application/json' }
393
+ headers: response_headers
259
394
  )
260
395
  end
261
396
 
@@ -264,6 +399,147 @@ describe GoCardlessPro::Services::SubscriptionsService do
264
399
  expect(first_response_stub).to have_been_requested
265
400
  expect(second_response_stub).to have_been_requested
266
401
  end
402
+
403
+ describe 'retry behaviour' do
404
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
405
+
406
+ it 'retries timeouts' do
407
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions$}).to_return(
408
+ body: {
409
+ 'subscriptions' => [{
410
+
411
+ 'amount' => 'amount-input',
412
+ 'created_at' => 'created_at-input',
413
+ 'currency' => 'currency-input',
414
+ 'day_of_month' => 'day_of_month-input',
415
+ 'end_date' => 'end_date-input',
416
+ 'id' => 'id-input',
417
+ 'interval' => 'interval-input',
418
+ 'interval_unit' => 'interval_unit-input',
419
+ 'links' => 'links-input',
420
+ 'metadata' => 'metadata-input',
421
+ 'month' => 'month-input',
422
+ 'name' => 'name-input',
423
+ 'payment_reference' => 'payment_reference-input',
424
+ 'start_date' => 'start_date-input',
425
+ 'status' => 'status-input',
426
+ 'upcoming_payments' => 'upcoming_payments-input'
427
+ }],
428
+ meta: {
429
+ cursors: { after: 'AB345' },
430
+ limit: 1
431
+ }
432
+ }.to_json,
433
+ headers: response_headers
434
+ )
435
+
436
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions\?after=AB345})
437
+ .to_timeout.then
438
+ .to_return(
439
+ body: {
440
+ 'subscriptions' => [{
441
+
442
+ 'amount' => 'amount-input',
443
+ 'created_at' => 'created_at-input',
444
+ 'currency' => 'currency-input',
445
+ 'day_of_month' => 'day_of_month-input',
446
+ 'end_date' => 'end_date-input',
447
+ 'id' => 'id-input',
448
+ 'interval' => 'interval-input',
449
+ 'interval_unit' => 'interval_unit-input',
450
+ 'links' => 'links-input',
451
+ 'metadata' => 'metadata-input',
452
+ 'month' => 'month-input',
453
+ 'name' => 'name-input',
454
+ 'payment_reference' => 'payment_reference-input',
455
+ 'start_date' => 'start_date-input',
456
+ 'status' => 'status-input',
457
+ 'upcoming_payments' => 'upcoming_payments-input'
458
+ }],
459
+ meta: {
460
+ limit: 2,
461
+ cursors: {}
462
+ }
463
+ }.to_json,
464
+ headers: response_headers
465
+ )
466
+
467
+ client.subscriptions.all.to_a
468
+
469
+ expect(first_response_stub).to have_been_requested
470
+ expect(second_response_stub).to have_been_requested.twice
471
+ end
472
+
473
+ it 'retries 5XX errors' do
474
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions$}).to_return(
475
+ body: {
476
+ 'subscriptions' => [{
477
+
478
+ 'amount' => 'amount-input',
479
+ 'created_at' => 'created_at-input',
480
+ 'currency' => 'currency-input',
481
+ 'day_of_month' => 'day_of_month-input',
482
+ 'end_date' => 'end_date-input',
483
+ 'id' => 'id-input',
484
+ 'interval' => 'interval-input',
485
+ 'interval_unit' => 'interval_unit-input',
486
+ 'links' => 'links-input',
487
+ 'metadata' => 'metadata-input',
488
+ 'month' => 'month-input',
489
+ 'name' => 'name-input',
490
+ 'payment_reference' => 'payment_reference-input',
491
+ 'start_date' => 'start_date-input',
492
+ 'status' => 'status-input',
493
+ 'upcoming_payments' => 'upcoming_payments-input'
494
+ }],
495
+ meta: {
496
+ cursors: { after: 'AB345' },
497
+ limit: 1
498
+ }
499
+ }.to_json,
500
+ headers: response_headers
501
+ )
502
+
503
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions\?after=AB345})
504
+ .to_return(
505
+ status: 502,
506
+ body: '<html><body>Response from Cloudflare</body></html>',
507
+ headers: { 'Content-Type' => 'text/html' }
508
+ ).then.to_return(
509
+ body: {
510
+ 'subscriptions' => [{
511
+
512
+ 'amount' => 'amount-input',
513
+ 'created_at' => 'created_at-input',
514
+ 'currency' => 'currency-input',
515
+ 'day_of_month' => 'day_of_month-input',
516
+ 'end_date' => 'end_date-input',
517
+ 'id' => 'id-input',
518
+ 'interval' => 'interval-input',
519
+ 'interval_unit' => 'interval_unit-input',
520
+ 'links' => 'links-input',
521
+ 'metadata' => 'metadata-input',
522
+ 'month' => 'month-input',
523
+ 'name' => 'name-input',
524
+ 'payment_reference' => 'payment_reference-input',
525
+ 'start_date' => 'start_date-input',
526
+ 'status' => 'status-input',
527
+ 'upcoming_payments' => 'upcoming_payments-input'
528
+ }],
529
+ meta: {
530
+ limit: 2,
531
+ cursors: {}
532
+ }
533
+ }.to_json,
534
+ headers: response_headers
535
+ )
536
+
537
+ client.subscriptions.all.to_a
538
+
539
+ expect(first_response_stub).to have_been_requested
540
+ expect(second_response_stub).to have_been_requested.twice
541
+ end
542
+ end
267
543
  end
268
544
 
269
545
  describe '#get' do
@@ -298,7 +574,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
298
574
  'upcoming_payments' => 'upcoming_payments-input'
299
575
  }
300
576
  }.to_json,
301
- headers: { 'Content-Type' => 'application/json' }
577
+ headers: response_headers
302
578
  )
303
579
  end
304
580
 
@@ -339,7 +615,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
339
615
  'upcoming_payments' => 'upcoming_payments-input'
340
616
  }
341
617
  }.to_json,
342
- headers: { 'Content-Type' => 'application/json' }
618
+ headers: response_headers
343
619
  )
344
620
  end
345
621
 
@@ -353,7 +629,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
353
629
  stub_url = '/subscriptions/:identity'.gsub(':identity', id)
354
630
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
355
631
  body: '',
356
- headers: { 'Content-Type' => 'application/json' }
632
+ headers: response_headers
357
633
  )
358
634
  end
359
635
 
@@ -369,6 +645,33 @@ describe GoCardlessPro::Services::SubscriptionsService do
369
645
  expect { get_response }.to_not raise_error(/bad URI/)
370
646
  end
371
647
  end
648
+
649
+ describe 'retry behaviour' do
650
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
651
+
652
+ it 'retries timeouts' do
653
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
654
+
655
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
656
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
657
+
658
+ get_response
659
+ expect(stub).to have_been_requested.twice
660
+ end
661
+
662
+ it 'retries 5XX errors' do
663
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
664
+
665
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
666
+ .to_return(status: 502,
667
+ headers: { 'Content-Type' => 'text/html' },
668
+ body: '<html><body>Response from Cloudflare</body></html>')
669
+ .then.to_return(status: 200, headers: response_headers)
670
+
671
+ get_response
672
+ expect(stub).to have_been_requested.twice
673
+ end
674
+ end
372
675
  end
373
676
 
374
677
  describe '#update' do
@@ -402,7 +705,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
402
705
  'upcoming_payments' => 'upcoming_payments-input'
403
706
  }
404
707
  }.to_json,
405
- headers: { 'Content-Type' => 'application/json' }
708
+ headers: response_headers
406
709
  )
407
710
  end
408
711
 
@@ -410,6 +713,31 @@ describe GoCardlessPro::Services::SubscriptionsService do
410
713
  expect(put_update_response).to be_a(GoCardlessPro::Resources::Subscription)
411
714
  expect(stub).to have_been_requested
412
715
  end
716
+
717
+ describe 'retry behaviour' do
718
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
719
+
720
+ it 'retries timeouts' do
721
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
722
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
723
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
724
+
725
+ put_update_response
726
+ expect(stub).to have_been_requested.twice
727
+ end
728
+
729
+ it 'retries 5XX errors' do
730
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
731
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
732
+ .to_return(status: 502,
733
+ headers: { 'Content-Type' => 'text/html' },
734
+ body: '<html><body>Response from Cloudflare</body></html>')
735
+ .then.to_return(status: 200, headers: response_headers)
736
+
737
+ put_update_response
738
+ expect(stub).to have_been_requested.twice
739
+ end
740
+ end
413
741
  end
414
742
  end
415
743
 
@@ -443,7 +771,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
443
771
  'upcoming_payments' => 'upcoming_payments-input'
444
772
  }
445
773
  }.to_json,
446
- headers: { 'Content-Type' => 'application/json' }
774
+ headers: response_headers
447
775
  )
448
776
  end
449
777
 
@@ -453,6 +781,17 @@ describe GoCardlessPro::Services::SubscriptionsService do
453
781
  expect(stub).to have_been_requested
454
782
  end
455
783
 
784
+ describe 'retry behaviour' do
785
+ it "doesn't retry errors" do
786
+ stub_url = '/subscriptions/:identity/actions/cancel'.gsub(':identity', resource_id)
787
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/)
788
+ .to_timeout
789
+
790
+ expect { post_response }.to raise_error(Faraday::TimeoutError)
791
+ expect(stub).to have_been_requested
792
+ end
793
+ end
794
+
456
795
  context 'when the request needs a body and custom header' do
457
796
  let(:body) { { foo: 'bar' } }
458
797
  let(:headers) { { 'Foo' => 'Bar' } }
@@ -489,7 +828,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
489
828
  'upcoming_payments' => 'upcoming_payments-input'
490
829
  }
491
830
  }.to_json,
492
- headers: { 'Content-Type' => 'application/json' }
831
+ headers: response_headers
493
832
  )
494
833
  end
495
834
  end