fortnox-api 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/.env.template +7 -0
  3. data/.env.test +11 -3
  4. data/.gitignore +7 -1
  5. data/.rubocop.yml +17 -1
  6. data/.travis.yml +10 -9
  7. data/CHANGELOG.md +22 -8
  8. data/CONTRIBUTE.md +21 -9
  9. data/DEVELOPER_README.md +72 -0
  10. data/Guardfile +13 -4
  11. data/README.md +226 -64
  12. data/Rakefile +128 -0
  13. data/bin/get_tokens +79 -0
  14. data/bin/renew_tokens +28 -0
  15. data/fortnox-api.gemspec +10 -9
  16. data/lib/fortnox/api/mappers/base/from_json.rb +4 -3
  17. data/lib/fortnox/api/mappers/base/to_json.rb +2 -3
  18. data/lib/fortnox/api/models/base.rb +12 -10
  19. data/lib/fortnox/api/models/customer.rb +55 -55
  20. data/lib/fortnox/api/models/label.rb +2 -2
  21. data/lib/fortnox/api/repositories/authentication.rb +61 -0
  22. data/lib/fortnox/api/repositories/base/savers.rb +3 -1
  23. data/lib/fortnox/api/repositories/base.rb +21 -35
  24. data/lib/fortnox/api/repositories.rb +1 -0
  25. data/lib/fortnox/api/request_handling.rb +30 -18
  26. data/lib/fortnox/api/types/document_row.rb +3 -3
  27. data/lib/fortnox/api/types/enums.rb +27 -11
  28. data/lib/fortnox/api/types/model.rb +1 -4
  29. data/lib/fortnox/api/types/sized.rb +2 -2
  30. data/lib/fortnox/api/types.rb +14 -1
  31. data/lib/fortnox/api/version.rb +1 -1
  32. data/lib/fortnox/api.rb +12 -32
  33. data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +4 -4
  34. data/spec/fortnox/api/mappers/base/from_json_spec.rb +10 -12
  35. data/spec/fortnox/api/mappers/base/to_json_spec.rb +48 -57
  36. data/spec/fortnox/api/mappers/base_spec.rb +4 -7
  37. data/spec/fortnox/api/mappers/contexts/json_conversion.rb +38 -33
  38. data/spec/fortnox/api/mappers/unit_spec.rb +3 -4
  39. data/spec/fortnox/api/models/base_spec.rb +27 -16
  40. data/spec/fortnox/api/models/unit_spec.rb +5 -3
  41. data/spec/fortnox/api/repositories/article_spec.rb +14 -9
  42. data/spec/fortnox/api/repositories/authentication_spec.rb +103 -0
  43. data/spec/fortnox/api/repositories/base_spec.rb +106 -319
  44. data/spec/fortnox/api/repositories/customer_spec.rb +37 -7
  45. data/spec/fortnox/api/repositories/examples/all.rb +0 -1
  46. data/spec/fortnox/api/repositories/examples/find.rb +5 -8
  47. data/spec/fortnox/api/repositories/examples/only.rb +4 -13
  48. data/spec/fortnox/api/repositories/examples/save.rb +32 -18
  49. data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -5
  50. data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +1 -4
  51. data/spec/fortnox/api/repositories/examples/search.rb +4 -7
  52. data/spec/fortnox/api/repositories/invoice_spec.rb +64 -15
  53. data/spec/fortnox/api/repositories/order_spec.rb +11 -9
  54. data/spec/fortnox/api/repositories/project_spec.rb +7 -6
  55. data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +9 -7
  56. data/spec/fortnox/api/repositories/unit_spec.rb +13 -11
  57. data/spec/fortnox/api/types/country_spec.rb +1 -1
  58. data/spec/fortnox/api/types/email_spec.rb +2 -2
  59. data/spec/fortnox/api/types/examples/document_row.rb +3 -3
  60. data/spec/fortnox/api/types/examples/enum.rb +4 -4
  61. data/spec/fortnox/api/types/examples/types.rb +1 -3
  62. data/spec/fortnox/api/types/housework_types_spec.rb +54 -61
  63. data/spec/fortnox/api/types/model_spec.rb +3 -27
  64. data/spec/fortnox/api/types/order_row_spec.rb +2 -2
  65. data/spec/fortnox/api/types/required_spec.rb +6 -11
  66. data/spec/fortnox/api/types/sales_account_spec.rb +57 -0
  67. data/spec/fortnox/api_spec.rb +19 -124
  68. data/spec/spec_helper.rb +0 -14
  69. data/spec/support/helpers/configuration_helper.rb +30 -3
  70. data/spec/support/helpers.rb +1 -1
  71. data/spec/support/matchers/type/attribute_matcher.rb +2 -2
  72. data/spec/support/matchers/type/have_nullable_date_matcher.rb +6 -4
  73. data/spec/support/matchers/type/have_nullable_matcher.rb +1 -1
  74. data/spec/support/matchers/type/have_nullable_string_matcher.rb +5 -5
  75. data/spec/support/matchers/type/require_attribute_matcher.rb +5 -5
  76. data/spec/support/matchers/type/type_matcher.rb +1 -1
  77. data/spec/support/vcr_setup.rb +16 -0
  78. data/spec/vcr_cassettes/articles/all.yml +16 -43
  79. data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +10 -12
  80. data/spec/vcr_cassettes/articles/find_failure.yml +10 -12
  81. data/spec/vcr_cassettes/articles/find_id_1.yml +13 -14
  82. data/spec/vcr_cassettes/articles/find_new.yml +14 -16
  83. data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +13 -15
  84. data/spec/vcr_cassettes/articles/save_new.yml +13 -15
  85. data/spec/vcr_cassettes/articles/save_old.yml +14 -16
  86. data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +13 -15
  87. data/spec/vcr_cassettes/articles/search_by_name.yml +16 -15
  88. data/spec/vcr_cassettes/articles/search_miss.yml +10 -12
  89. data/spec/vcr_cassettes/articles/search_with_special_char.yml +10 -12
  90. data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +13 -27
  91. data/spec/vcr_cassettes/authentication/expired_token.yml +54 -0
  92. data/spec/vcr_cassettes/authentication/invalid_authorization.yml +57 -0
  93. data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +58 -0
  94. data/spec/vcr_cassettes/authentication/valid_request.yml +63 -0
  95. data/spec/vcr_cassettes/customers/all.yml +20 -127
  96. data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +10 -12
  97. data/spec/vcr_cassettes/customers/find_failure.yml +10 -12
  98. data/spec/vcr_cassettes/customers/find_id_1.yml +14 -15
  99. data/spec/vcr_cassettes/customers/find_new.yml +13 -15
  100. data/spec/vcr_cassettes/customers/find_with_sales_account.yml +63 -0
  101. data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +13 -15
  102. data/spec/vcr_cassettes/customers/save_new.yml +12 -14
  103. data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +12 -14
  104. data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +63 -0
  105. data/spec/vcr_cassettes/customers/save_old.yml +13 -15
  106. data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +12 -14
  107. data/spec/vcr_cassettes/customers/search_by_name.yml +13 -45
  108. data/spec/vcr_cassettes/customers/search_miss.yml +10 -12
  109. data/spec/vcr_cassettes/customers/search_with_special_char.yml +10 -12
  110. data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +14 -16
  111. data/spec/vcr_cassettes/invoices/all.yml +47 -112
  112. data/spec/vcr_cassettes/invoices/filter_hit.yml +14 -18
  113. data/spec/vcr_cassettes/invoices/filter_invalid.yml +10 -12
  114. data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +10 -12
  115. data/spec/vcr_cassettes/invoices/find_failure.yml +10 -12
  116. data/spec/vcr_cassettes/invoices/find_id_1.yml +15 -16
  117. data/spec/vcr_cassettes/invoices/find_new.yml +16 -18
  118. data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +13 -15
  119. data/spec/vcr_cassettes/invoices/row_description_limit.yml +65 -0
  120. data/spec/vcr_cassettes/invoices/save_new.yml +14 -16
  121. data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +14 -16
  122. data/spec/vcr_cassettes/invoices/save_new_with_country.yml +14 -15
  123. data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +15 -16
  124. data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +14 -15
  125. data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +14 -15
  126. data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +14 -15
  127. data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +15 -16
  128. data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +15 -16
  129. data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +14 -15
  130. data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +14 -15
  131. data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +65 -0
  132. data/spec/vcr_cassettes/invoices/save_old.yml +16 -18
  133. data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +16 -18
  134. data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +16 -17
  135. data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +16 -18
  136. data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +16 -17
  137. data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +15 -16
  138. data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +14 -15
  139. data/spec/vcr_cassettes/invoices/search_by_name.yml +13 -21
  140. data/spec/vcr_cassettes/invoices/search_miss.yml +10 -12
  141. data/spec/vcr_cassettes/invoices/search_with_special_char.yml +10 -12
  142. data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +14 -16
  143. data/spec/vcr_cassettes/orders/all.yml +19 -113
  144. data/spec/vcr_cassettes/orders/filter_hit.yml +14 -20
  145. data/spec/vcr_cassettes/orders/filter_invalid.yml +10 -12
  146. data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +10 -12
  147. data/spec/vcr_cassettes/orders/find_failure.yml +10 -12
  148. data/spec/vcr_cassettes/orders/find_id_1.yml +17 -17
  149. data/spec/vcr_cassettes/orders/find_new.yml +16 -18
  150. data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +11 -13
  151. data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +11 -13
  152. data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +15 -16
  153. data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +15 -16
  154. data/spec/vcr_cassettes/orders/housework_type_construction.yml +15 -16
  155. data/spec/vcr_cassettes/orders/housework_type_cooking.yml +11 -13
  156. data/spec/vcr_cassettes/orders/housework_type_electricity.yml +15 -16
  157. data/spec/vcr_cassettes/orders/housework_type_gardening.yml +15 -16
  158. data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +15 -16
  159. data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +15 -16
  160. data/spec/vcr_cassettes/orders/housework_type_hvac.yml +15 -16
  161. data/spec/vcr_cassettes/orders/housework_type_itservices.yml +15 -16
  162. data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +15 -16
  163. data/spec/vcr_cassettes/orders/housework_type_masonry.yml +15 -16
  164. data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +15 -16
  165. data/spec/vcr_cassettes/orders/housework_type_othercare.yml +15 -16
  166. data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +15 -16
  167. data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +15 -16
  168. data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +15 -16
  169. data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +15 -16
  170. data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +11 -13
  171. data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +13 -15
  172. data/spec/vcr_cassettes/orders/save_new.yml +16 -18
  173. data/spec/vcr_cassettes/orders/save_old.yml +16 -18
  174. data/spec/vcr_cassettes/orders/save_with_nested_model.yml +15 -16
  175. data/spec/vcr_cassettes/orders/search_by_name.yml +13 -17
  176. data/spec/vcr_cassettes/orders/search_miss.yml +10 -12
  177. data/spec/vcr_cassettes/orders/search_with_special_char.yml +10 -12
  178. data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +14 -16
  179. data/spec/vcr_cassettes/projects/all.yml +14 -37
  180. data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +10 -12
  181. data/spec/vcr_cassettes/projects/find_failure.yml +10 -12
  182. data/spec/vcr_cassettes/projects/find_id_1.yml +13 -15
  183. data/spec/vcr_cassettes/projects/find_new.yml +14 -16
  184. data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +15 -16
  185. data/spec/vcr_cassettes/projects/save_new.yml +13 -15
  186. data/spec/vcr_cassettes/projects/save_old.yml +14 -16
  187. data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +12 -14
  188. data/spec/vcr_cassettes/termsofpayments/all.yml +16 -23
  189. data/spec/vcr_cassettes/termsofpayments/find_failure.yml +10 -12
  190. data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +13 -16
  191. data/spec/vcr_cassettes/termsofpayments/find_new.yml +12 -14
  192. data/spec/vcr_cassettes/termsofpayments/save_new.yml +12 -14
  193. data/spec/vcr_cassettes/termsofpayments/save_old.yml +12 -14
  194. data/spec/vcr_cassettes/units/all.yml +13 -24
  195. data/spec/vcr_cassettes/units/find_failure.yml +10 -12
  196. data/spec/vcr_cassettes/units/find_id_1.yml +13 -15
  197. data/spec/vcr_cassettes/units/find_new.yml +12 -14
  198. data/spec/vcr_cassettes/units/save_new.yml +12 -14
  199. data/spec/vcr_cassettes/units/save_old.yml +12 -14
  200. data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +12 -14
  201. metadata +39 -230
  202. data/lib/fortnox/api/circular_queue.rb +0 -39
  203. data/spec/fortnox/api/circular_queue_spec.rb +0 -52
  204. data/spec/support/helpers/when_performing_helper.rb +0 -7
  205. 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('Model::RepositoryBaseTest', Class.new)
9
- stub_const('Repository::Test', Class.new(described_class))
10
- stub_const('Repository::Test::MODEL', Model::RepositoryBaseTest)
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.register(:repositorybasetest, Model::RepositoryBaseTest)
13
- end
16
+ Fortnox::API::Registry.enable_stubs!
14
17
 
15
- after do
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(:access_token) { '3f08d038-f380-4893-94a0-a08f6e60e67a' }
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
- context 'without client secret' do
60
- include_examples 'missing configuration' do
61
- before do
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
- let(:message) { 'have to provide your client secret' }
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
- describe 'first token store' do
133
- let(:repository) { Repository::Test.new(token_store: :store1) }
32
+ context 'with expired access token' do
33
+ before { Fortnox::API.access_token = ENV.fetch('EXPIRED_ACCESS_TOKEN') }
134
34
 
135
- it { is_expected.to eql access_token }
136
- end
137
-
138
- describe 'second token store' do
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
- Fortnox::API.configure do |conf|
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
- describe 'making requests with multiple access tokens' do
60
+ context 'when receiving an error from remote server' do
287
61
  before do
288
- Fortnox::API.configure do |conf|
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
- :get,
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: 200,
305
- body: '1'
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
- stub_request(
309
- :get,
310
- 'https://api.fortnox.se/3/test'
311
- ).with(
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 subsequent requests on same object' do
325
- let!(:response1) { repository.get('/test', body: '') }
326
- let!(:response2) { repository.get('/test', body: '') }
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
- # rubocop:disable RSpec/MultipleExpectations
330
- # All these checks must be run in same it-statement because
331
- # of the random starting index.
332
- it 'works' do
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 raising error from remote server' do
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
- Fortnox::API.configure do |conf|
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
- :post,
353
- 'https://api.fortnox.se/3/test'
102
+ :get,
103
+ 'https://api.fortnox.se/3nonsense'
354
104
  ).to_return(
355
- status: 500,
356
- body: {
357
- 'ErrorInformation' => {
358
- 'error' => 1,
359
- 'message' => error_message
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
- subject { -> { repository.post('/test', body: '') } }
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
- it { is_expected.to raise_error(Fortnox::API::RemoteServerError) }
369
- it { is_expected.to raise_error(error_message) }
122
+ describe 'update existing' do
123
+ context 'with no change' do
124
+ before do
125
+ set_api_test_configuration
370
126
 
371
- context 'with debugging enabled' do
372
- around do |example|
373
- Fortnox::API.config.debugging = true
374
- example.run
375
- Fortnox::API.config.debugging = false
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 { is_expected.to raise_error(/\<HTTParty\:\:Request\:0x/) }
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, order: :defined, integration: true do
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', 100
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', 33
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
@@ -15,4 +15,3 @@ RSpec.shared_examples_for '.all' do |count|
15
15
  end
16
16
  end
17
17
  end
18
- # rubocop:enable RSpec/DescribeClass
@@ -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
- subject { find_failure }
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
- it { is_expected.to raise_error(Fortnox::API::RemoteServerError, /Kan inte hitta /) }
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 { repository_only(repository, vcr_cassette, filter) }
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
- subject do
34
- when_performing do
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
- it { is_expected.to raise_error(Fortnox::API::RemoteServerError, /ogiltigt filter/) }
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