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.
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
@@ -22,8 +22,8 @@ module Fortnox
22
22
  # DeliveredQuantity Delivered quantity. 14 digits
23
23
  attribute :delivered_quantity, Types::Sized::Float[-9_999_999_999_999.9, 9_999_999_999_999.9]
24
24
 
25
- # Description Description Row description. 50 characters
26
- attribute :description, Types::Sized::String[50]
25
+ # Description Description Row description.
26
+ attribute :description, Types::Sized::String[255]
27
27
 
28
28
  # Discount amount. 12 digits (for amount) / 5 digits (for percent)
29
29
  # TODO(hannes): Verify that we can send in more than 5 digits through
@@ -45,7 +45,7 @@ module Fortnox
45
45
  attribute :housework_type, Types::HouseworkType
46
46
 
47
47
  # Price Price per unit. 12 digits
48
- attribute :price, Types::Sized::Float[0.0, 99_999_999_999.9]
48
+ attribute :price, Types::Sized::Float[-9_999_999_999, 99_999_999_999.9]
49
49
 
50
50
  # Project Code of the project for the row.
51
51
  attribute :project, Types::Nullable::String
@@ -7,6 +7,7 @@ module Fortnox
7
7
  def self.sized(size)
8
8
  lambda do |value|
9
9
  return nil if value == ''
10
+
10
11
  value.to_s.upcase[0...size] unless value.nil?
11
12
  end
12
13
  end
@@ -14,14 +15,16 @@ module Fortnox
14
15
  def self.default
15
16
  lambda do |value|
16
17
  return nil if value == ''
17
- value.to_s.upcase unless value.nil?
18
+
19
+ value&.to_s&.upcase
18
20
  end
19
21
  end
20
22
 
21
23
  def self.lower_case
22
24
  lambda do |value|
23
25
  return nil if value == ''
24
- value.to_s.downcase unless value.nil?
26
+
27
+ value&.to_s&.downcase
25
28
  end
26
29
  end
27
30
  end
@@ -33,16 +36,29 @@ module Fortnox
33
36
  'AMOUNT', 'PERCENT'
34
37
  )
35
38
  HOUSEWORK_TYPES = {
36
- rot: %w[
37
- CONSTRUCTION ELECTRICITY GLASSMETALWORK GROUNDDRAINAGEWORK
38
- MASONRY PAINTINGWALLPAPERING HVAC OTHERCOSTS
39
+ rot: [
40
+ 'CONSTRUCTION',
41
+ 'ELECTRICITY',
42
+ 'GLASSMETALWORK',
43
+ 'GROUNDDRAINAGEWORK',
44
+ 'HVAC',
45
+ 'MASONRY',
46
+ 'OTHERCOSTS',
47
+ 'PAINTINGWALLPAPERING'
39
48
  ],
40
- rut: %w[
41
- MAJORAPPLIANCEREPAIR MOVINGSERVICES ITSERVICES CLEANING
42
- TEXTILECLOTHING SNOWPLOWING GARDENING BABYSITTING OTHERCARE
43
- OTHERCOSTS
49
+ rut: [
50
+ 'BABYSITTING',
51
+ 'CLEANING',
52
+ 'GARDENING',
53
+ 'ITSERVICES',
54
+ 'MAJORAPPLIANCEREPAIR',
55
+ 'MOVINGSERVICES',
56
+ 'OTHERCARE',
57
+ 'OTHERCOSTS',
58
+ 'SNOWPLOWING',
59
+ 'TEXTILECLOTHING'
44
60
  ],
45
- legacy_rut: %w[COOKING TUTORING]
61
+ legacy_rut: ['COOKING', 'TUTORING']
46
62
  }.freeze
47
63
 
48
64
  # TODO: RUT to be added:
@@ -85,7 +101,7 @@ module Fortnox
85
101
  'SEVAT', 'SEREVERSEDVAT', 'EUREVERSEDVAT', 'EUVAT', 'EXPORT'
86
102
  )
87
103
  DefaultDeliveryTypeValues = Types::Strict::String.enum(
88
- 'PRINT', 'EMAIL', 'PRINTSERVICE'
104
+ 'PRINT', 'EMAIL', 'PRINTSERVICE', 'ELECTRONICINVOICE'
89
105
  )
90
106
  ProjectStatusTypes = Types::Strict::String.enum(
91
107
  'NOTSTARTED', 'ONGOING', 'COMPLETED'
@@ -5,7 +5,6 @@ module Fortnox
5
5
  module Types
6
6
  class Model < Dry::Struct
7
7
  transform_types(&:omittable)
8
- transform_keys(&:to_sym)
9
8
 
10
9
  def initialize(input_attributes)
11
10
  if (missing_key = first_missing_required_key(input_attributes))
@@ -19,9 +18,7 @@ module Fortnox
19
18
  private
20
19
 
21
20
  def missing_keys(attributes)
22
- non_nil_attributes = attributes.reject { |_, value| value.nil? }
23
-
24
- attribute_keys = non_nil_attributes.keys
21
+ attribute_keys = attributes.compact.keys
25
22
  schema_keys = self.class.schema.keys.map(&:name)
26
23
 
27
24
  schema_keys - attribute_keys
@@ -7,7 +7,7 @@ module Fortnox
7
7
  module String
8
8
  def self.[](size)
9
9
  Types::Strict::String.constrained(max_size: size).optional.constructor do |value|
10
- value.to_s unless value.nil?
10
+ value&.to_s
11
11
  end
12
12
  end
13
13
  end
@@ -23,7 +23,7 @@ module Fortnox
23
23
  module Float
24
24
  def self.[](low, high)
25
25
  Types::Strict::Float.constrained(gteq: low, lteq: high).optional.constructor do |value|
26
- value.to_f unless value.nil?
26
+ value&.to_f
27
27
  end
28
28
  end
29
29
  end
@@ -38,6 +38,7 @@ module Fortnox
38
38
  .optional
39
39
  .constructor do |value|
40
40
  next nil if value.nil? || value == ''
41
+
41
42
  value
42
43
  end
43
44
 
@@ -92,7 +93,7 @@ module Fortnox
92
93
  Email = Strict::String
93
94
  .constrained(max_size: 1024, format: /^$|\A[[[:alnum:]]+-_.]+@[\w+-_.]+\.[a-z]+\z/i)
94
95
  .optional
95
- .constructor { |v| v.to_s.downcase unless v.nil? }
96
+ .constructor { |v| v&.to_s&.downcase }
96
97
 
97
98
  HouseworkType = Strict::String
98
99
  .constrained(included_in: HouseworkTypes.values)
@@ -119,6 +120,18 @@ module Fortnox
119
120
  .optional
120
121
  .constructor(EnumConstructors.lower_case)
121
122
 
123
+ # Some Fortnox endpoints returns a String and some returns an Integer...
124
+ # The documentation says it should be a string, so let's keep it as a string.
125
+ SalesAccount = Strict::String
126
+ .constrained(format: /^[0-9]{4}$/)
127
+ .optional
128
+ .constructor do |value|
129
+ next nil if value == '' || value.nil?
130
+ next value.to_s if value.is_a?(::Integer)
131
+
132
+ value
133
+ end
134
+
122
135
  require_relative 'types/model'
123
136
  require_relative 'types/default_delivery_types'
124
137
  require_relative 'types/default_templates'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fortnox
4
4
  module API
5
- VERSION = '0.8.2'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
data/lib/fortnox/api.rb CHANGED
@@ -5,7 +5,6 @@ require 'dry-configurable'
5
5
  require 'dry-container'
6
6
  require 'logger'
7
7
 
8
- require_relative 'api/circular_queue'
9
8
  require_relative 'api/version'
10
9
 
11
10
  module Fortnox
@@ -14,10 +13,7 @@ module Fortnox
14
13
 
15
14
  DEFAULT_CONFIGURATION = {
16
15
  base_url: 'https://api.fortnox.se/3/',
17
- client_secret: nil,
18
- token_store: {},
19
- access_token: nil,
20
- access_tokens: nil,
16
+ token_url: 'https://apps.fortnox.se/oauth-v1/token',
21
17
  debugging: false,
22
18
  logger: lambda {
23
19
  logger = Logger.new($stdout)
@@ -27,23 +23,18 @@ module Fortnox
27
23
  }.freeze
28
24
 
29
25
  setting :base_url, default: DEFAULT_CONFIGURATION[:base_url]
30
- setting :client_secret, default: DEFAULT_CONFIGURATION[:client_secret]
31
- setting :token_store, default: DEFAULT_CONFIGURATION[:token_store]
32
- setting :access_token, default: DEFAULT_CONFIGURATION[:access_token], constructor: (proc do |value|
33
- next if value.nil? # nil is a valid unassigned value
34
- invalid_access_token_format!(value) unless value.is_a?(String)
35
- config.token_store = { default: value }
36
- value
37
- end)
38
- setting :access_tokens, default: DEFAULT_CONFIGURATION[:access_tokens], constructor: (proc do |value|
39
- next if value.nil? # nil is a valid unassigned value
40
- invalid_access_tokens_format!(value) unless value.is_a?(Hash) || value.is_a?(Array)
41
- config.token_store = value.is_a?(Hash) ? value : { default: value }
42
- value
43
- end)
26
+ setting :token_url, default: DEFAULT_CONFIGURATION[:token_url]
44
27
  setting :debugging, default: DEFAULT_CONFIGURATION[:debugging], reader: true
45
28
  setting :logger, default: DEFAULT_CONFIGURATION[:logger], reader: true
46
29
 
30
+ def self.access_token=(token)
31
+ Thread.current[:access_token] = token
32
+ end
33
+
34
+ def self.access_token
35
+ Thread.current[:access_token]
36
+ end
37
+
47
38
  class Exception < StandardError
48
39
  end
49
40
 
@@ -59,21 +50,10 @@ module Fortnox
59
50
  class MissingConfiguration < Fortnox::API::Exception
60
51
  end
61
52
 
62
- Registry = Dry::Container.new
63
-
64
- def self.invalid_access_token_format!(value)
65
- raise ArgumentError,
66
- 'expected a String, but '\
67
- "#{value.inspect} is a(n) #{value.class}"
53
+ class MissingAccessToken < Fortnox::API::Exception
68
54
  end
69
- private_class_method :invalid_access_token_format!
70
55
 
71
- def self.invalid_access_tokens_format!(value)
72
- raise ArgumentError,
73
- 'expected a Hash or an Array, but '\
74
- "#{value.inspect} is a(n) #{value.class}"
75
- end
76
- private_class_method :invalid_access_tokens_format!
56
+ Registry = Dry::Container.new
77
57
  end
78
58
  end
79
59
 
@@ -6,6 +6,8 @@ require 'fortnox/api/mappers/base/canonical_name_sym'
6
6
  describe Fortnox::API::Mapper::CanonicalNameSym do
7
7
  describe '.canonical_name_sym' do
8
8
  context 'with simple class' do
9
+ subject { TestClass.canonical_name_sym }
10
+
9
11
  before do
10
12
  test_class = Class.new do
11
13
  extend Fortnox::API::Mapper::CanonicalNameSym
@@ -14,12 +16,12 @@ describe Fortnox::API::Mapper::CanonicalNameSym do
14
16
  stub_const('TestClass', test_class)
15
17
  end
16
18
 
17
- subject { TestClass.canonical_name_sym }
18
-
19
19
  it { is_expected.to eq(:testclass) }
20
20
  end
21
21
 
22
22
  context 'when class included in module' do
23
+ subject { Something::Test.canonical_name_sym }
24
+
23
25
  before do
24
26
  test_class = Class.new do
25
27
  extend Fortnox::API::Mapper::CanonicalNameSym
@@ -28,8 +30,6 @@ describe Fortnox::API::Mapper::CanonicalNameSym do
28
30
  stub_const('Something::Test', test_class)
29
31
  end
30
32
 
31
- subject { Something::Test.canonical_name_sym }
32
-
33
33
  it { is_expected.to eq(:test) }
34
34
  end
35
35
  end
@@ -8,14 +8,12 @@ describe Fortnox::API::Mapper::FromJSON do
8
8
  include_context 'with JSON conversion'
9
9
 
10
10
  before do
11
- module Test
12
- class BaseMapper
13
- include Fortnox::API::Mapper::FromJSON
14
- end
11
+ Test::BaseMapper.class_eval do
12
+ include Fortnox::API::Mapper::FromJSON # rubocop:disable RSpec/DescribedClass
15
13
  end
16
14
 
17
- register_mapper(:categories, Test::CategoryMapper)
18
- register_mapper(:designer, Test::ProductDesignerMapper)
15
+ add_to_registry(:categories, Test::CategoryMapper)
16
+ add_to_registry(:designer, Test::ProductDesignerMapper)
19
17
  end
20
18
 
21
19
  let(:mapper) { Test::ProductMapper.new }
@@ -26,10 +24,10 @@ describe Fortnox::API::Mapper::FromJSON do
26
24
  {
27
25
  'Product' => {
28
26
  '@url': 'someurl@example.com',
29
- 'Name': 'Ford Mustang',
30
- 'VAT': 30_000,
31
- 'Categories': [{ 'Name' => 'Cars', 'ID' => '1' }, { 'Name' => 'Fast cars', 'ID' => '2' }],
32
- 'Designer': { 'Name' => 'John Najjar', 'ID' => '23' }
27
+ Name: 'Ford Mustang',
28
+ VAT: 30_000,
29
+ Categories: [{ Name: 'Cars', ID: '1' }, { Name: 'Fast cars', ID: '2' }],
30
+ Designer: { Name: 'John Najjar', ID: '23' }
33
31
  }
34
32
  }
35
33
  end
@@ -47,7 +45,7 @@ describe Fortnox::API::Mapper::FromJSON do
47
45
  # expect( entity_hash[:url] ).to eq 'someurl@example.com'
48
46
  # end
49
47
 
50
- context 'with nested models' do
48
+ describe 'nested models' do
51
49
  let(:expected_nested_model_hash) do
52
50
  [{ name: 'Cars', id: '1' }, { name: 'Fast cars', id: '2' }]
53
51
  end
@@ -57,7 +55,7 @@ describe Fortnox::API::Mapper::FromJSON do
57
55
  end
58
56
  end
59
57
 
60
- context 'with nested model' do
58
+ describe 'nested model' do
61
59
  let(:expected_nested_model_hash) { { name: 'John Najjar', id: '23' } }
62
60
 
63
61
  specify 'is converted correctly' do
@@ -5,73 +5,64 @@ require 'fortnox/api/mappers/base/to_json'
5
5
  require 'fortnox/api/mappers/contexts/json_conversion'
6
6
 
7
7
  describe Fortnox::API::Mapper::ToJSON do
8
- # TODO: Following error is risen sometimes when all specs are run:
9
- # "NoMethodError: undefined method `call\' for Test::ProductMapper:Class`"
10
- it 'should be tested when random error is fixed!'
8
+ include_context 'with JSON conversion'
11
9
 
12
- describe 'wrap_entity_json_hash' do
13
- it 'should be tested'
10
+ before do
11
+ Test::BaseMapper.class_eval do
12
+ include Fortnox::API::Mapper::ToJSON # rubocop:disable RSpec/DescribedClass
13
+ end
14
14
  end
15
15
 
16
- # include_context 'with JSON conversion'
16
+ let(:mapper) { Test::ProductMapper.new }
17
17
 
18
- # before do
19
- # module Test
20
- # class BaseMapper
21
- # include Fortnox::API::Mapper::ToJSON
22
- # end
23
- # end
24
- # end
18
+ describe '#entity_to_hash' do
19
+ let(:product) do
20
+ category1 = Test::Category.new(name: 'Cars', id: '1')
21
+ category2 = Test::Category.new(name: 'Fast Cars', id: '2')
22
+ product_designer = Test::ProductDesigner.new(name: 'John Najjar', id: '23')
25
23
 
26
- # let( :mapper ){ Test::ProductMapper.new }
24
+ Test::Product.new(url: 'someurl@example.com',
25
+ name: 'Ford Mustang',
26
+ vat: 30_000.0,
27
+ categories: [category1, category2],
28
+ designer: product_designer)
29
+ end
30
+ let(:keys_to_filter) { [:url] }
31
+ let(:returned_hash) { mapper.entity_to_hash(product, keys_to_filter) }
32
+ let(:inner_hash) { returned_hash['Product'] }
27
33
 
28
- # describe 'entity_to_hash' do
29
- # let( :category1 ){ Test::Category.new( name: 'Cars', id: '1' ) }
30
- # let( :category2 ){ Test::Category.new( name: 'Fast Cars', id: '2' ) }
31
- # let( :product_designer ){ Test::ProductDesigner.new( name: 'John Najjar', id: '23' ) }
32
- # let( :product ) do
33
- # Test::Product.new( url: 'someurl@example.com',
34
- # name: 'Ford Mustang',
35
- # vat: 30000.0 ,
36
- # categories: [category1, category2],
37
- # designer: product_designer )
38
- # end
39
- # let( :keys_to_filter ){ [:url] }
40
- # let( :returned_hash ){ mapper.entity_to_hash( product, keys_to_filter ) }
41
- # let( :inner_hash ){ returned_hash['Product'] }
34
+ it 'includes JSON entity wrapper' do
35
+ expect(returned_hash).to have_key('Product')
36
+ end
42
37
 
43
- # it 'includes JSON entity wrapper' do
44
- # expect( returned_hash ).to have_key( 'Product' )
45
- # end
38
+ describe 'keys without mapping' do
39
+ specify 'converts correctly' do
40
+ expect(inner_hash['Name']).to eq('Ford Mustang')
41
+ end
42
+ end
46
43
 
47
- # context 'with keys without mapping' do
48
- # specify 'converts correctly' do
49
- # expect( inner_hash['Name'] ).to eq( 'Ford Mustang' )
50
- # end
51
- # end
44
+ describe 'keys to filter' do
45
+ specify 'filteres out those keys' do
46
+ expect(inner_hash).not_to have_key('@url')
47
+ end
48
+ end
52
49
 
53
- # context 'with keys to filter' do
54
- # specify 'filteres out those keys' do
55
- # expect( inner_hash ).not_to have_key( '@url' )
56
- # end
57
- # end
50
+ describe 'nested models' do
51
+ let(:expected_nested_model_hash) do
52
+ [{ 'Name' => 'Cars', 'ID' => '1' }, { 'Name' => 'Fast Cars', 'ID' => '2' }]
53
+ end
58
54
 
59
- # context 'with nested models' do
60
- # let( :expected_nested_model_hash ) do
61
- # [{"Name" => "Cars", "ID" => "1"}, {"Name" => "Fast Cars", "ID" => "2"}]
62
- # end
55
+ specify 'are converted correctly' do
56
+ expect(inner_hash['Categories']).to eq(expected_nested_model_hash)
57
+ end
58
+ end
63
59
 
64
- # specify 'are converted correctly' do
65
- # expect( inner_hash['Categories'] ).to eq( expected_nested_model_hash )
66
- # end
67
- # end
60
+ describe 'nested model' do
61
+ let(:expected_nested_model_hash) { { 'Name' => 'John Najjar', 'ID' => '23' } }
68
62
 
69
- # context 'with nested model' do
70
- # let( :expected_nested_model_hash ){ { 'Name' => 'John Najjar', 'ID' => '23' } }
71
-
72
- # specify 'is converted correctly' do
73
- # expect( inner_hash['Designer'] ).to eq( expected_nested_model_hash )
74
- # end
75
- # end
76
- # end
63
+ specify 'is converted correctly' do
64
+ expect(inner_hash['Designer']).to eq(expected_nested_model_hash)
65
+ end
66
+ end
67
+ end
77
68
  end
@@ -99,6 +99,7 @@ describe Fortnox::API::Mapper::Base do
99
99
  let(:value) { true }
100
100
  end
101
101
  end
102
+
102
103
  describe 'falseclass' do
103
104
  include_examples 'identity mapper', :falseclass do
104
105
  let(:value) { false }
@@ -126,17 +127,15 @@ describe Fortnox::API::Mapper::Base do
126
127
 
127
128
  describe 'special cases' do
128
129
  context 'with SE' do
129
- subject { mapper.call('SE') }
130
-
131
130
  it 'translates code to country name in Swedish' do
132
- is_expected.to eq('Sverige')
131
+ expect(mapper.call('SE')).to eq('Sverige')
133
132
  end
134
133
  end
135
134
 
136
135
  context 'with nil value' do
137
136
  subject { mapper.call(nil) }
138
137
 
139
- it { is_expected.to eq(nil) }
138
+ it { is_expected.to be_nil }
140
139
  end
141
140
 
142
141
  context 'with empty string' do
@@ -146,10 +145,8 @@ describe Fortnox::API::Mapper::Base do
146
145
  end
147
146
 
148
147
  context 'with nonsense' do
149
- subject { -> { mapper.call('nonsense') } }
150
-
151
148
  it 'is not supported (since input is sanitised) and therefore blows up' do
152
- raise_error(NoMethodError)
149
+ expect { mapper.call('nonsense') }.to raise_error(NoMethodError)
153
150
  end
154
151
  end
155
152
  end
@@ -2,56 +2,61 @@
2
2
 
3
3
  require 'fortnox/api'
4
4
  require 'fortnox/api/mappers'
5
+ require 'dry/container/stub'
5
6
 
6
7
  shared_context 'with JSON conversion' do
7
- before do
8
- module Test
9
- class BaseMapper
10
- end
11
-
12
- class CategoryMapper < BaseMapper
13
- KEY_MAP = { id: 'ID' }.freeze
14
- end
15
-
16
- class ProductDesignerMapper < BaseMapper
17
- KEY_MAP = { id: 'ID' }.freeze
18
- end
19
-
20
- class ProductMapper < BaseMapper
21
- KEY_MAP = {
22
- vat: 'VAT',
23
- url: '@url' # TODO: How to handle url attribute?
24
- }.freeze
25
- JSON_ENTITY_WRAPPER = 'Product'
26
- JSON_COLLECTION_WRAPPER = 'Products'
27
- end
8
+ include Helpers::Configuration
28
9
 
29
- class Category < Fortnox::API::Model::Base
10
+ before do
11
+ stub_const('Test::BaseMapper', Class.new)
12
+
13
+ stub_const('Test::CategoryMapper', Class.new(Test::BaseMapper))
14
+ stub_const('Test::CategoryMapper::KEY_MAP', { id: 'ID' }.freeze)
15
+
16
+ stub_const('Test::ProductDesignerMapper', Class.new(Test::BaseMapper))
17
+ stub_const('Test::ProductDesignerMapper::KEY_MAP', { id: 'ID' }.freeze)
18
+
19
+ stub_const('Test::ProductMapper', Class.new(Test::BaseMapper))
20
+ stub_const(
21
+ 'Test::ProductMapper::KEY_MAP',
22
+ {
23
+ vat: 'VAT',
24
+ url: '@url' # TODO: How to handle url attribute?
25
+ }.freeze
26
+ )
27
+ stub_const('Test::ProductMapper::JSON_ENTITY_WRAPPER', 'Product')
28
+ stub_const('Test::ProductMapper::JSON_COLLECTION_WRAPPER', 'Products')
29
+
30
+ stub_const(
31
+ 'Test::Category',
32
+ Class.new(Fortnox::API::Model::Base) do
30
33
  attribute :name, 'strict.string'
31
34
  attribute :id, 'strict.string'
32
35
  end
36
+ )
33
37
 
34
- class ProductDesigner < Fortnox::API::Model::Base
38
+ stub_const(
39
+ 'Test::ProductDesigner',
40
+ Class.new(Fortnox::API::Model::Base) do
35
41
  attribute :name, 'strict.string'
36
42
  attribute :id, 'strict.string'
37
43
  end
44
+ )
38
45
 
39
- class Product < Fortnox::API::Model::Base
46
+ stub_const(
47
+ 'Test::Product',
48
+ Class.new(Fortnox::API::Model::Base) do
40
49
  attribute :url, 'strict.string'
41
50
  attribute :name, 'strict.string'
42
51
  attribute :vat, 'strict.float'
43
52
  attribute :categories, Dry::Types['coercible.array'].of(Test::Category)
44
53
  attribute :designer, Test::ProductDesigner
45
54
  end
46
- end
47
-
48
- def register_mapper(mapper_sym, mapper)
49
- return if Fortnox::API::Registry.key? mapper_sym
50
- Fortnox::API::Registry.register(mapper_sym, mapper)
51
- end
55
+ )
52
56
 
53
- register_mapper(:category, Test::CategoryMapper)
54
- register_mapper(:productdesigner, Test::ProductDesignerMapper)
55
- register_mapper(:product, Test::ProductMapper)
57
+ Fortnox::API::Registry.enable_stubs!
58
+ add_to_registry(:category, Test::CategoryMapper)
59
+ add_to_registry(:productdesigner, Test::ProductDesignerMapper)
60
+ add_to_registry(:product, Test::ProductMapper)
56
61
  end
57
62
  end
@@ -8,7 +8,6 @@ require 'fortnox/api/mappers/examples/mapper'
8
8
 
9
9
  module Fortnox
10
10
  module API
11
- # Shhh Rubocop, we don't need a comment here ... Really
12
11
  module Mapper
13
12
  describe Unit do
14
13
  context 'when mapping model' do
@@ -17,13 +16,13 @@ module Fortnox
17
16
  let(:model_hash) { { code: 'lbs', description: 'Pounds' } }
18
17
 
19
18
  describe '#entity_to_hash' do
20
- subject { Unit.new.entity_to_hash(model, {}) }
19
+ subject { described_class.new.entity_to_hash(model, {}) }
21
20
 
22
21
  it { is_expected.to eq(serialised_model_hash) }
23
22
  end
24
23
 
25
24
  describe '#wrapped_json_hash_to_entity_hash' do
26
- subject { Unit.new.wrapped_json_hash_to_entity_hash(serialised_model_hash) }
25
+ subject { described_class.new.wrapped_json_hash_to_entity_hash(serialised_model_hash) }
27
26
 
28
27
  it { is_expected.to eq(model_hash) }
29
28
  end
@@ -46,7 +45,7 @@ module Fortnox
46
45
  end
47
46
 
48
47
  describe '#wrapped_json_collection_to_entities_hash' do
49
- subject { Unit.new.wrapped_json_collection_to_entities_hash(serialised_collection_hash) }
48
+ subject { described_class.new.wrapped_json_collection_to_entities_hash(serialised_collection_hash) }
50
49
 
51
50
  it { is_expected.to eq(collection_hash) }
52
51
  end