fortnox-api 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.env.template +7 -0
  4. data/.env.test +11 -3
  5. data/.gitignore +7 -1
  6. data/.rubocop.yml +18 -2
  7. data/.tool-versions +1 -0
  8. data/.travis.yml +15 -12
  9. data/CHANGELOG.md +58 -3
  10. data/CONTRIBUTE.md +21 -9
  11. data/DEVELOPER_README.md +72 -0
  12. data/Guardfile +13 -4
  13. data/README.md +226 -64
  14. data/Rakefile +128 -0
  15. data/bin/get_tokens +79 -0
  16. data/bin/renew_tokens +28 -0
  17. data/fortnox-api.gemspec +31 -25
  18. data/lib/fortnox/api/mappers/article.rb +1 -1
  19. data/lib/fortnox/api/mappers/base/from_json.rb +5 -4
  20. data/lib/fortnox/api/mappers/base/to_json.rb +4 -5
  21. data/lib/fortnox/api/mappers/base.rb +3 -3
  22. data/lib/fortnox/api/mappers/customer.rb +1 -1
  23. data/lib/fortnox/api/mappers/default_delivery_types.rb +1 -1
  24. data/lib/fortnox/api/mappers/default_templates.rb +1 -1
  25. data/lib/fortnox/api/mappers/edi_information.rb +1 -1
  26. data/lib/fortnox/api/mappers/email_information.rb +1 -1
  27. data/lib/fortnox/api/mappers/invoice.rb +4 -4
  28. data/lib/fortnox/api/mappers/invoice_row.rb +1 -1
  29. data/lib/fortnox/api/mappers/order.rb +4 -4
  30. data/lib/fortnox/api/mappers/order_row.rb +1 -1
  31. data/lib/fortnox/api/mappers/project.rb +1 -1
  32. data/lib/fortnox/api/mappers/terms_of_payment.rb +1 -1
  33. data/lib/fortnox/api/mappers/unit.rb +1 -1
  34. data/lib/fortnox/api/mappers/value/country_string.rb +1 -1
  35. data/lib/fortnox/api/mappers.rb +18 -18
  36. data/lib/fortnox/api/models/article.rb +2 -2
  37. data/lib/fortnox/api/models/base.rb +23 -21
  38. data/lib/fortnox/api/models/customer.rb +57 -57
  39. data/lib/fortnox/api/models/document.rb +2 -2
  40. data/lib/fortnox/api/models/invoice.rb +2 -2
  41. data/lib/fortnox/api/models/label.rb +3 -3
  42. data/lib/fortnox/api/models/order.rb +2 -2
  43. data/lib/fortnox/api/models/project.rb +2 -2
  44. data/lib/fortnox/api/models/terms_of_payment.rb +2 -2
  45. data/lib/fortnox/api/models/unit.rb +2 -2
  46. data/lib/fortnox/api/models.rb +7 -7
  47. data/lib/fortnox/api/repositories/article.rb +3 -3
  48. data/lib/fortnox/api/repositories/authentication.rb +61 -0
  49. data/lib/fortnox/api/repositories/base/savers.rb +3 -1
  50. data/lib/fortnox/api/repositories/base.rb +25 -38
  51. data/lib/fortnox/api/repositories/customer.rb +3 -3
  52. data/lib/fortnox/api/repositories/invoice.rb +3 -3
  53. data/lib/fortnox/api/repositories/order.rb +3 -3
  54. data/lib/fortnox/api/repositories/project.rb +3 -3
  55. data/lib/fortnox/api/repositories/terms_of_payment.rb +3 -3
  56. data/lib/fortnox/api/repositories/unit.rb +3 -3
  57. data/lib/fortnox/api/repositories.rb +8 -7
  58. data/lib/fortnox/api/request_handling.rb +30 -18
  59. data/lib/fortnox/api/types/default_delivery_types.rb +0 -2
  60. data/lib/fortnox/api/types/default_templates.rb +0 -2
  61. data/lib/fortnox/api/types/document_row.rb +3 -3
  62. data/lib/fortnox/api/types/edi_information.rb +0 -2
  63. data/lib/fortnox/api/types/email_information.rb +0 -2
  64. data/lib/fortnox/api/types/enums.rb +27 -11
  65. data/lib/fortnox/api/types/invoice_row.rb +1 -1
  66. data/lib/fortnox/api/types/model.rb +5 -9
  67. data/lib/fortnox/api/types/nullable.rb +13 -9
  68. data/lib/fortnox/api/types/order_row.rb +1 -1
  69. data/lib/fortnox/api/types/required.rb +3 -3
  70. data/lib/fortnox/api/types/sized.rb +4 -4
  71. data/lib/fortnox/api/types.rb +31 -23
  72. data/lib/fortnox/api/version.rb +1 -1
  73. data/lib/fortnox/api.rb +21 -39
  74. data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +13 -11
  75. data/spec/fortnox/api/mappers/base/from_json_spec.rb +10 -12
  76. data/spec/fortnox/api/mappers/base/to_json_spec.rb +48 -57
  77. data/spec/fortnox/api/mappers/base_spec.rb +4 -7
  78. data/spec/fortnox/api/mappers/contexts/json_conversion.rb +38 -33
  79. data/spec/fortnox/api/mappers/default_delivery_types_spec.rb +1 -1
  80. data/spec/fortnox/api/mappers/examples/mapper.rb +1 -1
  81. data/spec/fortnox/api/mappers/unit_spec.rb +3 -4
  82. data/spec/fortnox/api/models/base_spec.rb +33 -22
  83. data/spec/fortnox/api/models/unit_spec.rb +5 -3
  84. data/spec/fortnox/api/repositories/article_spec.rb +14 -9
  85. data/spec/fortnox/api/repositories/authentication_spec.rb +103 -0
  86. data/spec/fortnox/api/repositories/base_spec.rb +105 -326
  87. data/spec/fortnox/api/repositories/customer_spec.rb +37 -7
  88. data/spec/fortnox/api/repositories/examples/all.rb +0 -1
  89. data/spec/fortnox/api/repositories/examples/find.rb +5 -8
  90. data/spec/fortnox/api/repositories/examples/only.rb +4 -13
  91. data/spec/fortnox/api/repositories/examples/save.rb +32 -18
  92. data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -5
  93. data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +1 -4
  94. data/spec/fortnox/api/repositories/examples/search.rb +4 -7
  95. data/spec/fortnox/api/repositories/invoice_spec.rb +64 -21
  96. data/spec/fortnox/api/repositories/order_spec.rb +11 -9
  97. data/spec/fortnox/api/repositories/project_spec.rb +7 -6
  98. data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +9 -7
  99. data/spec/fortnox/api/repositories/unit_spec.rb +13 -11
  100. data/spec/fortnox/api/types/country_spec.rb +1 -1
  101. data/spec/fortnox/api/types/email_spec.rb +2 -2
  102. data/spec/fortnox/api/types/examples/document_row.rb +3 -3
  103. data/spec/fortnox/api/types/examples/enum.rb +4 -4
  104. data/spec/fortnox/api/types/examples/types.rb +1 -3
  105. data/spec/fortnox/api/types/housework_types_spec.rb +54 -90
  106. data/spec/fortnox/api/types/model_spec.rb +13 -23
  107. data/spec/fortnox/api/types/nullable_spec.rb +30 -10
  108. data/spec/fortnox/api/types/order_row_spec.rb +2 -2
  109. data/spec/fortnox/api/types/required_spec.rb +7 -15
  110. data/spec/fortnox/api/types/sales_account_spec.rb +57 -0
  111. data/spec/fortnox/api_spec.rb +19 -124
  112. data/spec/spec_helper.rb +0 -14
  113. data/spec/support/helpers/configuration_helper.rb +30 -3
  114. data/spec/support/helpers.rb +1 -1
  115. data/spec/support/matchers/type/attribute_matcher.rb +2 -2
  116. data/spec/support/matchers/type/enum_matcher.rb +1 -1
  117. data/spec/support/matchers/type/have_account_number_matcher.rb +1 -1
  118. data/spec/support/matchers/type/have_email_matcher.rb +1 -1
  119. data/spec/support/matchers/type/have_nullable_date_matcher.rb +7 -5
  120. data/spec/support/matchers/type/have_nullable_matcher.rb +1 -1
  121. data/spec/support/matchers/type/have_nullable_string_matcher.rb +5 -5
  122. data/spec/support/matchers/type/require_attribute_matcher.rb +5 -5
  123. data/spec/support/matchers/type/type_matcher.rb +1 -1
  124. data/spec/support/vcr_setup.rb +16 -0
  125. data/spec/vcr_cassettes/articles/all.yml +32 -49
  126. data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +27 -23
  127. data/spec/vcr_cassettes/articles/find_failure.yml +27 -23
  128. data/spec/vcr_cassettes/articles/find_id_1.yml +29 -24
  129. data/spec/vcr_cassettes/articles/find_new.yml +30 -26
  130. data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +29 -25
  131. data/spec/vcr_cassettes/articles/save_new.yml +29 -25
  132. data/spec/vcr_cassettes/articles/save_old.yml +30 -26
  133. data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +29 -25
  134. data/spec/vcr_cassettes/articles/search_by_name.yml +32 -25
  135. data/spec/vcr_cassettes/articles/search_miss.yml +27 -23
  136. data/spec/vcr_cassettes/articles/search_with_special_char.yml +27 -23
  137. data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +29 -36
  138. data/spec/vcr_cassettes/authentication/expired_token.yml +54 -0
  139. data/spec/vcr_cassettes/authentication/invalid_authorization.yml +57 -0
  140. data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +58 -0
  141. data/spec/vcr_cassettes/authentication/valid_request.yml +63 -0
  142. data/spec/vcr_cassettes/customers/all.yml +35 -136
  143. data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +27 -23
  144. data/spec/vcr_cassettes/customers/find_failure.yml +27 -23
  145. data/spec/vcr_cassettes/customers/find_id_1.yml +30 -25
  146. data/spec/vcr_cassettes/customers/find_new.yml +29 -25
  147. data/spec/vcr_cassettes/customers/find_with_sales_account.yml +63 -0
  148. data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +29 -25
  149. data/spec/vcr_cassettes/customers/save_new.yml +28 -24
  150. data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +28 -24
  151. data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +63 -0
  152. data/spec/vcr_cassettes/customers/save_old.yml +29 -25
  153. data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +28 -24
  154. data/spec/vcr_cassettes/customers/search_by_name.yml +29 -53
  155. data/spec/vcr_cassettes/customers/search_miss.yml +27 -23
  156. data/spec/vcr_cassettes/customers/search_with_special_char.yml +27 -23
  157. data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +30 -26
  158. data/spec/vcr_cassettes/invoices/all.yml +62 -121
  159. data/spec/vcr_cassettes/invoices/filter_hit.yml +30 -28
  160. data/spec/vcr_cassettes/invoices/filter_invalid.yml +27 -23
  161. data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +27 -23
  162. data/spec/vcr_cassettes/invoices/find_failure.yml +27 -23
  163. data/spec/vcr_cassettes/invoices/find_id_1.yml +31 -26
  164. data/spec/vcr_cassettes/invoices/find_new.yml +32 -28
  165. data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +29 -25
  166. data/spec/vcr_cassettes/invoices/row_description_limit.yml +65 -0
  167. data/spec/vcr_cassettes/invoices/save_new.yml +31 -27
  168. data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +31 -27
  169. data/spec/vcr_cassettes/invoices/save_new_with_country.yml +31 -26
  170. data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +32 -27
  171. data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +31 -26
  172. data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +31 -26
  173. data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +31 -26
  174. data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +32 -27
  175. data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +32 -27
  176. data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +31 -26
  177. data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +31 -26
  178. data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +65 -0
  179. data/spec/vcr_cassettes/invoices/save_old.yml +32 -28
  180. data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +32 -28
  181. data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +32 -27
  182. data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +32 -28
  183. data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +32 -27
  184. data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +32 -27
  185. data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +31 -26
  186. data/spec/vcr_cassettes/invoices/search_by_name.yml +29 -31
  187. data/spec/vcr_cassettes/invoices/search_miss.yml +27 -23
  188. data/spec/vcr_cassettes/invoices/search_with_special_char.yml +27 -23
  189. data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +30 -26
  190. data/spec/vcr_cassettes/orders/all.yml +35 -123
  191. data/spec/vcr_cassettes/orders/filter_hit.yml +30 -30
  192. data/spec/vcr_cassettes/orders/filter_invalid.yml +27 -23
  193. data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +27 -23
  194. data/spec/vcr_cassettes/orders/find_failure.yml +27 -23
  195. data/spec/vcr_cassettes/orders/find_id_1.yml +33 -27
  196. data/spec/vcr_cassettes/orders/find_new.yml +32 -28
  197. data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +28 -24
  198. data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +28 -24
  199. data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +32 -27
  200. data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +32 -27
  201. data/spec/vcr_cassettes/orders/housework_type_construction.yml +32 -27
  202. data/spec/vcr_cassettes/orders/housework_type_cooking.yml +28 -24
  203. data/spec/vcr_cassettes/orders/housework_type_electricity.yml +32 -27
  204. data/spec/vcr_cassettes/orders/housework_type_gardening.yml +32 -27
  205. data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +32 -27
  206. data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +32 -27
  207. data/spec/vcr_cassettes/orders/housework_type_hvac.yml +32 -27
  208. data/spec/vcr_cassettes/orders/housework_type_itservices.yml +32 -27
  209. data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +32 -27
  210. data/spec/vcr_cassettes/orders/housework_type_masonry.yml +32 -27
  211. data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +32 -27
  212. data/spec/vcr_cassettes/orders/housework_type_othercare.yml +32 -27
  213. data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +32 -27
  214. data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +32 -27
  215. data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +32 -27
  216. data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +32 -27
  217. data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +28 -24
  218. data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +29 -25
  219. data/spec/vcr_cassettes/orders/save_new.yml +32 -28
  220. data/spec/vcr_cassettes/orders/save_old.yml +32 -28
  221. data/spec/vcr_cassettes/orders/save_with_nested_model.yml +32 -27
  222. data/spec/vcr_cassettes/orders/search_by_name.yml +29 -27
  223. data/spec/vcr_cassettes/orders/search_miss.yml +27 -23
  224. data/spec/vcr_cassettes/orders/search_with_special_char.yml +27 -23
  225. data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +30 -26
  226. data/spec/vcr_cassettes/projects/all.yml +30 -43
  227. data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +27 -23
  228. data/spec/vcr_cassettes/projects/find_failure.yml +27 -23
  229. data/spec/vcr_cassettes/projects/find_id_1.yml +29 -25
  230. data/spec/vcr_cassettes/projects/find_new.yml +30 -26
  231. data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +31 -26
  232. data/spec/vcr_cassettes/projects/save_new.yml +29 -25
  233. data/spec/vcr_cassettes/projects/save_old.yml +30 -26
  234. data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +29 -25
  235. data/spec/vcr_cassettes/termsofpayments/all.yml +32 -32
  236. data/spec/vcr_cassettes/termsofpayments/find_failure.yml +27 -23
  237. data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +29 -26
  238. data/spec/vcr_cassettes/termsofpayments/find_new.yml +29 -25
  239. data/spec/vcr_cassettes/termsofpayments/save_new.yml +29 -25
  240. data/spec/vcr_cassettes/termsofpayments/save_old.yml +29 -25
  241. data/spec/vcr_cassettes/units/all.yml +29 -32
  242. data/spec/vcr_cassettes/units/find_failure.yml +27 -23
  243. data/spec/vcr_cassettes/units/find_id_1.yml +29 -25
  244. data/spec/vcr_cassettes/units/find_new.yml +29 -25
  245. data/spec/vcr_cassettes/units/save_new.yml +29 -25
  246. data/spec/vcr_cassettes/units/save_old.yml +29 -25
  247. data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +29 -25
  248. metadata +130 -261
  249. data/lib/fortnox/api/circular_queue.rb +0 -39
  250. data/spec/fortnox/api/circular_queue_spec.rb +0 -52
  251. data/spec/support/helpers/dummy_class_helper.rb +0 -38
  252. data/spec/support/helpers/when_performing_helper.rb +0 -7
  253. data/spec/vcr_cassettes/invoices/save_new_with_country_KR.yml +0 -61
  254. data/spec/vcr_cassettes/orders/housework_without_tax_reduction_type.yml +0 -57
  255. data/temp.txt +0 -1
@@ -2,388 +2,167 @@
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'fortnox/api'
5
+ require 'jwt'
6
+ require 'dry/container/stub'
5
7
 
6
- describe Fortnox::API::Repository::Base do
7
- using_test_class do
8
- module Model
9
- class RepositoryBaseTest
10
- end
11
- end
12
- module Repository
13
- class Test < Fortnox::API::Repository::Base
14
- MODEL = Model::RepositoryBaseTest
15
- end
16
- end
17
- end
8
+ describe Fortnox::API::Repository::Base, integration: true do
9
+ include Helpers::Configuration
18
10
 
19
11
  before do
20
- Fortnox::API::Registry.register(:repositorybasetest, Model::RepositoryBaseTest)
21
- end
22
-
23
- after do
24
- # HACK: Currently, there is no way to remove keys from the Dry::Container#register.
25
- # We could move the register call to a before(:all) hook, but that registered key
26
- # would then leak into other tests. Instead, we can simply delete it with this little hack :)
27
- Fortnox::API::Registry._container.delete('repositorybasetest')
28
- end
29
-
30
- let(:access_token) { '3f08d038-f380-4893-94a0-a08f6e60e67a' }
31
- let(:access_token2) { '89feajou-sif8-8f8u-29ja-xdfniokeniod' }
32
- let(:client_secret) { 'P5K5vE3Kun' }
33
- let(:repository) { Repository::Test.new }
34
- let(:application_json) {}
35
- let(:headers) do
36
- {
37
- 'Access-Token' => access_token,
38
- 'Client-Secret' => client_secret,
39
- 'Content-Type' => 'application/json',
40
- 'Accept' => 'application/json'
41
- }
42
- end
43
-
44
- describe 'creation' do
45
- shared_examples_for 'missing configuration' do
46
- subject { -> { Repository::Test.new(token_store: :store1).get('nonsense') } }
47
-
48
- let(:error) { Fortnox::API::MissingConfiguration }
49
-
50
- it { is_expected.to raise_error(error, /#{message}/) }
51
- end
52
-
53
- context 'without base url' do
54
- include_examples 'missing configuration' do
55
- before do
56
- Fortnox::API.configure do |config|
57
- config.base_url = nil
58
- config.client_secret = client_secret
59
- config.access_tokens = { store1: access_token }
60
- end
61
- end
62
-
63
- let(:message) { 'have to provide a base url' }
64
- end
65
- end
12
+ stub_const('TestModel', Class.new)
13
+ stub_const('TestRepository', Class.new(described_class))
14
+ stub_const('TestRepository::MODEL', TestModel)
66
15
 
67
- context 'without client secret' do
68
- include_examples 'missing configuration' do
69
- before do
70
- Fortnox::API.configure do |config|
71
- config.client_secret = nil
72
- config.access_tokens = { store1: access_token }
73
- end
74
- end
16
+ Fortnox::API::Registry.enable_stubs!
75
17
 
76
- let(:message) { 'have to provide your client secret' }
77
- end
78
- end
18
+ add_to_registry(:testmodel, TestModel)
79
19
  end
80
20
 
81
- describe '#next_access_token' do
82
- before { Fortnox::API.configure { |conf| conf.client_secret = client_secret } }
83
-
84
- context 'with default token store' do
85
- context 'with one access token' do
86
- subject { repository.next_access_token }
87
-
88
- before { Fortnox::API.configure { |conf| conf.access_token = access_token } }
89
-
90
- it { is_expected.to eql(access_token) }
91
- end
92
-
93
- context 'with one access token and two subsequent requests' do
94
- subject { repository.next_access_token }
21
+ let(:repository) { TestRepository.new }
95
22
 
96
- before do
97
- Fortnox::API.configure { |conf| conf.access_token = access_token }
98
- repository.next_access_token
99
- end
23
+ describe '#initialize' do
24
+ context 'without providing an access token' do
25
+ before { Fortnox::API.access_token = nil }
100
26
 
101
- it 'still uses the same token' do
102
- is_expected.to eql(access_token)
103
- end
27
+ it 'raises an error' do
28
+ expect { TestRepository.new }.to raise_error(Fortnox::API::MissingAccessToken)
104
29
  end
105
-
106
- context 'with multiple access tokens' do
107
- subject { access_tokens }
108
-
109
- before { Fortnox::API.configure { |conf| conf.access_tokens = access_tokens } }
110
- let(:access_tokens) { [access_token, access_token2] }
111
-
112
- it { is_expected.to include(repository.next_access_token) }
113
-
114
- it 'changes token on next request' do
115
- token1 = repository.next_access_token
116
- token2 = repository.next_access_token
117
-
118
- expect(token1).not_to eql(token2)
119
- end
120
-
121
- it 'circulates tokens' do
122
- token1 = repository.next_access_token
123
- repository.next_access_token
124
- token3 = repository.next_access_token
125
-
126
- expect(token1).to eql(token3)
127
- end
128
- end
129
- end
130
-
131
- context 'with multiple stores' do
132
- subject { repository.next_access_token }
133
-
134
- before do
135
- Fortnox::API.configure do |config|
136
- config.access_tokens = { store1: access_token, store2: access_token2 }
137
- end
138
- end
139
-
140
- describe 'first token store' do
141
- let(:repository) { Repository::Test.new(token_store: :store1) }
142
-
143
- it { is_expected.to eql access_token }
144
- end
145
-
146
- describe 'second token store' do
147
- let(:repository) { Repository::Test.new(token_store: :store2) }
148
-
149
- it { is_expected.to eql access_token2 }
150
- end
151
- end
152
- end
153
-
154
- describe '#access_tokens' do
155
- subject(:access_tokens) { repository.access_tokens }
156
-
157
- before { Fortnox::API.configure { |conf| conf.client_secret = client_secret } }
158
-
159
- let(:token_store_not_present) do
160
- "There is no token store named #{token_store.inspect}. Available stores are #{available_stores}."
161
- end
162
-
163
- let(:error) { Fortnox::API::MissingConfiguration }
164
-
165
- context 'with non existing token store' do
166
- subject { -> { access_tokens } }
167
-
168
- before do
169
- Fortnox::API.configure { |conf| conf.access_tokens = { some_store: [access_token] } }
170
- end
171
-
172
- let(:repository) { Repository::Test.new(token_store: token_store) }
173
- let(:token_store) { :non_existing_store }
174
- let(:available_stores) { [:some_store] }
175
-
176
- it { is_expected.to raise_error(error, token_store_not_present) }
177
- end
178
-
179
- context 'with no tokens set' do
180
- subject { -> { access_tokens } }
181
-
182
- before { Fortnox::API.configure { |conf| conf.access_tokens = {} } }
183
- let(:token_store) { :default }
184
- let(:available_stores) { [] }
185
-
186
- it { is_expected.to raise_error(error, token_store_not_present) }
187
- end
188
-
189
- context 'with one access token in token store' do
190
- before { Fortnox::API.configure { |conf| conf.access_token = access_token } }
191
- let(:token_store) { :default }
192
-
193
- it { is_expected.to eql(access_token) }
194
- end
195
-
196
- context 'with multiple access tokens' do
197
- before do
198
- Fortnox::API.configure { |conf| conf.access_tokens = [access_token, access_token2] }
199
- end
200
- let(:token_store) { :default }
201
-
202
- it { is_expected.to eql([access_token, access_token2]) }
203
30
  end
204
31
 
205
- context 'with multiple token stores' do
206
- before do
207
- Fortnox::API.configure do |conf|
208
- conf.access_tokens = { store_a: store_a_tokens, store_b: store_b_token }
209
- end
210
- end
211
- let(:store_a_tokens) { %w[token_a1 token_a2] }
212
- let(:store_b_token) { 'token_b1' }
213
-
214
- context 'with valid store name' do
215
- let(:repository) { Repository::Test.new(token_store: :store_a) }
216
-
217
- it { is_expected.to eql(store_a_tokens) }
218
- end
219
-
220
- context 'with non collection' do
221
- let(:repository) { Repository::Test.new(token_store: :store_b) }
222
-
223
- it { is_expected.to eql(store_b_token) }
224
- end
225
-
226
- context 'with invalid store name' do
227
- subject { -> { access_tokens } }
32
+ context 'with expired access token' do
33
+ before { Fortnox::API.access_token = ENV.fetch('EXPIRED_ACCESS_TOKEN') }
228
34
 
229
- let(:repository) { Repository::Test.new(token_store: :nonsence_store) }
230
-
231
- it { is_expected.to raise_error(error) }
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"/)
232
39
  end
233
40
  end
234
41
  end
235
42
 
236
- describe '#check_access_tokens!' do
237
- subject { -> { repository.check_access_tokens!(tokens) } }
238
-
239
- before { Fortnox::API.configure { |conf| conf.client_secret = client_secret } }
240
- let(:error) { Fortnox::API::MissingConfiguration }
241
- let(:message) { "not provided any access tokens in token store #{token_store.inspect}" }
242
- let(:token_store) { :default }
243
-
244
- context 'with nil' do
245
- let(:tokens) { nil }
246
-
247
- it { is_expected.to raise_error(error, /#{message}/) }
248
- end
249
-
250
- context 'with empty array' do
251
- let(:tokens) { [] }
252
-
253
- it { is_expected.to raise_error(error, /#{message}/) }
254
- end
255
-
256
- context 'with an empty, non default, token store' do
257
- before { Fortnox::API.configure { |conf| conf.access_tokens = { token_store => tokens } } }
258
- let(:repository) { Repository::Test.new(token_store: token_store) }
259
- let(:tokens) { [] }
260
- let(:token_store) { :store1 }
261
-
262
- it { is_expected.to raise_error(error, /#{message}/) }
263
- end
264
-
265
- context 'with valid tokens' do
266
- let(:tokens) { %w[12345 abcde] }
267
-
268
- it { is_expected.not_to raise_error }
269
- end
270
- end
271
-
272
43
  context 'when making a request including the proper headers' do
44
+ subject { repository.get('/test', body: '') }
45
+
273
46
  before do
274
- Fortnox::API.configure do |conf|
275
- conf.client_secret = client_secret
276
- conf.access_token = access_token
277
- end
47
+ set_api_test_configuration
278
48
 
279
49
  stub_request(
280
50
  :get,
281
51
  'https://api.fortnox.se/3/test'
282
- ).with(
283
- headers: headers
284
52
  ).to_return(
285
53
  status: 200
286
54
  )
287
55
  end
288
56
 
289
- subject { repository.get('/test', body: '') }
290
-
291
57
  it { is_expected.to be_nil }
292
58
  end
293
59
 
294
- describe 'making requests with multiple access tokens' do
60
+ context 'when receiving an error from remote server' do
295
61
  before do
296
- Fortnox::API.configure do |conf|
297
- conf.client_secret = client_secret
298
- conf.access_tokens = [access_token, access_token2]
299
- end
62
+ set_api_test_configuration
300
63
 
301
64
  stub_request(
302
- :get,
65
+ :post,
303
66
  'https://api.fortnox.se/3/test'
304
- ).with(
305
- headers: {
306
- 'Access-Token' => access_token,
307
- 'Client-Secret' => client_secret,
308
- 'Content-Type' => 'application/json',
309
- 'Accept' => 'application/json'
310
- }
311
67
  ).to_return(
312
- status: 200,
313
- 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' }
314
76
  )
77
+ end
315
78
 
316
- stub_request(
317
- :get,
318
- 'https://api.fortnox.se/3/test'
319
- ).with(
320
- headers: {
321
- 'Access-Token' => access_token2,
322
- 'Client-Secret' => client_secret,
323
- 'Content-Type' => 'application/json',
324
- 'Accept' => 'application/json'
325
- }
326
- ).to_return(
327
- status: 200,
328
- body: '2'
329
- )
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/)
330
83
  end
331
84
 
332
- context 'with subsequent requests on same object' do
333
- let!(:response1) { repository.get('/test', body: '') }
334
- let!(:response2) { repository.get('/test', body: '') }
335
- 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 }
336
88
 
337
- # rubocop:disable RSpec/MultipleExpectations
338
- # All these checks must be run in same it-statement because
339
- # of the random starting index.
340
- it 'works' do
341
- expect(response1).not_to eq(response2)
342
- expect(response1).to eq(response3)
343
- 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/)
344
93
  end
345
- # rubocop:enable RSpec/MultipleExpectations
346
94
  end
347
95
  end
348
96
 
349
- context 'when raising error from remote server' do
350
- error_message = 'Räkenskapsår finns inte upplagt. '\
351
- 'För att kunna skapa en faktura krävs det att det finns ett räkenskapsår'
352
-
97
+ context 'when receiving HTML from remote server' do
353
98
  before do
354
- Fortnox::API.configure do |conf|
355
- conf.client_secret = client_secret
356
- conf.access_tokens = [access_token, access_token2]
357
- end
99
+ set_api_test_configuration
358
100
 
359
101
  stub_request(
360
- :post,
361
- 'https://api.fortnox.se/3/test'
102
+ :get,
103
+ 'https://api.fortnox.se/3nonsense'
362
104
  ).to_return(
363
- status: 500,
364
- body: {
365
- 'ErrorInformation' => {
366
- 'error' => 1,
367
- 'message' => error_message
368
- }
369
- }.to_json,
370
- 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' }
371
111
  )
372
112
  end
373
113
 
374
- 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
375
121
 
376
- it { is_expected.to raise_error(Fortnox::API::RemoteServerError) }
377
- 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
378
126
 
379
- context 'with debugging enabled' do
380
- around do |example|
381
- Fortnox::API.config.debugging = true
382
- example.run
383
- 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 })
384
161
  end
385
162
 
386
- 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
387
166
  end
388
167
  end
389
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', 31
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