fortnox-api 0.8.2 → 0.9.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.
- checksums.yaml +4 -4
- data/.env.template +7 -0
- data/.env.test +11 -3
- data/.gitignore +7 -1
- data/.rubocop.yml +17 -1
- data/.travis.yml +10 -9
- data/CHANGELOG.md +22 -8
- data/CONTRIBUTE.md +21 -9
- data/DEVELOPER_README.md +72 -0
- data/Guardfile +13 -4
- data/README.md +226 -64
- data/Rakefile +128 -0
- data/bin/get_tokens +79 -0
- data/bin/renew_tokens +28 -0
- data/fortnox-api.gemspec +10 -9
- data/lib/fortnox/api/mappers/base/from_json.rb +4 -3
- data/lib/fortnox/api/mappers/base/to_json.rb +2 -3
- data/lib/fortnox/api/models/base.rb +12 -10
- data/lib/fortnox/api/models/customer.rb +55 -55
- data/lib/fortnox/api/models/label.rb +2 -2
- data/lib/fortnox/api/repositories/authentication.rb +61 -0
- data/lib/fortnox/api/repositories/base/savers.rb +3 -1
- data/lib/fortnox/api/repositories/base.rb +21 -35
- data/lib/fortnox/api/repositories.rb +1 -0
- data/lib/fortnox/api/request_handling.rb +30 -18
- data/lib/fortnox/api/types/document_row.rb +3 -3
- data/lib/fortnox/api/types/enums.rb +27 -11
- data/lib/fortnox/api/types/model.rb +1 -4
- data/lib/fortnox/api/types/sized.rb +2 -2
- data/lib/fortnox/api/types.rb +14 -1
- data/lib/fortnox/api/version.rb +1 -1
- data/lib/fortnox/api.rb +12 -32
- data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +4 -4
- data/spec/fortnox/api/mappers/base/from_json_spec.rb +10 -12
- data/spec/fortnox/api/mappers/base/to_json_spec.rb +48 -57
- data/spec/fortnox/api/mappers/base_spec.rb +4 -7
- data/spec/fortnox/api/mappers/contexts/json_conversion.rb +38 -33
- data/spec/fortnox/api/mappers/unit_spec.rb +3 -4
- data/spec/fortnox/api/models/base_spec.rb +27 -16
- data/spec/fortnox/api/models/unit_spec.rb +5 -3
- data/spec/fortnox/api/repositories/article_spec.rb +14 -9
- data/spec/fortnox/api/repositories/authentication_spec.rb +103 -0
- data/spec/fortnox/api/repositories/base_spec.rb +106 -319
- data/spec/fortnox/api/repositories/customer_spec.rb +37 -7
- data/spec/fortnox/api/repositories/examples/all.rb +0 -1
- data/spec/fortnox/api/repositories/examples/find.rb +5 -8
- data/spec/fortnox/api/repositories/examples/only.rb +4 -13
- data/spec/fortnox/api/repositories/examples/save.rb +32 -18
- data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -5
- data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +1 -4
- data/spec/fortnox/api/repositories/examples/search.rb +4 -7
- data/spec/fortnox/api/repositories/invoice_spec.rb +64 -15
- data/spec/fortnox/api/repositories/order_spec.rb +11 -9
- data/spec/fortnox/api/repositories/project_spec.rb +7 -6
- data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +9 -7
- data/spec/fortnox/api/repositories/unit_spec.rb +13 -11
- data/spec/fortnox/api/types/country_spec.rb +1 -1
- data/spec/fortnox/api/types/email_spec.rb +2 -2
- data/spec/fortnox/api/types/examples/document_row.rb +3 -3
- data/spec/fortnox/api/types/examples/enum.rb +4 -4
- data/spec/fortnox/api/types/examples/types.rb +1 -3
- data/spec/fortnox/api/types/housework_types_spec.rb +54 -61
- data/spec/fortnox/api/types/model_spec.rb +3 -27
- data/spec/fortnox/api/types/order_row_spec.rb +2 -2
- data/spec/fortnox/api/types/required_spec.rb +6 -11
- data/spec/fortnox/api/types/sales_account_spec.rb +57 -0
- data/spec/fortnox/api_spec.rb +19 -124
- data/spec/spec_helper.rb +0 -14
- data/spec/support/helpers/configuration_helper.rb +30 -3
- data/spec/support/helpers.rb +1 -1
- data/spec/support/matchers/type/attribute_matcher.rb +2 -2
- data/spec/support/matchers/type/have_nullable_date_matcher.rb +6 -4
- data/spec/support/matchers/type/have_nullable_matcher.rb +1 -1
- data/spec/support/matchers/type/have_nullable_string_matcher.rb +5 -5
- data/spec/support/matchers/type/require_attribute_matcher.rb +5 -5
- data/spec/support/matchers/type/type_matcher.rb +1 -1
- data/spec/support/vcr_setup.rb +16 -0
- data/spec/vcr_cassettes/articles/all.yml +16 -43
- data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/articles/find_failure.yml +10 -12
- data/spec/vcr_cassettes/articles/find_id_1.yml +13 -14
- data/spec/vcr_cassettes/articles/find_new.yml +14 -16
- data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/articles/save_new.yml +13 -15
- data/spec/vcr_cassettes/articles/save_old.yml +14 -16
- data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +13 -15
- data/spec/vcr_cassettes/articles/search_by_name.yml +16 -15
- data/spec/vcr_cassettes/articles/search_miss.yml +10 -12
- data/spec/vcr_cassettes/articles/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +13 -27
- data/spec/vcr_cassettes/authentication/expired_token.yml +54 -0
- data/spec/vcr_cassettes/authentication/invalid_authorization.yml +57 -0
- data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +58 -0
- data/spec/vcr_cassettes/authentication/valid_request.yml +63 -0
- data/spec/vcr_cassettes/customers/all.yml +20 -127
- data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/customers/find_failure.yml +10 -12
- data/spec/vcr_cassettes/customers/find_id_1.yml +14 -15
- data/spec/vcr_cassettes/customers/find_new.yml +13 -15
- data/spec/vcr_cassettes/customers/find_with_sales_account.yml +63 -0
- data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/customers/save_new.yml +12 -14
- data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +12 -14
- data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +63 -0
- data/spec/vcr_cassettes/customers/save_old.yml +13 -15
- data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +12 -14
- data/spec/vcr_cassettes/customers/search_by_name.yml +13 -45
- data/spec/vcr_cassettes/customers/search_miss.yml +10 -12
- data/spec/vcr_cassettes/customers/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/invoices/all.yml +47 -112
- data/spec/vcr_cassettes/invoices/filter_hit.yml +14 -18
- data/spec/vcr_cassettes/invoices/filter_invalid.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_failure.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_id_1.yml +15 -16
- data/spec/vcr_cassettes/invoices/find_new.yml +16 -18
- data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/invoices/row_description_limit.yml +65 -0
- data/spec/vcr_cassettes/invoices/save_new.yml +14 -16
- data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +14 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +65 -0
- data/spec/vcr_cassettes/invoices/save_old.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +16 -17
- data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +16 -17
- data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +14 -15
- data/spec/vcr_cassettes/invoices/search_by_name.yml +13 -21
- data/spec/vcr_cassettes/invoices/search_miss.yml +10 -12
- data/spec/vcr_cassettes/invoices/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/orders/all.yml +19 -113
- data/spec/vcr_cassettes/orders/filter_hit.yml +14 -20
- data/spec/vcr_cassettes/orders/filter_invalid.yml +10 -12
- data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/orders/find_failure.yml +10 -12
- data/spec/vcr_cassettes/orders/find_id_1.yml +17 -17
- data/spec/vcr_cassettes/orders/find_new.yml +16 -18
- data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_construction.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_cooking.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_type_electricity.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_gardening.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_hvac.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_itservices.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_masonry.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_othercare.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +11 -13
- data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/orders/save_new.yml +16 -18
- data/spec/vcr_cassettes/orders/save_old.yml +16 -18
- data/spec/vcr_cassettes/orders/save_with_nested_model.yml +15 -16
- data/spec/vcr_cassettes/orders/search_by_name.yml +13 -17
- data/spec/vcr_cassettes/orders/search_miss.yml +10 -12
- data/spec/vcr_cassettes/orders/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/projects/all.yml +14 -37
- data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/projects/find_failure.yml +10 -12
- data/spec/vcr_cassettes/projects/find_id_1.yml +13 -15
- data/spec/vcr_cassettes/projects/find_new.yml +14 -16
- data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +15 -16
- data/spec/vcr_cassettes/projects/save_new.yml +13 -15
- data/spec/vcr_cassettes/projects/save_old.yml +14 -16
- data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/all.yml +16 -23
- data/spec/vcr_cassettes/termsofpayments/find_failure.yml +10 -12
- data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +13 -16
- data/spec/vcr_cassettes/termsofpayments/find_new.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/save_new.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/save_old.yml +12 -14
- data/spec/vcr_cassettes/units/all.yml +13 -24
- data/spec/vcr_cassettes/units/find_failure.yml +10 -12
- data/spec/vcr_cassettes/units/find_id_1.yml +13 -15
- data/spec/vcr_cassettes/units/find_new.yml +12 -14
- data/spec/vcr_cassettes/units/save_new.yml +12 -14
- data/spec/vcr_cassettes/units/save_old.yml +12 -14
- data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +12 -14
- metadata +39 -230
- data/lib/fortnox/api/circular_queue.rb +0 -39
- data/spec/fortnox/api/circular_queue_spec.rb +0 -52
- data/spec/support/helpers/when_performing_helper.rb +0 -7
- data/temp.txt +0 -1
@@ -2,380 +2,167 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
require 'fortnox/api'
|
5
|
+
require 'jwt'
|
6
|
+
require 'dry/container/stub'
|
7
|
+
|
8
|
+
describe Fortnox::API::Repository::Base, integration: true do
|
9
|
+
include Helpers::Configuration
|
5
10
|
|
6
|
-
describe Fortnox::API::Repository::Base do
|
7
11
|
before do
|
8
|
-
stub_const('
|
9
|
-
stub_const('
|
10
|
-
stub_const('
|
12
|
+
stub_const('TestModel', Class.new)
|
13
|
+
stub_const('TestRepository', Class.new(described_class))
|
14
|
+
stub_const('TestRepository::MODEL', TestModel)
|
11
15
|
|
12
|
-
Fortnox::API::Registry.
|
13
|
-
end
|
16
|
+
Fortnox::API::Registry.enable_stubs!
|
14
17
|
|
15
|
-
|
16
|
-
# HACK: Currently, there is no way to remove keys from the Dry::Container#register.
|
17
|
-
# We could move the register call to a before(:all) hook, but that registered key
|
18
|
-
# would then leak into other tests. Instead, we can simply delete it with this little hack :)
|
19
|
-
Fortnox::API::Registry._container.delete('repositorybasetest')
|
18
|
+
add_to_registry(:testmodel, TestModel)
|
20
19
|
end
|
21
20
|
|
22
|
-
let(:
|
23
|
-
let(:access_token2) { '89feajou-sif8-8f8u-29ja-xdfniokeniod' }
|
24
|
-
let(:client_secret) { 'P5K5vE3Kun' }
|
25
|
-
let(:repository) { Repository::Test.new }
|
26
|
-
let(:application_json) {}
|
27
|
-
let(:headers) do
|
28
|
-
{
|
29
|
-
'Access-Token' => access_token,
|
30
|
-
'Client-Secret' => client_secret,
|
31
|
-
'Content-Type' => 'application/json',
|
32
|
-
'Accept' => 'application/json'
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
describe 'creation' do
|
37
|
-
shared_examples_for 'missing configuration' do
|
38
|
-
subject { -> { Repository::Test.new(token_store: :store1).get('nonsense') } }
|
39
|
-
|
40
|
-
let(:error) { Fortnox::API::MissingConfiguration }
|
41
|
-
|
42
|
-
it { is_expected.to raise_error(error, /#{message}/) }
|
43
|
-
end
|
44
|
-
|
45
|
-
context 'without base url' do
|
46
|
-
include_examples 'missing configuration' do
|
47
|
-
before do
|
48
|
-
Fortnox::API.configure do |config|
|
49
|
-
config.base_url = nil
|
50
|
-
config.client_secret = client_secret
|
51
|
-
config.access_tokens = { store1: access_token }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
let(:message) { 'have to provide a base url' }
|
56
|
-
end
|
57
|
-
end
|
21
|
+
let(:repository) { TestRepository.new }
|
58
22
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
Fortnox::API.configure do |config|
|
63
|
-
config.client_secret = nil
|
64
|
-
config.access_tokens = { store1: access_token }
|
65
|
-
end
|
66
|
-
end
|
23
|
+
describe '#initialize' do
|
24
|
+
context 'without providing an access token' do
|
25
|
+
before { Fortnox::API.access_token = nil }
|
67
26
|
|
68
|
-
|
27
|
+
it 'raises an error' do
|
28
|
+
expect { TestRepository.new }.to raise_error(Fortnox::API::MissingAccessToken)
|
69
29
|
end
|
70
30
|
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe '#next_access_token' do
|
74
|
-
before { Fortnox::API.configure { |conf| conf.client_secret = client_secret } }
|
75
|
-
|
76
|
-
context 'with default token store' do
|
77
|
-
context 'with one access token' do
|
78
|
-
subject { repository.next_access_token }
|
79
|
-
|
80
|
-
before { Fortnox::API.configure { |conf| conf.access_token = access_token } }
|
81
|
-
|
82
|
-
it { is_expected.to eql(access_token) }
|
83
|
-
end
|
84
|
-
|
85
|
-
context 'with one access token and two subsequent requests' do
|
86
|
-
subject { repository.next_access_token }
|
87
|
-
|
88
|
-
before do
|
89
|
-
Fortnox::API.configure { |conf| conf.access_token = access_token }
|
90
|
-
repository.next_access_token
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'still uses the same token' do
|
94
|
-
is_expected.to eql(access_token)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
context 'with multiple access tokens' do
|
99
|
-
subject { access_tokens }
|
100
|
-
|
101
|
-
before { Fortnox::API.configure { |conf| conf.access_tokens = access_tokens } }
|
102
|
-
let(:access_tokens) { [access_token, access_token2] }
|
103
|
-
|
104
|
-
it { is_expected.to include(repository.next_access_token) }
|
105
|
-
|
106
|
-
it 'changes token on next request' do
|
107
|
-
token1 = repository.next_access_token
|
108
|
-
token2 = repository.next_access_token
|
109
|
-
|
110
|
-
expect(token1).not_to eql(token2)
|
111
|
-
end
|
112
|
-
|
113
|
-
it 'circulates tokens' do
|
114
|
-
token1 = repository.next_access_token
|
115
|
-
repository.next_access_token
|
116
|
-
token3 = repository.next_access_token
|
117
|
-
|
118
|
-
expect(token1).to eql(token3)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
context 'with multiple stores' do
|
124
|
-
subject { repository.next_access_token }
|
125
|
-
|
126
|
-
before do
|
127
|
-
Fortnox::API.configure do |config|
|
128
|
-
config.access_tokens = { store1: access_token, store2: access_token2 }
|
129
|
-
end
|
130
|
-
end
|
131
31
|
|
132
|
-
|
133
|
-
|
32
|
+
context 'with expired access token' do
|
33
|
+
before { Fortnox::API.access_token = ENV.fetch('EXPIRED_ACCESS_TOKEN') }
|
134
34
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
let(:repository) { Repository::Test.new(token_store: :store2) }
|
140
|
-
|
141
|
-
it { is_expected.to eql access_token2 }
|
35
|
+
it 'raises an error' do
|
36
|
+
expect do
|
37
|
+
VCR.use_cassette('authentication/expired_token') { repository.get('/customers', body: '') }
|
38
|
+
end.to raise_error(Fortnox::API::RemoteServerError, /Unauthorized request(.)*"message":"unauthorized"/)
|
142
39
|
end
|
143
40
|
end
|
144
41
|
end
|
145
42
|
|
146
|
-
describe '#access_tokens' do
|
147
|
-
subject(:access_tokens) { repository.access_tokens }
|
148
|
-
|
149
|
-
before { Fortnox::API.configure { |conf| conf.client_secret = client_secret } }
|
150
|
-
|
151
|
-
let(:token_store_not_present) do
|
152
|
-
"There is no token store named #{token_store.inspect}. Available stores are #{available_stores}."
|
153
|
-
end
|
154
|
-
|
155
|
-
let(:error) { Fortnox::API::MissingConfiguration }
|
156
|
-
|
157
|
-
context 'with non existing token store' do
|
158
|
-
subject { -> { access_tokens } }
|
159
|
-
|
160
|
-
before do
|
161
|
-
Fortnox::API.configure { |conf| conf.access_tokens = { some_store: [access_token] } }
|
162
|
-
end
|
163
|
-
|
164
|
-
let(:repository) { Repository::Test.new(token_store: token_store) }
|
165
|
-
let(:token_store) { :non_existing_store }
|
166
|
-
let(:available_stores) { [:some_store] }
|
167
|
-
|
168
|
-
it { is_expected.to raise_error(error, token_store_not_present) }
|
169
|
-
end
|
170
|
-
|
171
|
-
context 'with no tokens set' do
|
172
|
-
subject { -> { access_tokens } }
|
173
|
-
|
174
|
-
before { Fortnox::API.configure { |conf| conf.access_tokens = {} } }
|
175
|
-
let(:token_store) { :default }
|
176
|
-
let(:available_stores) { [] }
|
177
|
-
|
178
|
-
it { is_expected.to raise_error(error, token_store_not_present) }
|
179
|
-
end
|
180
|
-
|
181
|
-
context 'with one access token in token store' do
|
182
|
-
before { Fortnox::API.configure { |conf| conf.access_token = access_token } }
|
183
|
-
let(:token_store) { :default }
|
184
|
-
|
185
|
-
it { is_expected.to eql(access_token) }
|
186
|
-
end
|
187
|
-
|
188
|
-
context 'with multiple access tokens' do
|
189
|
-
before do
|
190
|
-
Fortnox::API.configure { |conf| conf.access_tokens = [access_token, access_token2] }
|
191
|
-
end
|
192
|
-
let(:token_store) { :default }
|
193
|
-
|
194
|
-
it { is_expected.to eql([access_token, access_token2]) }
|
195
|
-
end
|
196
|
-
|
197
|
-
context 'with multiple token stores' do
|
198
|
-
before do
|
199
|
-
Fortnox::API.configure do |conf|
|
200
|
-
conf.access_tokens = { store_a: store_a_tokens, store_b: store_b_token }
|
201
|
-
end
|
202
|
-
end
|
203
|
-
let(:store_a_tokens) { %w[token_a1 token_a2] }
|
204
|
-
let(:store_b_token) { 'token_b1' }
|
205
|
-
|
206
|
-
context 'with valid store name' do
|
207
|
-
let(:repository) { Repository::Test.new(token_store: :store_a) }
|
208
|
-
|
209
|
-
it { is_expected.to eql(store_a_tokens) }
|
210
|
-
end
|
211
|
-
|
212
|
-
context 'with non collection' do
|
213
|
-
let(:repository) { Repository::Test.new(token_store: :store_b) }
|
214
|
-
|
215
|
-
it { is_expected.to eql(store_b_token) }
|
216
|
-
end
|
217
|
-
|
218
|
-
context 'with invalid store name' do
|
219
|
-
subject { -> { access_tokens } }
|
220
|
-
|
221
|
-
let(:repository) { Repository::Test.new(token_store: :nonsence_store) }
|
222
|
-
|
223
|
-
it { is_expected.to raise_error(error) }
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
describe '#check_access_tokens!' do
|
229
|
-
subject { -> { repository.check_access_tokens!(tokens) } }
|
230
|
-
|
231
|
-
before { Fortnox::API.configure { |conf| conf.client_secret = client_secret } }
|
232
|
-
let(:error) { Fortnox::API::MissingConfiguration }
|
233
|
-
let(:message) { "not provided any access tokens in token store #{token_store.inspect}" }
|
234
|
-
let(:token_store) { :default }
|
235
|
-
|
236
|
-
context 'with nil' do
|
237
|
-
let(:tokens) { nil }
|
238
|
-
|
239
|
-
it { is_expected.to raise_error(error, /#{message}/) }
|
240
|
-
end
|
241
|
-
|
242
|
-
context 'with empty array' do
|
243
|
-
let(:tokens) { [] }
|
244
|
-
|
245
|
-
it { is_expected.to raise_error(error, /#{message}/) }
|
246
|
-
end
|
247
|
-
|
248
|
-
context 'with an empty, non default, token store' do
|
249
|
-
before { Fortnox::API.configure { |conf| conf.access_tokens = { token_store => tokens } } }
|
250
|
-
let(:repository) { Repository::Test.new(token_store: token_store) }
|
251
|
-
let(:tokens) { [] }
|
252
|
-
let(:token_store) { :store1 }
|
253
|
-
|
254
|
-
it { is_expected.to raise_error(error, /#{message}/) }
|
255
|
-
end
|
256
|
-
|
257
|
-
context 'with valid tokens' do
|
258
|
-
let(:tokens) { %w[12345 abcde] }
|
259
|
-
|
260
|
-
it { is_expected.not_to raise_error }
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
43
|
context 'when making a request including the proper headers' do
|
44
|
+
subject { repository.get('/test', body: '') }
|
45
|
+
|
265
46
|
before do
|
266
|
-
|
267
|
-
conf.client_secret = client_secret
|
268
|
-
conf.access_token = access_token
|
269
|
-
end
|
47
|
+
set_api_test_configuration
|
270
48
|
|
271
49
|
stub_request(
|
272
50
|
:get,
|
273
51
|
'https://api.fortnox.se/3/test'
|
274
|
-
).with(
|
275
|
-
headers: headers
|
276
52
|
).to_return(
|
277
53
|
status: 200
|
278
54
|
)
|
279
55
|
end
|
280
56
|
|
281
|
-
subject { repository.get('/test', body: '') }
|
282
|
-
|
283
57
|
it { is_expected.to be_nil }
|
284
58
|
end
|
285
59
|
|
286
|
-
|
60
|
+
context 'when receiving an error from remote server' do
|
287
61
|
before do
|
288
|
-
|
289
|
-
conf.client_secret = client_secret
|
290
|
-
conf.access_tokens = [access_token, access_token2]
|
291
|
-
end
|
62
|
+
set_api_test_configuration
|
292
63
|
|
293
64
|
stub_request(
|
294
|
-
:
|
65
|
+
:post,
|
295
66
|
'https://api.fortnox.se/3/test'
|
296
|
-
).with(
|
297
|
-
headers: {
|
298
|
-
'Access-Token' => access_token,
|
299
|
-
'Client-Secret' => client_secret,
|
300
|
-
'Content-Type' => 'application/json',
|
301
|
-
'Accept' => 'application/json'
|
302
|
-
}
|
303
67
|
).to_return(
|
304
|
-
status:
|
305
|
-
body:
|
68
|
+
status: 500,
|
69
|
+
body: {
|
70
|
+
'ErrorInformation' => {
|
71
|
+
'error' => 1,
|
72
|
+
'message' => 'some-error-message'
|
73
|
+
}
|
74
|
+
}.to_json,
|
75
|
+
headers: { 'Content-Type' => 'application/json' }
|
306
76
|
)
|
77
|
+
end
|
307
78
|
|
308
|
-
|
309
|
-
|
310
|
-
'
|
311
|
-
|
312
|
-
headers: {
|
313
|
-
'Access-Token' => access_token2,
|
314
|
-
'Client-Secret' => client_secret,
|
315
|
-
'Content-Type' => 'application/json',
|
316
|
-
'Accept' => 'application/json'
|
317
|
-
}
|
318
|
-
).to_return(
|
319
|
-
status: 200,
|
320
|
-
body: '2'
|
321
|
-
)
|
79
|
+
it 'raises an error' do
|
80
|
+
expect do
|
81
|
+
repository.post('/test', body: '')
|
82
|
+
end.to raise_error(Fortnox::API::RemoteServerError, /some-error-message/)
|
322
83
|
end
|
323
84
|
|
324
|
-
context 'with
|
325
|
-
|
326
|
-
|
327
|
-
let!(:response3) { repository.get('/test', body: '') }
|
85
|
+
context 'with debugging enabled' do
|
86
|
+
before { Fortnox::API.config.debugging = true }
|
87
|
+
after { Fortnox::API.config.debugging = false }
|
328
88
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
expect(response1).not_to eq(response2)
|
334
|
-
expect(response1).to eq(response3)
|
335
|
-
expect(response3).not_to eq(response2)
|
89
|
+
it do
|
90
|
+
expect do
|
91
|
+
repository.post('/test', body: '')
|
92
|
+
end.to raise_error(/<HTTParty::Request:0x/)
|
336
93
|
end
|
337
|
-
# rubocop:enable RSpec/MultipleExpectations
|
338
94
|
end
|
339
95
|
end
|
340
96
|
|
341
|
-
context 'when
|
342
|
-
error_message = 'Räkenskapsår finns inte upplagt. '\
|
343
|
-
'För att kunna skapa en faktura krävs det att det finns ett räkenskapsår'
|
344
|
-
|
97
|
+
context 'when receiving HTML from remote server' do
|
345
98
|
before do
|
346
|
-
|
347
|
-
conf.client_secret = client_secret
|
348
|
-
conf.access_tokens = [access_token, access_token2]
|
349
|
-
end
|
99
|
+
set_api_test_configuration
|
350
100
|
|
351
101
|
stub_request(
|
352
|
-
:
|
353
|
-
'https://api.fortnox.se/
|
102
|
+
:get,
|
103
|
+
'https://api.fortnox.se/3nonsense'
|
354
104
|
).to_return(
|
355
|
-
status:
|
356
|
-
body:
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
}.to_json,
|
362
|
-
headers: { 'Content-Type' => 'application/json' }
|
105
|
+
status: 404,
|
106
|
+
body: "<html>\r\n" \
|
107
|
+
"<head><title>404 Not Found</title></head>\r\n" \
|
108
|
+
"<body>\r\n<center><h1>404 Not Found</h1></center>\r\n" \
|
109
|
+
"<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n",
|
110
|
+
headers: { 'Content-Type' => 'text/html' }
|
363
111
|
)
|
364
112
|
end
|
365
113
|
|
366
|
-
|
114
|
+
it 'raises an error' do
|
115
|
+
expect do
|
116
|
+
repository.get('nonsense')
|
117
|
+
end.to raise_error(Fortnox::API::RemoteServerError,
|
118
|
+
%r{Fortnox API's response has content type "text/html"})
|
119
|
+
end
|
120
|
+
end
|
367
121
|
|
368
|
-
|
369
|
-
|
122
|
+
describe 'update existing' do
|
123
|
+
context 'with no change' do
|
124
|
+
before do
|
125
|
+
set_api_test_configuration
|
370
126
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
127
|
+
stub_const(
|
128
|
+
'Product',
|
129
|
+
Class.new(Fortnox::API::Model::Base) do
|
130
|
+
attribute :name, 'strict.string'
|
131
|
+
attribute :description, 'string' # nullable
|
132
|
+
end
|
133
|
+
)
|
134
|
+
stub_const('Product::STUB', { name: '' }.freeze)
|
135
|
+
stub_const('Product::UNIQUE_ID', :name)
|
136
|
+
|
137
|
+
stub_const('ProductMapper', Class.new(Fortnox::API::Mapper::Base))
|
138
|
+
stub_const('ProductMapper::JSON_ENTITY_WRAPPER', 'Product')
|
139
|
+
stub_const('ProductMapper::JSON_COLLECTION_WRAPPER', 'Products')
|
140
|
+
stub_const('ProductMapper::KEY_MAP', {})
|
141
|
+
|
142
|
+
stub_const('TestRepository::MODEL', Product)
|
143
|
+
stub_const('TestRepository::MAPPER', ProductMapper)
|
144
|
+
stub_const('TestRepository::URI', '/test/')
|
145
|
+
|
146
|
+
Fortnox::API::Registry.enable_stubs!
|
147
|
+
add_to_registry(:product, ProductMapper)
|
148
|
+
|
149
|
+
stub_request(
|
150
|
+
:post,
|
151
|
+
'https://api.fortnox.se/3/test/'
|
152
|
+
).to_return(
|
153
|
+
status: 200,
|
154
|
+
body: '{"Product" : {"Name": "test", "Desription": null}}',
|
155
|
+
headers: { 'Content-Type' => 'application/json' }
|
156
|
+
).times(1) # Only stub the first save request
|
157
|
+
end
|
158
|
+
|
159
|
+
let(:updated_entity) do
|
160
|
+
repository.save(Product.new(name: 'test')).update({ description: nil })
|
376
161
|
end
|
377
162
|
|
378
|
-
it
|
163
|
+
it 'does not call Fortnox' do
|
164
|
+
expect { repository.save(updated_entity) }.not_to raise_error
|
165
|
+
end
|
379
166
|
end
|
380
167
|
end
|
381
168
|
end
|
@@ -10,13 +10,14 @@ require 'fortnox/api/repositories/examples/save'
|
|
10
10
|
require 'fortnox/api/repositories/examples/save_with_specially_named_attribute'
|
11
11
|
require 'fortnox/api/repositories/examples/search'
|
12
12
|
|
13
|
-
describe Fortnox::API::Repository::Customer,
|
13
|
+
describe Fortnox::API::Repository::Customer, integration: true, order: :defined do
|
14
14
|
include Helpers::Configuration
|
15
|
-
|
16
|
-
before { set_api_test_configuration }
|
15
|
+
include Helpers::Repositories
|
17
16
|
|
18
17
|
subject(:repository) { described_class.new }
|
19
18
|
|
19
|
+
before { set_api_test_configuration }
|
20
|
+
|
20
21
|
include_examples '.save', :name
|
21
22
|
|
22
23
|
include_examples '.save with specially named attribute',
|
@@ -24,11 +25,12 @@ describe Fortnox::API::Repository::Customer, order: :defined, integration: true
|
|
24
25
|
:email_invoice_cc,
|
25
26
|
'test@example.com'
|
26
27
|
|
27
|
-
# It is not yet possible to delete Customers. Therefore, expected nr of
|
28
|
+
# VCR: It is not yet possible to delete Customers. Therefore, expected nr of
|
28
29
|
# Customers when running .all will continue to increase
|
29
30
|
# (until 100, which is max by default).
|
30
|
-
include_examples '.all',
|
31
|
+
include_examples '.all', 7
|
31
32
|
|
33
|
+
# VCR: Searched Customers needs to be created manually
|
32
34
|
include_examples '.find', '1' do
|
33
35
|
let(:find_by_hash_failure) { { city: 'Not Found' } }
|
34
36
|
let(:single_param_find_by_hash) { { find_hash: { city: 'New York' }, matches: 2 } }
|
@@ -38,8 +40,8 @@ describe Fortnox::API::Repository::Customer, order: :defined, integration: true
|
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
|
-
# When recording new VCR casettes, expected matches must be increased
|
42
|
-
include_examples '.search', :name, 'Test',
|
43
|
+
# NOTE: When recording new VCR casettes, expected matches must be increased
|
44
|
+
include_examples '.search', :name, 'Test', 2
|
43
45
|
|
44
46
|
describe 'country reference' do
|
45
47
|
describe 'with valid country code \'SE\'' do
|
@@ -67,4 +69,32 @@ describe Fortnox::API::Repository::Customer, order: :defined, integration: true
|
|
67
69
|
end
|
68
70
|
end
|
69
71
|
end
|
72
|
+
|
73
|
+
describe 'sales account' do
|
74
|
+
# VCR: The Sales Account needs to be created manually in Fortnox
|
75
|
+
context 'when saving a Customer with a Sales Account set' do
|
76
|
+
let(:customer) do
|
77
|
+
VCR.use_cassette("#{vcr_dir}/save_new_with_sales_account") do
|
78
|
+
repository.save(
|
79
|
+
described_class::MODEL.new(
|
80
|
+
name: 'Customer with Sales Account',
|
81
|
+
sales_account: '3001'
|
82
|
+
)
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when fetching that Customer' do
|
88
|
+
subject { fetched_customer.sales_account }
|
89
|
+
|
90
|
+
let(:fetched_customer) do
|
91
|
+
VCR.use_cassette("#{vcr_dir}/find_with_sales_account") do
|
92
|
+
repository.find(customer.customer_number)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it { is_expected.to eq('3001') }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
70
100
|
end
|
@@ -28,16 +28,13 @@ shared_examples_for '.find' do |searched_entity_id, find_by_hash: true|
|
|
28
28
|
end
|
29
29
|
|
30
30
|
context 'when not found' do
|
31
|
-
|
32
|
-
|
33
|
-
let(:not_found_id) { '123456789' }
|
34
|
-
let(:find_failure) do
|
35
|
-
when_performing do
|
36
|
-
VCR.use_cassette("#{vcr_dir}/find_failure") { repository.find(not_found_id) }
|
37
|
-
end
|
31
|
+
let(:find_with_non_existing_id) do
|
32
|
+
VCR.use_cassette("#{vcr_dir}/find_failure") { repository.find('123456789') }
|
38
33
|
end
|
39
34
|
|
40
|
-
|
35
|
+
specify do
|
36
|
+
expect { find_with_non_existing_id }.to raise_error(Fortnox::API::RemoteServerError, /Kan inte hitta /)
|
37
|
+
end
|
41
38
|
end
|
42
39
|
end
|
43
40
|
|
@@ -2,14 +2,8 @@
|
|
2
2
|
|
3
3
|
shared_examples_for '.only' do |matching_filter, expected_matches, missing_filter: nil|
|
4
4
|
describe '.only' do
|
5
|
-
def repository_only(repository, vcr_cassette, filter)
|
6
|
-
VCR.use_cassette("#{vcr_dir}/#{vcr_cassette}") do
|
7
|
-
repository.only(filter)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
5
|
shared_examples '.only response' do |vcr_cassette, expected_entries|
|
12
|
-
subject {
|
6
|
+
subject { VCR.use_cassette("#{vcr_dir}/#{vcr_cassette}") { repository.only(filter) } }
|
13
7
|
|
14
8
|
it { is_expected.to be_instance_of(Array) }
|
15
9
|
it { is_expected.to have(expected_entries).entries }
|
@@ -30,14 +24,11 @@ shared_examples_for '.only' do |matching_filter, expected_matches, missing_filte
|
|
30
24
|
end
|
31
25
|
|
32
26
|
context 'with invalid filter' do
|
33
|
-
|
34
|
-
|
35
|
-
repository_only(repository, 'filter_invalid', 'doesntexist')
|
36
|
-
end
|
27
|
+
let(:only_with_invalid_filter) do
|
28
|
+
VCR.use_cassette("#{vcr_dir}/filter_invalid") { repository.only('doesntexist') }
|
37
29
|
end
|
38
30
|
|
39
|
-
|
31
|
+
specify { expect { only_with_invalid_filter }.to raise_error(Fortnox::API::RemoteServerError, /ogiltigt filter/) }
|
40
32
|
end
|
41
33
|
end
|
42
34
|
end
|
43
|
-
# rubocop:enable RSpec/DescribeClass
|