charging-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,184 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Charging::Http do
5
+ let(:mock_response) { double('restclient http response') }
6
+ let(:configuration) { Charging.configuration }
7
+
8
+ describe '.basic_credential_for' do
9
+ it 'should accept only user' do
10
+ expect(described_class.basic_credential_for('user')).to eql 'Basic dXNlcg=='
11
+ end
12
+
13
+ it 'should accept user and password' do
14
+ expect(described_class.basic_credential_for('user', 'pass')).to eql 'Basic dXNlcjpwYXNz'
15
+ end
16
+ end
17
+
18
+ describe '.request_to_api' do
19
+ %w[post put patch].each do |method|
20
+ context 'with body as json' do
21
+ it "should use RestClient.#{method} with the supplied params and common options" do
22
+ expect(RestClient).to receive(method).with(
23
+ "#{configuration.url}/foo",
24
+ '{"hello":"world"}',
25
+ params: {},
26
+ authorization: 'Basic OnNvbWUtYXBwLXRva2Vu',
27
+ content_type: :json,
28
+ accept: :json,
29
+ user_agent: configuration.user_agent
30
+ ).and_return(mock_response)
31
+
32
+ described_class.request_to_api(method, '/foo', {}, 'some-app-token', {hello: 'world'})
33
+ end
34
+ end
35
+
36
+ context 'with body as string' do
37
+ it 'should use RestClient.post with the supplied params and common options' do
38
+ expect(RestClient).to receive(method).with(
39
+ "#{configuration.url}/foo",
40
+ '{"hello":"world"}',
41
+ params: {},
42
+ authorization: 'Basic OnNvbWUtYXBwLXRva2Vu',
43
+ content_type: :json,
44
+ accept: :json,
45
+ user_agent: configuration.user_agent
46
+ ).and_return(mock_response)
47
+
48
+ described_class.request_to_api(method, '/foo', {}, 'some-app-token', '{"hello":"world"}')
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ describe ".delete" do
55
+ it 'should delegate to request_to_api' do
56
+ expect(Charging::Http).to receive(:request_to_api).with(
57
+ :delete,
58
+ '/foo',
59
+ {etag: 'etag'},
60
+ 'some-app-token'
61
+ )
62
+
63
+ described_class.delete('/foo', 'some-app-token', 'etag')
64
+ end
65
+ end
66
+
67
+ describe ".get" do
68
+ it 'should delegate to request_to_api' do
69
+ expect(Charging::Http).to receive(:request_to_api).with(
70
+ :get,
71
+ '/foo',
72
+ {},
73
+ 'some-app-token'
74
+ )
75
+
76
+ described_class.get('/foo', 'some-app-token')
77
+ end
78
+ end
79
+
80
+ %w[post].each do |method|
81
+ describe ".#{method}" do
82
+ it 'should delegate to request_to_api' do
83
+ expect(Charging::Http).to receive(:request_to_api).with(
84
+ method.to_sym,
85
+ '/foo',
86
+ {spam: 'eggs'},
87
+ 'some-app-token',
88
+ 'body'
89
+ )
90
+
91
+ described_class.send(method, '/foo', 'some-app-token', 'body', spam: 'eggs')
92
+ end
93
+ end
94
+ end
95
+
96
+ %w[put patch].each do |method|
97
+ describe ".#{method}" do
98
+ it 'should delegate to request_to_api' do
99
+ expect(Charging::Http).to receive(:request_to_api).with(
100
+ method.to_sym,
101
+ '/foo',
102
+ {etag: 'etag'},
103
+ 'some-app-token',
104
+ 'body'
105
+ )
106
+
107
+ described_class.send(method, '/foo', 'some-app-token', 'etag', 'body')
108
+ end
109
+ end
110
+ end
111
+
112
+ specify('.charging_path') do
113
+ expect(described_class.charging_path('/path')).to eql "#{configuration.url}/path"
114
+ end
115
+
116
+ describe '.common_params' do
117
+ context 'without etag' do
118
+ it 'should to return a hash with request headers' do
119
+ expect(described_class.common_params('token', nil)).to eql({
120
+ accept: :json,
121
+ authorization: 'Basic OnRva2Vu',
122
+ content_type: :json,
123
+ user_agent: configuration.user_agent
124
+ })
125
+ end
126
+ end
127
+
128
+ context 'with etag' do
129
+ it 'should to return a hash with request headers' do
130
+ expect(described_class.common_params('token', 'etag')).to eql({
131
+ accept: :json,
132
+ authorization: 'Basic OnRva2Vu',
133
+ content_type: :json,
134
+ user_agent: configuration.user_agent,
135
+ 'If-Match' => 'etag'
136
+ })
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '.encoded_body' do
142
+ context 'with hash body' do
143
+ it 'should convert to string in json format' do
144
+ expect(described_class.encoded_body({some: 'value'})).to eql('{"some":"value"}')
145
+ end
146
+ end
147
+
148
+ context 'with string body' do
149
+ it 'should repass the original body' do
150
+ expect(described_class.encoded_body('some text')).to eql('some text')
151
+ end
152
+ end
153
+ end
154
+
155
+ describe '.should_follow_redirect' do
156
+ context 'on success response code (200)' do
157
+ it 'should call return on response' do
158
+ response = double(code: 200).tap do |mock|
159
+ expect(mock).not_to receive(:follow_redirection)
160
+ expect(mock).to receive(:return!)
161
+ end
162
+
163
+ described_class.should_follow_redirect.call(response, nil, nil)
164
+ end
165
+ end
166
+
167
+ {
168
+ 301 => 'moved permanently',
169
+ 302 => 'found',
170
+ 307 => 'temporary redirect'
171
+ }.each do |status_code, status_message|
172
+ context "on #{status_message} response code (#{status_code})" do
173
+ it 'should call follow redirection on response' do
174
+ response = double(code: status_code).tap do |mock|
175
+ expect(mock).to receive(:follow_redirection)
176
+ expect(mock).not_to receive(:return!)
177
+ end
178
+
179
+ described_class.should_follow_redirect.call(response, nil, nil)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,442 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Charging::Invoice, :vcr do
6
+ let(:national_identifier) { Faker.cnpj_generator }
7
+ let(:domain) { create_domain(current_account, national_identifier) }
8
+ let(:charge_account) { create_charge_account(domain) }
9
+ let(:attributes) do
10
+ {
11
+ kind: 2,
12
+ amount: 123.45,
13
+ document_number: '000000000000001',
14
+ drawee: {
15
+ name: 'Nome do Sacado',
16
+ address: 'Rua do Carmo, 43',
17
+ city_state: 'Rio de Janeiro/RJ',
18
+ zipcode: '21345-999',
19
+ national_identifier: '37.818.380/0001-86'
20
+ },
21
+ due_date: '2020-12-31'
22
+ }
23
+ end
24
+ let(:invoice) { described_class.new(attributes, domain, charge_account).create! }
25
+
26
+ context 'for new instance' do
27
+ INVOICE_ATTRIBUTES = [
28
+ :kind, :amount, :document_number, :drawee, :due_date, :portfolio_code,
29
+ :charging_features, :supplier_name, :discount, :interest, :rebate,
30
+ :ticket, :protest_code, :protest_days, :instructions, :demonstrative,
31
+ :our_number
32
+ ]
33
+
34
+ let(:response) { double(:response, code: 500) }
35
+
36
+ before do
37
+ VCR.use_cassette('Invoice/for new instance') do
38
+ attributes = Hash[*INVOICE_ATTRIBUTES.map {|attr| [attr, "#{attr} value"] }.flatten]
39
+ @domain = domain
40
+ @charge_account = charge_account
41
+
42
+ @new_invoice = described_class.new(attributes, @domain, @charge_account, response)
43
+ end
44
+ end
45
+
46
+ subject { @new_invoice }
47
+
48
+ INVOICE_ATTRIBUTES.each do |attribute|
49
+ describe attribute do
50
+ subject { super().send(attribute) }
51
+ it { is_expected.to eq "#{attribute} value"}
52
+ end
53
+ end
54
+
55
+ [:uuid, :uri, :etag, :document_date, :paid].each do |attribute|
56
+ describe attribute do
57
+ subject { super().send(attribute) }
58
+ it { is_expected.to be_nil }
59
+ end
60
+ end
61
+
62
+ describe '#domain' do
63
+ subject { super().domain }
64
+ it { is_expected.to eq @domain }
65
+ end
66
+
67
+ describe '#charge_account' do
68
+ subject { super().charge_account }
69
+ it { is_expected.to eq @charge_account }
70
+ end
71
+
72
+ describe '#last_response' do
73
+ subject { super().last_response }
74
+ it { is_expected.to eq response }
75
+ end
76
+
77
+ describe '#errors' do
78
+ subject { super().errors }
79
+ it { is_expected.to eq [] }
80
+ end
81
+
82
+ specify('#persisted?') { expect(subject).to_not be_persisted }
83
+ specify('#deleted?') { expect(subject).to_not be_deleted }
84
+
85
+ describe '#attributes' do
86
+ subject { super().attributes }
87
+ it do
88
+ is_expected.to eq({
89
+ amount: 'amount value',
90
+ kind: 'kind value',
91
+ document_number: 'document_number value',
92
+ drawee: 'drawee value',
93
+ due_date: 'due_date value',
94
+ charging_features: 'charging_features value',
95
+ supplier_name: 'supplier_name value',
96
+ discount: 'discount value',
97
+ interest: 'interest value',
98
+ rebate: 'rebate value',
99
+ ticket: 'ticket value',
100
+ protest_code: 'protest_code value',
101
+ protest_days: 'protest_days value',
102
+ instructions: 'instructions value',
103
+ demonstrative: 'demonstrative value',
104
+ our_number: 'our_number value',
105
+ portfolio_code: 'portfolio_code value'
106
+ })
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#create!' do
112
+ it 'should require a domain and load errors' do
113
+ VCR.use_cassette('Invoice/try create an invoice with invalid domain') do
114
+ invoice = described_class.new(attributes, nil, charge_account)
115
+
116
+ expect(invoice.errors).to be_empty
117
+
118
+ expected_error = [StandardError, 'can not create without a domain']
119
+ expect { invoice.create! }.to raise_error(*expected_error)
120
+
121
+ expect(invoice.errors).to eq ['can not create without a domain']
122
+ end
123
+ end
124
+
125
+ it 'should require a charge account and load errors' do
126
+ VCR.use_cassette('Invoice/try create an invoice with invalid charge account') do
127
+ invoice = described_class.new(attributes, domain, nil)
128
+
129
+ expect(invoice.errors).to be_empty
130
+
131
+ expected_error = [StandardError, 'can not create wihtout a charge account']
132
+ expect { invoice.create! }.to raise_error(*expected_error)
133
+
134
+ expect(invoice.errors).to eq ['can not create wihtout a charge account']
135
+ end
136
+ end
137
+
138
+ context 'when everything is OK' do
139
+ before do
140
+ VCR.use_cassette('Invoice/creating an invoice') do
141
+ @charge_account = charge_account
142
+ @domain = @charge_account.domain
143
+
144
+ @invoice = described_class.new(attributes, domain, charge_account).create!
145
+ end
146
+ end
147
+
148
+ subject { @invoice }
149
+
150
+ [:uuid, :uri, :etag].each do |attribute|
151
+ describe attribute do
152
+ subject { super().send(attribute) }
153
+ it { is_expected.not_to be_nil }
154
+ end
155
+ end
156
+
157
+ it 'should be persisted' do
158
+ expect(subject).to be_persisted
159
+ end
160
+ end
161
+ end
162
+
163
+ describe '#billet_url' do
164
+ context 'for not persisted invoice' do
165
+ subject {
166
+ VCR.use_cassette('Invoice/check billet url for new invoice instance') do
167
+ described_class.new({}, domain, charge_account, nil)
168
+ end
169
+ }
170
+
171
+ describe '#billet_url' do
172
+ subject { super().billet_url }
173
+ it { is_expected.to be_nil }
174
+ end
175
+ end
176
+
177
+ context 'for a persisted invoice' do
178
+ it 'should be nil if something wrong' do
179
+ VCR.use_cassette('Invoice/try get billet url when something is wrong') do
180
+ @invoice = invoice
181
+
182
+ expect(Charging::Http)
183
+ .to receive(:get).with("/invoices/#{@invoice.uuid}/billet/", domain.token)
184
+ .and_return(double(:server_error, code: 500, body: 'generic error message'))
185
+
186
+ expect(@invoice.billet_url).to be_nil
187
+ end
188
+ end
189
+
190
+ it 'should get current billet url' do
191
+ VCR.use_cassette('Invoice/finding current billet url for invoice') do
192
+ @invoice = invoice
193
+ expected_url = %r{http://sandbox.charging.financeconnect.com.br/billets/#{@invoice.uuid}/\w+/}
194
+ expect(@invoice.billet_url).to match expected_url
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ describe '#pay!' do
201
+ context 'when something went wrong' do
202
+ it 'should load raise error' do
203
+ VCR.use_cassette('Invoice/paying an invoice and something is wrong') do
204
+ body = MultiJson.encode({
205
+ amount: invoice.amount,
206
+ date: Time.now.strftime('%Y-%m-%d')
207
+ })
208
+
209
+ expect(Charging::Http)
210
+ .to receive(:post).with("/invoices/#{invoice.uuid}/pay/", domain.token, body, etag: invoice.etag)
211
+ .and_return(double(:response, code: 500))
212
+
213
+ expected_error = [Charging::Http::LastResponseError]
214
+
215
+ expect { invoice.pay! }.to raise_error(*expected_error)
216
+ end
217
+ end
218
+ end
219
+
220
+ context 'when success payment' do
221
+ it 'should update paid value' do
222
+ VCR.use_cassette('Invoice/paying an invoice') do
223
+ invoice.pay!
224
+
225
+ expect(invoice.paid).to eq("123.45")
226
+ end
227
+ end
228
+ end
229
+
230
+ it 'should pass a new amount' do
231
+ VCR.use_cassette('Invoice/paying an invoice with another amount') do
232
+ body = MultiJson.encode({
233
+ amount: 100,
234
+ date: Time.now.strftime('%Y-%m-%d')
235
+ })
236
+
237
+ @invoice = invoice
238
+ @domain = domain
239
+
240
+ expect(Charging::Http)
241
+ .to receive(:post)
242
+ .with("/invoices/#{@invoice.uuid}/pay/", @domain.token, body, etag: @invoice.etag)
243
+ .and_return(double(:response, code: 201))
244
+
245
+ expect(invoice).to receive(:reload_attributes!)
246
+
247
+ invoice.pay!(amount: 100)
248
+ end
249
+ end
250
+
251
+ it 'should pass a payment date' do
252
+ VCR.use_cassette('Invoice/paying an invoice with another date') do
253
+ today = Time.now.strftime('%Y-%m-%d')
254
+
255
+ @invoice = invoice
256
+ @domain = domain
257
+
258
+ body = MultiJson.encode({
259
+ amount: @invoice.amount,
260
+ date: today
261
+ })
262
+
263
+ expect(Charging::Http)
264
+ .to receive(:post).with("/invoices/#{@invoice.uuid}/pay/", @domain.token, body, etag: @invoice.etag)
265
+ .and_return(double(:response, code: 201))
266
+
267
+ expect(invoice).to receive(:reload_attributes!)
268
+
269
+ invoice.pay!(date: today)
270
+ end
271
+ end
272
+
273
+ it 'should pass a note' do
274
+ VCR.use_cassette('Invoice/paying an invoice and adding a note') do
275
+ @invoice = invoice
276
+
277
+ body = MultiJson.encode({
278
+ amount: @invoice.amount,
279
+ date: Time.now.strftime('%Y-%m-%d'),
280
+ note: 'some note for payment'
281
+ })
282
+
283
+ expect(Charging::Http)
284
+ .to receive(:post).with("/invoices/#{@invoice.uuid}/pay/", domain.token, body, etag: @invoice.etag)
285
+ .and_return(double(:response, code: 201))
286
+
287
+ expect(invoice).to receive(:reload_attributes!)
288
+
289
+ invoice.pay!(note: "some note for payment")
290
+ end
291
+ end
292
+ end
293
+
294
+ describe '#payments' do
295
+ context 'invoice without payments' do
296
+ it 'should return an empty array' do
297
+ VCR.use_cassette('Invoice/invoice without payments') do
298
+ expect(invoice.payments).to eq []
299
+ end
300
+ end
301
+ end
302
+
303
+ context 'invoice with payments' do
304
+ it 'should return an empty array' do
305
+ VCR.use_cassette('Invoice/invoice with payments') do
306
+ invoice.pay!
307
+
308
+ expect(invoice.payments).to_not be_empty
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+ describe '#destroy!' do
315
+ it 'should raise delete an invoice at API' do
316
+ VCR.use_cassette('Invoice/try delete an invoice with payments') do
317
+ invoice.pay!
318
+
319
+ expect(invoice.payments).to_not be_empty
320
+
321
+ expect { invoice.destroy! }.to raise_error Charging::Http::LastResponseError
322
+
323
+ expect(invoice).to_not be_deleted
324
+ expect(invoice).to be_persisted
325
+ end
326
+ end
327
+
328
+ it 'should delete an invoice without payments' do
329
+ VCR.use_cassette('Invoice/deleting an invoice without payments') do
330
+ expect(invoice).to be_persisted
331
+
332
+ expect { invoice.destroy! }.to_not raise_error
333
+
334
+ expect(invoice).to be_deleted
335
+ expect(invoice).to_not be_persisted
336
+ end
337
+ end
338
+ end
339
+
340
+ describe '.find_by_uuid' do
341
+ it 'should require an account' do
342
+ expected_error = [ArgumentError, 'domain required']
343
+
344
+ expect { described_class.find_by_uuid(nil, '') }.to raise_error(*expected_error)
345
+ end
346
+
347
+ it 'should require an uuid' do
348
+ VCR.use_cassette('Invoice/try find by uuid an invoice with nil value') do
349
+ expected_error = [ArgumentError, 'uuid required']
350
+
351
+ expect { described_class.find_by_uuid(domain, nil) }.to raise_error(*expected_error)
352
+ end
353
+ end
354
+
355
+ it 'should raise for invalid uuid' do
356
+ VCR.use_cassette('Invoice/try find by uuid an invoice with invalid uuid') do
357
+ expect { described_class.find_by_uuid(domain, 'invalid-uuid') }.to raise_error Charging::Http::LastResponseError
358
+ end
359
+ end
360
+
361
+ it 'should raise if not response to success (200)' do
362
+ VCR.use_cassette('Invoice/try find by uuid an invoice when response not success') do
363
+ response_mock = double('AcceptedResponse', code: 202, to_s: 'AcceptedResponse')
364
+
365
+ expect(described_class)
366
+ .to receive(:get_invoice)
367
+ .with(domain, 'uuid')
368
+ .and_return(response_mock)
369
+
370
+ expect {
371
+ described_class.find_by_uuid(domain, 'uuid')
372
+ }.to raise_error Charging::Http::LastResponseError, 'AcceptedResponse'
373
+ end
374
+ end
375
+
376
+ context 'for a valid uuid' do
377
+ before do
378
+ VCR.use_cassette('Invoice/find by uuid an invoice') do
379
+ @invoice = invoice
380
+ @find_result = described_class.find_by_uuid(domain, @invoice.uuid)
381
+ end
382
+ end
383
+
384
+ subject { @find_result }
385
+
386
+ it 'should instantiate a charge account' do
387
+ expect(subject).to be_an_instance_of(Charging::Invoice)
388
+ end
389
+
390
+ describe '#uri' do
391
+ subject { super().uri }
392
+ it { is_expected.to eq "http://sandbox.charging.financeconnect.com.br/invoices/#{@invoice.uuid}/" }
393
+ end
394
+ end
395
+ end
396
+
397
+ describe '.kinds' do
398
+ it 'should require a domain' do
399
+ expected_error = [ArgumentError, 'domain required']
400
+
401
+ expect { described_class.kinds(nil) }.to raise_error(*expected_error)
402
+ end
403
+
404
+ it 'should raise for invalid domain' do
405
+ VCR.use_cassette('Invoice/try get invoice kinds with invalid domain token') do
406
+ expected_error = [Charging::Http::LastResponseError]
407
+
408
+ expect { described_class.kinds(double(token: 'invalid')) }.to raise_error(*expected_error)
409
+ end
410
+ end
411
+
412
+ it 'should return an array with first page' do
413
+ VCR.use_cassette('Invoice/get first page of invoice kinds') do
414
+ default_result = described_class.kinds(domain)
415
+ first_page_result = described_class.kinds(domain, 1)
416
+
417
+ expect(default_result).to eq first_page_result
418
+
419
+ expect(default_result).to include({"acronym"=>"DM", "itau_code"=>1, "code"=>2, "name"=>"Duplicata Mercantil"})
420
+ expect(default_result.size).to eq 10
421
+ end
422
+ end
423
+
424
+ it 'should return an array with second page' do
425
+ VCR.use_cassette('Invoice/get second page of invoice kinds') do
426
+ result = described_class.kinds(domain, 2)
427
+
428
+ expect(result).to_not include({"acronym"=>"DM", "itau_code"=>1, "code"=>2, "name"=>"Duplicata Mercantil"})
429
+ expect(result.size).to eq 10
430
+ end
431
+ end
432
+
433
+ it 'should return an array of kinds passing new limit per page' do
434
+ VCR.use_cassette('Invoice/get first page of invoice kinds with 12 items per page') do
435
+ result = described_class.kinds(domain, 1, 12)
436
+
437
+ expect(result).to include({"acronym"=>"DM", "itau_code"=>1, "code"=>2, "name"=>"Duplicata Mercantil"})
438
+ expect(result.size).to eq 12
439
+ end
440
+ end
441
+ end
442
+ end