gocardless_pro 2.24.0 → 2.29.0

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +23 -4
  3. data/lib/gocardless_pro/api_service.rb +4 -0
  4. data/lib/gocardless_pro/client.rb +46 -1
  5. data/lib/gocardless_pro/error/authentication_error.rb +4 -0
  6. data/lib/gocardless_pro/error/permission_error.rb +4 -0
  7. data/lib/gocardless_pro/error/rate_limit_error.rb +4 -0
  8. data/lib/gocardless_pro/middlewares/raise_gocardless_errors.rb +12 -1
  9. data/lib/gocardless_pro/resources/bank_authorisation.rb +81 -0
  10. data/lib/gocardless_pro/resources/billing_request.rb +108 -0
  11. data/lib/gocardless_pro/resources/billing_request_flow.rb +72 -0
  12. data/lib/gocardless_pro/resources/billing_request_template.rb +68 -0
  13. data/lib/gocardless_pro/resources/block.rb +66 -0
  14. data/lib/gocardless_pro/resources/creditor.rb +2 -3
  15. data/lib/gocardless_pro/resources/event.rb +20 -0
  16. data/lib/gocardless_pro/resources/institution.rb +47 -0
  17. data/lib/gocardless_pro/resources/payer_authorisation.rb +131 -0
  18. data/lib/gocardless_pro/resources/payout_item.rb +4 -0
  19. data/lib/gocardless_pro/resources/redirect_flow.rb +2 -0
  20. data/lib/gocardless_pro/resources/scenario_simulator.rb +42 -0
  21. data/lib/gocardless_pro/resources/webhook.rb +62 -0
  22. data/lib/gocardless_pro/services/bank_authorisations_service.rb +80 -0
  23. data/lib/gocardless_pro/services/billing_request_flows_service.rb +70 -0
  24. data/lib/gocardless_pro/services/billing_request_templates_service.rb +131 -0
  25. data/lib/gocardless_pro/services/billing_requests_service.rb +352 -0
  26. data/lib/gocardless_pro/services/blocks_service.rb +223 -0
  27. data/lib/gocardless_pro/services/creditor_bank_accounts_service.rb +1 -5
  28. data/lib/gocardless_pro/services/creditors_service.rb +1 -3
  29. data/lib/gocardless_pro/services/currency_exchange_rates_service.rb +1 -1
  30. data/lib/gocardless_pro/services/customer_bank_accounts_service.rb +1 -5
  31. data/lib/gocardless_pro/services/customers_service.rb +1 -3
  32. data/lib/gocardless_pro/services/events_service.rb +1 -1
  33. data/lib/gocardless_pro/services/instalment_schedules_service.rb +1 -7
  34. data/lib/gocardless_pro/services/institutions_service.rb +56 -0
  35. data/lib/gocardless_pro/services/mandate_import_entries_service.rb +1 -1
  36. data/lib/gocardless_pro/services/mandate_imports_service.rb +0 -6
  37. data/lib/gocardless_pro/services/mandates_service.rb +1 -7
  38. data/lib/gocardless_pro/services/payer_authorisations_service.rb +202 -0
  39. data/lib/gocardless_pro/services/payments_service.rb +1 -7
  40. data/lib/gocardless_pro/services/payout_items_service.rb +1 -1
  41. data/lib/gocardless_pro/services/payouts_service.rb +1 -1
  42. data/lib/gocardless_pro/services/redirect_flows_service.rb +0 -4
  43. data/lib/gocardless_pro/services/refunds_service.rb +1 -3
  44. data/lib/gocardless_pro/services/scenario_simulators_service.rb +170 -0
  45. data/lib/gocardless_pro/services/subscriptions_service.rb +10 -13
  46. data/lib/gocardless_pro/services/tax_rates_service.rb +1 -1
  47. data/lib/gocardless_pro/services/webhooks_service.rb +111 -0
  48. data/lib/gocardless_pro/version.rb +1 -1
  49. data/lib/gocardless_pro.rb +30 -0
  50. data/spec/api_service_spec.rb +12 -1
  51. data/spec/middlewares/raise_gocardless_errors_spec.rb +30 -0
  52. data/spec/resources/bank_authorisation_spec.rb +259 -0
  53. data/spec/resources/billing_request_flow_spec.rb +219 -0
  54. data/spec/resources/billing_request_spec.rb +782 -0
  55. data/spec/resources/billing_request_template_spec.rb +502 -0
  56. data/spec/resources/block_spec.rb +560 -0
  57. data/spec/resources/institution_spec.rb +108 -0
  58. data/spec/resources/payer_authorisation_spec.rb +418 -0
  59. data/spec/resources/redirect_flow_spec.rb +9 -0
  60. data/spec/resources/scenario_simulator_spec.rb +63 -0
  61. data/spec/resources/webhook_spec.rb +323 -0
  62. data/spec/services/bank_authorisations_service_spec.rb +353 -0
  63. data/spec/services/billing_request_flows_service_spec.rb +253 -0
  64. data/spec/services/billing_request_templates_service_spec.rb +789 -0
  65. data/spec/services/billing_requests_service_spec.rb +1082 -0
  66. data/spec/services/blocks_service_spec.rb +823 -0
  67. data/spec/services/creditor_bank_accounts_service_spec.rb +0 -13
  68. data/spec/services/creditors_service_spec.rb +0 -13
  69. data/spec/services/customer_bank_accounts_service_spec.rb +0 -13
  70. data/spec/services/customers_service_spec.rb +0 -13
  71. data/spec/services/instalment_schedules_service_spec.rb +0 -26
  72. data/spec/services/institutions_service_spec.rb +232 -0
  73. data/spec/services/mandate_imports_service_spec.rb +0 -13
  74. data/spec/services/mandates_service_spec.rb +0 -13
  75. data/spec/services/payer_authorisations_service_spec.rb +559 -0
  76. data/spec/services/payments_service_spec.rb +0 -13
  77. data/spec/services/redirect_flows_service_spec.rb +9 -13
  78. data/spec/services/refunds_service_spec.rb +0 -13
  79. data/spec/services/scenario_simulators_service_spec.rb +74 -0
  80. data/spec/services/subscriptions_service_spec.rb +0 -13
  81. data/spec/services/webhooks_service_spec.rb +545 -0
  82. metadata +64 -7
@@ -0,0 +1,323 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoCardlessPro::Resources::Webhook do
4
+ let(:client) do
5
+ GoCardlessPro::Client.new(
6
+ access_token: 'SECRET_TOKEN'
7
+ )
8
+ end
9
+
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
12
+ describe '#list' do
13
+ describe 'with no filters' do
14
+ subject(:get_list_response) { client.webhooks.list }
15
+
16
+ before do
17
+ stub_request(:get, %r{.*api.gocardless.com/webhooks}).to_return(
18
+ body: {
19
+ 'webhooks' => [{
20
+
21
+ 'created_at' => 'created_at-input',
22
+ 'id' => 'id-input',
23
+ 'is_test' => 'is_test-input',
24
+ 'request_body' => 'request_body-input',
25
+ 'request_headers' => 'request_headers-input',
26
+ 'response_body' => 'response_body-input',
27
+ 'response_body_truncated' => 'response_body_truncated-input',
28
+ 'response_code' => 'response_code-input',
29
+ 'response_headers' => 'response_headers-input',
30
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
31
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
32
+ 'successful' => 'successful-input',
33
+ 'url' => 'url-input',
34
+ }],
35
+ meta: {
36
+ cursors: {
37
+ before: nil,
38
+ after: 'ABC123',
39
+ },
40
+ },
41
+ }.to_json,
42
+ headers: response_headers
43
+ )
44
+ end
45
+
46
+ it 'wraps each item in the resource class' do
47
+ expect(get_list_response.records.map(&:class).uniq.first).to eq(GoCardlessPro::Resources::Webhook)
48
+
49
+ expect(get_list_response.records.first.created_at).to eq('created_at-input')
50
+
51
+ expect(get_list_response.records.first.id).to eq('id-input')
52
+
53
+ expect(get_list_response.records.first.is_test).to eq('is_test-input')
54
+
55
+ expect(get_list_response.records.first.request_body).to eq('request_body-input')
56
+
57
+ expect(get_list_response.records.first.request_headers).to eq('request_headers-input')
58
+
59
+ expect(get_list_response.records.first.response_body).to eq('response_body-input')
60
+
61
+ expect(get_list_response.records.first.response_body_truncated).to eq('response_body_truncated-input')
62
+
63
+ expect(get_list_response.records.first.response_code).to eq('response_code-input')
64
+
65
+ expect(get_list_response.records.first.response_headers).to eq('response_headers-input')
66
+
67
+ expect(get_list_response.records.first.response_headers_content_truncated).to eq('response_headers_content_truncated-input')
68
+
69
+ expect(get_list_response.records.first.response_headers_count_truncated).to eq('response_headers_count_truncated-input')
70
+
71
+ expect(get_list_response.records.first.successful).to eq('successful-input')
72
+
73
+ expect(get_list_response.records.first.url).to eq('url-input')
74
+ end
75
+
76
+ it 'exposes the cursors for before and after' do
77
+ expect(get_list_response.before).to eq(nil)
78
+ expect(get_list_response.after).to eq('ABC123')
79
+ end
80
+
81
+ specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
82
+ end
83
+ end
84
+
85
+ describe '#all' do
86
+ let!(:first_response_stub) do
87
+ stub_request(:get, %r{.*api.gocardless.com/webhooks$}).to_return(
88
+ body: {
89
+ 'webhooks' => [{
90
+
91
+ 'created_at' => 'created_at-input',
92
+ 'id' => 'id-input',
93
+ 'is_test' => 'is_test-input',
94
+ 'request_body' => 'request_body-input',
95
+ 'request_headers' => 'request_headers-input',
96
+ 'response_body' => 'response_body-input',
97
+ 'response_body_truncated' => 'response_body_truncated-input',
98
+ 'response_code' => 'response_code-input',
99
+ 'response_headers' => 'response_headers-input',
100
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
101
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
102
+ 'successful' => 'successful-input',
103
+ 'url' => 'url-input',
104
+ }],
105
+ meta: {
106
+ cursors: { after: 'AB345' },
107
+ limit: 1,
108
+ },
109
+ }.to_json,
110
+ headers: response_headers
111
+ )
112
+ end
113
+
114
+ let!(:second_response_stub) do
115
+ stub_request(:get, %r{.*api.gocardless.com/webhooks\?after=AB345}).to_return(
116
+ body: {
117
+ 'webhooks' => [{
118
+
119
+ 'created_at' => 'created_at-input',
120
+ 'id' => 'id-input',
121
+ 'is_test' => 'is_test-input',
122
+ 'request_body' => 'request_body-input',
123
+ 'request_headers' => 'request_headers-input',
124
+ 'response_body' => 'response_body-input',
125
+ 'response_body_truncated' => 'response_body_truncated-input',
126
+ 'response_code' => 'response_code-input',
127
+ 'response_headers' => 'response_headers-input',
128
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
129
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
130
+ 'successful' => 'successful-input',
131
+ 'url' => 'url-input',
132
+ }],
133
+ meta: {
134
+ limit: 2,
135
+ cursors: {},
136
+ },
137
+ }.to_json,
138
+ headers: response_headers
139
+ )
140
+ end
141
+
142
+ it 'automatically makes the extra requests' do
143
+ expect(client.webhooks.all.to_a.length).to eq(2)
144
+ expect(first_response_stub).to have_been_requested
145
+ expect(second_response_stub).to have_been_requested
146
+ end
147
+ end
148
+
149
+ describe '#get' do
150
+ let(:id) { 'ID123' }
151
+
152
+ subject(:get_response) { client.webhooks.get(id) }
153
+
154
+ context 'passing in a custom header' do
155
+ let!(:stub) do
156
+ stub_url = '/webhooks/:identity'.gsub(':identity', id)
157
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).
158
+ with(headers: { 'Foo' => 'Bar' }).
159
+ to_return(
160
+ body: {
161
+ 'webhooks' => {
162
+
163
+ 'created_at' => 'created_at-input',
164
+ 'id' => 'id-input',
165
+ 'is_test' => 'is_test-input',
166
+ 'request_body' => 'request_body-input',
167
+ 'request_headers' => 'request_headers-input',
168
+ 'response_body' => 'response_body-input',
169
+ 'response_body_truncated' => 'response_body_truncated-input',
170
+ 'response_code' => 'response_code-input',
171
+ 'response_headers' => 'response_headers-input',
172
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
173
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
174
+ 'successful' => 'successful-input',
175
+ 'url' => 'url-input',
176
+ },
177
+ }.to_json,
178
+ headers: response_headers
179
+ )
180
+ end
181
+
182
+ subject(:get_response) do
183
+ client.webhooks.get(id, headers: {
184
+ 'Foo' => 'Bar',
185
+ })
186
+ end
187
+
188
+ it 'includes the header' do
189
+ get_response
190
+ expect(stub).to have_been_requested
191
+ end
192
+ end
193
+
194
+ context 'when there is a webhook to return' do
195
+ before do
196
+ stub_url = '/webhooks/:identity'.gsub(':identity', id)
197
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
198
+ body: {
199
+ 'webhooks' => {
200
+
201
+ 'created_at' => 'created_at-input',
202
+ 'id' => 'id-input',
203
+ 'is_test' => 'is_test-input',
204
+ 'request_body' => 'request_body-input',
205
+ 'request_headers' => 'request_headers-input',
206
+ 'response_body' => 'response_body-input',
207
+ 'response_body_truncated' => 'response_body_truncated-input',
208
+ 'response_code' => 'response_code-input',
209
+ 'response_headers' => 'response_headers-input',
210
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
211
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
212
+ 'successful' => 'successful-input',
213
+ 'url' => 'url-input',
214
+ },
215
+ }.to_json,
216
+ headers: response_headers
217
+ )
218
+ end
219
+
220
+ it 'wraps the response in a resource' do
221
+ expect(get_response).to be_a(GoCardlessPro::Resources::Webhook)
222
+ end
223
+ end
224
+
225
+ context 'when nothing is returned' do
226
+ before do
227
+ stub_url = '/webhooks/:identity'.gsub(':identity', id)
228
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
229
+ body: '',
230
+ headers: response_headers
231
+ )
232
+ end
233
+
234
+ it 'returns nil' do
235
+ expect(get_response).to be_nil
236
+ end
237
+ end
238
+
239
+ context "when an ID is specified which can't be included in a valid URI" do
240
+ let(:id) { '`' }
241
+
242
+ it "doesn't raise an error" do
243
+ expect { get_response }.to_not raise_error(/bad URI/)
244
+ end
245
+ end
246
+ end
247
+
248
+ describe '#retry' do
249
+ subject(:post_response) { client.webhooks.retry(resource_id) }
250
+
251
+ let(:resource_id) { 'ABC123' }
252
+
253
+ let!(:stub) do
254
+ # /webhooks/%v/actions/retry
255
+ stub_url = '/webhooks/:identity/actions/retry'.gsub(':identity', resource_id)
256
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
257
+ body: {
258
+ 'webhooks' => {
259
+
260
+ 'created_at' => 'created_at-input',
261
+ 'id' => 'id-input',
262
+ 'is_test' => 'is_test-input',
263
+ 'request_body' => 'request_body-input',
264
+ 'request_headers' => 'request_headers-input',
265
+ 'response_body' => 'response_body-input',
266
+ 'response_body_truncated' => 'response_body_truncated-input',
267
+ 'response_code' => 'response_code-input',
268
+ 'response_headers' => 'response_headers-input',
269
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
270
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
271
+ 'successful' => 'successful-input',
272
+ 'url' => 'url-input',
273
+ },
274
+ }.to_json,
275
+ headers: response_headers
276
+ )
277
+ end
278
+
279
+ it 'wraps the response and calls the right endpoint' do
280
+ expect(post_response).to be_a(GoCardlessPro::Resources::Webhook)
281
+
282
+ expect(stub).to have_been_requested
283
+ end
284
+
285
+ context 'when the request needs a body and custom header' do
286
+ let(:body) { { foo: 'bar' } }
287
+ let(:headers) { { 'Foo' => 'Bar' } }
288
+ subject(:post_response) { client.webhooks.retry(resource_id, body, headers) }
289
+
290
+ let(:resource_id) { 'ABC123' }
291
+
292
+ let!(:stub) do
293
+ # /webhooks/%v/actions/retry
294
+ stub_url = '/webhooks/:identity/actions/retry'.gsub(':identity', resource_id)
295
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
296
+ with(
297
+ body: { foo: 'bar' },
298
+ headers: { 'Foo' => 'Bar' }
299
+ ).to_return(
300
+ body: {
301
+ 'webhooks' => {
302
+
303
+ 'created_at' => 'created_at-input',
304
+ 'id' => 'id-input',
305
+ 'is_test' => 'is_test-input',
306
+ 'request_body' => 'request_body-input',
307
+ 'request_headers' => 'request_headers-input',
308
+ 'response_body' => 'response_body-input',
309
+ 'response_body_truncated' => 'response_body_truncated-input',
310
+ 'response_code' => 'response_code-input',
311
+ 'response_headers' => 'response_headers-input',
312
+ 'response_headers_content_truncated' => 'response_headers_content_truncated-input',
313
+ 'response_headers_count_truncated' => 'response_headers_count_truncated-input',
314
+ 'successful' => 'successful-input',
315
+ 'url' => 'url-input',
316
+ },
317
+ }.to_json,
318
+ headers: response_headers
319
+ )
320
+ end
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,353 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoCardlessPro::Services::BankAuthorisationsService do
4
+ let(:client) do
5
+ GoCardlessPro::Client.new(
6
+ access_token: 'SECRET_TOKEN'
7
+ )
8
+ end
9
+
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
12
+ describe '#get' do
13
+ let(:id) { 'ID123' }
14
+
15
+ subject(:get_response) { client.bank_authorisations.get(id) }
16
+
17
+ context 'passing in a custom header' do
18
+ let!(:stub) do
19
+ stub_url = '/bank_authorisations/:identity'.gsub(':identity', id)
20
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).
21
+ with(headers: { 'Foo' => 'Bar' }).
22
+ to_return(
23
+ body: {
24
+ 'bank_authorisations' => {
25
+
26
+ 'authorisation_type' => 'authorisation_type-input',
27
+ 'authorised_at' => 'authorised_at-input',
28
+ 'created_at' => 'created_at-input',
29
+ 'expires_at' => 'expires_at-input',
30
+ 'id' => 'id-input',
31
+ 'last_visited_at' => 'last_visited_at-input',
32
+ 'links' => 'links-input',
33
+ 'redirect_uri' => 'redirect_uri-input',
34
+ 'url' => 'url-input',
35
+ },
36
+ }.to_json,
37
+ headers: response_headers
38
+ )
39
+ end
40
+
41
+ subject(:get_response) do
42
+ client.bank_authorisations.get(id, headers: {
43
+ 'Foo' => 'Bar',
44
+ })
45
+ end
46
+
47
+ it 'includes the header' do
48
+ get_response
49
+ expect(stub).to have_been_requested
50
+ end
51
+ end
52
+
53
+ context 'when there is a bank_authorisation to return' do
54
+ before do
55
+ stub_url = '/bank_authorisations/:identity'.gsub(':identity', id)
56
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
57
+ body: {
58
+ 'bank_authorisations' => {
59
+
60
+ 'authorisation_type' => 'authorisation_type-input',
61
+ 'authorised_at' => 'authorised_at-input',
62
+ 'created_at' => 'created_at-input',
63
+ 'expires_at' => 'expires_at-input',
64
+ 'id' => 'id-input',
65
+ 'last_visited_at' => 'last_visited_at-input',
66
+ 'links' => 'links-input',
67
+ 'redirect_uri' => 'redirect_uri-input',
68
+ 'url' => 'url-input',
69
+ },
70
+ }.to_json,
71
+ headers: response_headers
72
+ )
73
+ end
74
+
75
+ it 'wraps the response in a resource' do
76
+ expect(get_response).to be_a(GoCardlessPro::Resources::BankAuthorisation)
77
+ end
78
+ end
79
+
80
+ context 'when nothing is returned' do
81
+ before do
82
+ stub_url = '/bank_authorisations/:identity'.gsub(':identity', id)
83
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
84
+ body: '',
85
+ headers: response_headers
86
+ )
87
+ end
88
+
89
+ it 'returns nil' do
90
+ expect(get_response).to be_nil
91
+ end
92
+ end
93
+
94
+ context "when an ID is specified which can't be included in a valid URI" do
95
+ let(:id) { '`' }
96
+
97
+ it "doesn't raise an error" do
98
+ expect { get_response }.to_not raise_error(/bad URI/)
99
+ end
100
+ end
101
+
102
+ describe 'retry behaviour' do
103
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
104
+
105
+ it 'retries timeouts' do
106
+ stub_url = '/bank_authorisations/:identity'.gsub(':identity', id)
107
+
108
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/).
109
+ to_timeout.then.to_return(status: 200, headers: response_headers)
110
+
111
+ get_response
112
+ expect(stub).to have_been_requested.twice
113
+ end
114
+
115
+ it 'retries 5XX errors, other than 500s' do
116
+ stub_url = '/bank_authorisations/:identity'.gsub(':identity', id)
117
+
118
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/).
119
+ to_return(status: 502,
120
+ headers: { 'Content-Type' => 'text/html' },
121
+ body: '<html><body>Response from Cloudflare</body></html>').
122
+ then.to_return(status: 200, headers: response_headers)
123
+
124
+ get_response
125
+ expect(stub).to have_been_requested.twice
126
+ end
127
+
128
+ it 'retries 500 errors returned by the API' do
129
+ stub_url = '/bank_authorisations/:identity'.gsub(':identity', id)
130
+
131
+ gocardless_error = {
132
+ 'error' => {
133
+ 'message' => 'Internal server error',
134
+ 'documentation_url' => 'https://developer.gocardless.com/#gocardless',
135
+ 'errors' => [{
136
+ 'message' => 'Internal server error',
137
+ 'reason' => 'internal_server_error',
138
+ }],
139
+ 'type' => 'gocardless',
140
+ 'code' => 500,
141
+ 'request_id' => 'dummy_request_id',
142
+ 'id' => 'dummy_exception_id',
143
+ },
144
+ }
145
+
146
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/).
147
+ to_return(status: 500,
148
+ headers: response_headers,
149
+ body: gocardless_error.to_json).
150
+ then.to_return(status: 200, headers: response_headers)
151
+
152
+ get_response
153
+ expect(stub).to have_been_requested.twice
154
+ end
155
+ end
156
+ end
157
+
158
+ describe '#create' do
159
+ subject(:post_create_response) { client.bank_authorisations.create(params: new_resource) }
160
+ context 'with a valid request' do
161
+ let(:new_resource) do
162
+ {
163
+
164
+ 'authorisation_type' => 'authorisation_type-input',
165
+ 'authorised_at' => 'authorised_at-input',
166
+ 'created_at' => 'created_at-input',
167
+ 'expires_at' => 'expires_at-input',
168
+ 'id' => 'id-input',
169
+ 'last_visited_at' => 'last_visited_at-input',
170
+ 'links' => 'links-input',
171
+ 'redirect_uri' => 'redirect_uri-input',
172
+ 'url' => 'url-input',
173
+ }
174
+ end
175
+
176
+ before do
177
+ stub_request(:post, %r{.*api.gocardless.com/bank_authorisations}).
178
+ with(
179
+ body: {
180
+ 'bank_authorisations' => {
181
+
182
+ 'authorisation_type' => 'authorisation_type-input',
183
+ 'authorised_at' => 'authorised_at-input',
184
+ 'created_at' => 'created_at-input',
185
+ 'expires_at' => 'expires_at-input',
186
+ 'id' => 'id-input',
187
+ 'last_visited_at' => 'last_visited_at-input',
188
+ 'links' => 'links-input',
189
+ 'redirect_uri' => 'redirect_uri-input',
190
+ 'url' => 'url-input',
191
+ },
192
+ }
193
+ ).
194
+ to_return(
195
+ body: {
196
+ 'bank_authorisations' =>
197
+
198
+ {
199
+
200
+ 'authorisation_type' => 'authorisation_type-input',
201
+ 'authorised_at' => 'authorised_at-input',
202
+ 'created_at' => 'created_at-input',
203
+ 'expires_at' => 'expires_at-input',
204
+ 'id' => 'id-input',
205
+ 'last_visited_at' => 'last_visited_at-input',
206
+ 'links' => 'links-input',
207
+ 'redirect_uri' => 'redirect_uri-input',
208
+ 'url' => 'url-input',
209
+ },
210
+
211
+ }.to_json,
212
+ headers: response_headers
213
+ )
214
+ end
215
+
216
+ it 'creates and returns the resource' do
217
+ expect(post_create_response).to be_a(GoCardlessPro::Resources::BankAuthorisation)
218
+ end
219
+
220
+ describe 'retry behaviour' do
221
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
222
+
223
+ it 'retries timeouts' do
224
+ stub = stub_request(:post, %r{.*api.gocardless.com/bank_authorisations}).
225
+ to_timeout.then.to_return(status: 200, headers: response_headers)
226
+
227
+ post_create_response
228
+ expect(stub).to have_been_requested.twice
229
+ end
230
+
231
+ it 'retries 5XX errors' do
232
+ stub = stub_request(:post, %r{.*api.gocardless.com/bank_authorisations}).
233
+ to_return(status: 502,
234
+ headers: { 'Content-Type' => 'text/html' },
235
+ body: '<html><body>Response from Cloudflare</body></html>').
236
+ then.to_return(status: 200, headers: response_headers)
237
+
238
+ post_create_response
239
+ expect(stub).to have_been_requested.twice
240
+ end
241
+ end
242
+ end
243
+
244
+ context 'with a request that returns a validation error' do
245
+ let(:new_resource) { {} }
246
+
247
+ before do
248
+ stub_request(:post, %r{.*api.gocardless.com/bank_authorisations}).to_return(
249
+ body: {
250
+ error: {
251
+ type: 'validation_failed',
252
+ code: 422,
253
+ errors: [
254
+ { message: 'test error message', field: 'test_field' },
255
+ ],
256
+ },
257
+ }.to_json,
258
+ headers: response_headers,
259
+ status: 422
260
+ )
261
+ end
262
+
263
+ it 'throws the correct error' do
264
+ expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
265
+ end
266
+ end
267
+
268
+ context 'with a request that returns an idempotent creation conflict error' do
269
+ let(:id) { 'ID123' }
270
+
271
+ let(:new_resource) do
272
+ {
273
+
274
+ 'authorisation_type' => 'authorisation_type-input',
275
+ 'authorised_at' => 'authorised_at-input',
276
+ 'created_at' => 'created_at-input',
277
+ 'expires_at' => 'expires_at-input',
278
+ 'id' => 'id-input',
279
+ 'last_visited_at' => 'last_visited_at-input',
280
+ 'links' => 'links-input',
281
+ 'redirect_uri' => 'redirect_uri-input',
282
+ 'url' => 'url-input',
283
+ }
284
+ end
285
+
286
+ let!(:post_stub) do
287
+ stub_request(:post, %r{.*api.gocardless.com/bank_authorisations}).to_return(
288
+ body: {
289
+ error: {
290
+ type: 'invalid_state',
291
+ code: 409,
292
+ errors: [
293
+ {
294
+ message: 'A resource has already been created with this idempotency key',
295
+ reason: 'idempotent_creation_conflict',
296
+ links: {
297
+ conflicting_resource_id: id,
298
+ },
299
+ },
300
+ ],
301
+ },
302
+ }.to_json,
303
+ headers: response_headers,
304
+ status: 409
305
+ )
306
+ end
307
+
308
+ let!(:get_stub) do
309
+ stub_url = "/bank_authorisations/#{id}"
310
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).
311
+ to_return(
312
+ body: {
313
+ 'bank_authorisations' => {
314
+
315
+ 'authorisation_type' => 'authorisation_type-input',
316
+ 'authorised_at' => 'authorised_at-input',
317
+ 'created_at' => 'created_at-input',
318
+ 'expires_at' => 'expires_at-input',
319
+ 'id' => 'id-input',
320
+ 'last_visited_at' => 'last_visited_at-input',
321
+ 'links' => 'links-input',
322
+ 'redirect_uri' => 'redirect_uri-input',
323
+ 'url' => 'url-input',
324
+ },
325
+ }.to_json,
326
+ headers: response_headers
327
+ )
328
+ end
329
+
330
+ context 'with default behaviour' do
331
+ it 'fetches the already-created resource' do
332
+ post_create_response
333
+ expect(post_stub).to have_been_requested
334
+ expect(get_stub).to have_been_requested
335
+ end
336
+ end
337
+
338
+ context 'with on_idempotency_conflict: :raise' do
339
+ let(:client) do
340
+ GoCardlessPro::Client.new(
341
+ access_token: 'SECRET_TOKEN',
342
+ on_idempotency_conflict: :raise
343
+ )
344
+ end
345
+
346
+ it 'raises an IdempotencyConflict error' do
347
+ expect { post_create_response }.
348
+ to raise_error(GoCardlessPro::IdempotencyConflict)
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end