charging-client 0.0.2

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.
@@ -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