fortnox-api 0.6.3 → 0.7.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 (153) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +4 -0
  3. data/.travis.yml +20 -15
  4. data/CHANGELOG.md +18 -0
  5. data/README.md +11 -19
  6. data/fortnox-api.gemspec +6 -6
  7. data/lib/fortnox/api/mappers.rb +5 -38
  8. data/lib/fortnox/api/mappers/base.rb +2 -24
  9. data/lib/fortnox/api/mappers/base/canonical_name_sym.rb +21 -0
  10. data/lib/fortnox/api/mappers/value/array.rb +18 -0
  11. data/lib/fortnox/api/mappers/value/country_code_string.rb +24 -0
  12. data/lib/fortnox/api/mappers/value/date.rb +11 -0
  13. data/lib/fortnox/api/mappers/value/hash.rb +16 -0
  14. data/lib/fortnox/api/mappers/value/identity.rb +18 -0
  15. data/lib/fortnox/api/models/document.rb +1 -1
  16. data/lib/fortnox/api/models/invoice.rb +1 -1
  17. data/lib/fortnox/api/models/order.rb +1 -1
  18. data/lib/fortnox/api/repositories/base.rb +9 -11
  19. data/lib/fortnox/api/request_handling.rb +0 -1
  20. data/lib/fortnox/api/types.rb +19 -3
  21. data/lib/fortnox/api/types/enums.rb +0 -23
  22. data/lib/fortnox/api/types/shim/country_code_string.rb +10 -0
  23. data/lib/fortnox/api/types/sized.rb +3 -16
  24. data/lib/fortnox/api/version.rb +1 -1
  25. data/spec/fortnox/api/circular_queue_spec.rb +52 -0
  26. data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +34 -0
  27. data/spec/fortnox/api/mappers/base_spec.rb +33 -43
  28. data/spec/fortnox/api/mappers/contexts/json_conversion.rb +1 -1
  29. data/spec/fortnox/api/repositories/article_spec.rb +4 -2
  30. data/spec/fortnox/api/repositories/base_spec.rb +28 -8
  31. data/spec/fortnox/api/repositories/customer_spec.rb +2 -1
  32. data/spec/fortnox/api/repositories/invoice_spec.rb +147 -3
  33. data/spec/fortnox/api/repositories/order_spec.rb +2 -2
  34. data/spec/fortnox/api/repositories/project_spec.rb +1 -1
  35. data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +4 -2
  36. data/spec/fortnox/api/repositories/unit_spec.rb +6 -3
  37. data/spec/fortnox/api/types/country_code_spec.rb +76 -0
  38. data/spec/fortnox/api/types/email_spec.rb +8 -7
  39. data/spec/fortnox/api/types/enums_spec.rb +0 -1
  40. data/spec/fortnox/api/types/examples/document_row.rb +1 -1
  41. data/spec/fortnox/api_spec.rb +3 -1
  42. data/spec/vcr_cassettes/articles/all.yml +22 -8
  43. data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +6 -6
  44. data/spec/vcr_cassettes/articles/find_failure.yml +6 -6
  45. data/spec/vcr_cassettes/articles/find_id_1.yml +7 -7
  46. data/spec/vcr_cassettes/articles/find_new.yml +9 -9
  47. data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +6 -6
  48. data/spec/vcr_cassettes/articles/save_new.yml +8 -8
  49. data/spec/vcr_cassettes/articles/save_old.yml +9 -9
  50. data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +8 -8
  51. data/spec/vcr_cassettes/articles/search_by_name.yml +6 -6
  52. data/spec/vcr_cassettes/articles/search_miss.yml +6 -6
  53. data/spec/vcr_cassettes/articles/search_with_special_char.yml +6 -6
  54. data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +18 -8
  55. data/spec/vcr_cassettes/customers/all.yml +9 -9
  56. data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +6 -6
  57. data/spec/vcr_cassettes/customers/find_failure.yml +6 -6
  58. data/spec/vcr_cassettes/customers/find_id_1.yml +7 -7
  59. data/spec/vcr_cassettes/customers/find_new.yml +9 -9
  60. data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +5 -5
  61. data/spec/vcr_cassettes/customers/save_new.yml +7 -7
  62. data/spec/vcr_cassettes/customers/save_old.yml +8 -8
  63. data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +7 -7
  64. data/spec/vcr_cassettes/customers/search_by_name.yml +16 -8
  65. data/spec/vcr_cassettes/customers/search_miss.yml +6 -6
  66. data/spec/vcr_cassettes/customers/search_with_special_char.yml +6 -6
  67. data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +7 -7
  68. data/spec/vcr_cassettes/invoices/all.yml +100 -65
  69. data/spec/vcr_cassettes/invoices/filter_hit.yml +11 -8
  70. data/spec/vcr_cassettes/invoices/filter_invalid.yml +6 -6
  71. data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +6 -6
  72. data/spec/vcr_cassettes/invoices/find_failure.yml +6 -6
  73. data/spec/vcr_cassettes/invoices/find_id_1.yml +7 -7
  74. data/spec/vcr_cassettes/invoices/find_new.yml +11 -12
  75. data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +8 -8
  76. data/spec/vcr_cassettes/invoices/save_new.yml +9 -10
  77. data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +47 -0
  78. data/spec/vcr_cassettes/invoices/save_new_with_country.yml +46 -0
  79. data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +47 -0
  80. data/spec/vcr_cassettes/invoices/save_new_with_country_KR.yml +47 -0
  81. data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +46 -0
  82. data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +46 -0
  83. data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +46 -0
  84. data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +47 -0
  85. data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +47 -0
  86. data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +46 -0
  87. data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +46 -0
  88. data/spec/vcr_cassettes/invoices/save_old.yml +10 -11
  89. data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +48 -0
  90. data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +47 -0
  91. data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +48 -0
  92. data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +47 -0
  93. data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +9 -10
  94. data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +9 -10
  95. data/spec/vcr_cassettes/invoices/search_by_name.yml +13 -9
  96. data/spec/vcr_cassettes/invoices/search_miss.yml +6 -6
  97. data/spec/vcr_cassettes/invoices/search_with_special_char.yml +6 -6
  98. data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +9 -9
  99. data/spec/vcr_cassettes/orders/all.yml +105 -105
  100. data/spec/vcr_cassettes/orders/filter_hit.yml +13 -10
  101. data/spec/vcr_cassettes/orders/filter_invalid.yml +6 -6
  102. data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +6 -6
  103. data/spec/vcr_cassettes/orders/find_failure.yml +6 -6
  104. data/spec/vcr_cassettes/orders/find_id_1.yml +8 -8
  105. data/spec/vcr_cassettes/orders/find_new.yml +11 -12
  106. data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +9 -9
  107. data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +9 -9
  108. data/spec/vcr_cassettes/orders/housework_type_construction.yml +9 -9
  109. data/spec/vcr_cassettes/orders/housework_type_cooking.yml +6 -6
  110. data/spec/vcr_cassettes/orders/housework_type_electricity.yml +9 -9
  111. data/spec/vcr_cassettes/orders/housework_type_gardening.yml +9 -9
  112. data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +9 -9
  113. data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +9 -9
  114. data/spec/vcr_cassettes/orders/housework_type_hvac.yml +9 -9
  115. data/spec/vcr_cassettes/orders/housework_type_masonry.yml +9 -9
  116. data/spec/vcr_cassettes/orders/housework_type_othercare.yml +9 -9
  117. data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +9 -9
  118. data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +9 -9
  119. data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +9 -9
  120. data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +9 -9
  121. data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +6 -6
  122. data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +7 -7
  123. data/spec/vcr_cassettes/orders/save_new.yml +9 -10
  124. data/spec/vcr_cassettes/orders/save_old.yml +10 -11
  125. data/spec/vcr_cassettes/orders/save_with_nested_model.yml +9 -10
  126. data/spec/vcr_cassettes/orders/search_by_name.yml +9 -8
  127. data/spec/vcr_cassettes/orders/search_miss.yml +6 -6
  128. data/spec/vcr_cassettes/orders/search_with_special_char.yml +6 -6
  129. data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +8 -8
  130. data/spec/vcr_cassettes/projects/all.yml +18 -8
  131. data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +6 -6
  132. data/spec/vcr_cassettes/projects/find_failure.yml +6 -6
  133. data/spec/vcr_cassettes/projects/find_id_1.yml +6 -6
  134. data/spec/vcr_cassettes/projects/find_new.yml +9 -9
  135. data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +7 -7
  136. data/spec/vcr_cassettes/projects/save_new.yml +8 -8
  137. data/spec/vcr_cassettes/projects/save_old.yml +8 -8
  138. data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +7 -7
  139. data/spec/vcr_cassettes/termsofpayments/all.yml +10 -7
  140. data/spec/vcr_cassettes/termsofpayments/find_failure.yml +6 -6
  141. data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +6 -6
  142. data/spec/vcr_cassettes/termsofpayments/find_new.yml +8 -8
  143. data/spec/vcr_cassettes/termsofpayments/save_new.yml +8 -8
  144. data/spec/vcr_cassettes/termsofpayments/save_old.yml +8 -8
  145. data/spec/vcr_cassettes/units/all.yml +14 -10
  146. data/spec/vcr_cassettes/units/find_failure.yml +6 -6
  147. data/spec/vcr_cassettes/units/find_id_1.yml +6 -6
  148. data/spec/vcr_cassettes/units/find_new.yml +8 -10
  149. data/spec/vcr_cassettes/units/save_new.yml +8 -10
  150. data/spec/vcr_cassettes/units/save_old.yml +8 -10
  151. data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +8 -10
  152. data/temp.txt +1 -0
  153. metadata +71 -13
@@ -26,7 +26,6 @@ module Fortnox
26
26
  end
27
27
 
28
28
  def execute
29
- self.class.set_headers(@headers)
30
29
  response = yield(self.class)
31
30
  validate_and_parse response
32
31
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'dry-struct'
4
4
  require 'dry-types'
5
+ require 'countries'
6
+ require 'fortnox/api/types/shim/country_code_string'
5
7
 
6
8
  module Dry
7
9
  module Types
@@ -24,6 +26,7 @@ module Fortnox
24
26
  module API
25
27
  module Types
26
28
  include Dry::Types.module
29
+ ISO3166.configure { |config| config.locales = %i[en sv] }
27
30
 
28
31
  THE_TRUTH = { true => true, 'true' => true, false => false, 'false' => false }.freeze
29
32
 
@@ -45,9 +48,22 @@ module Fortnox
45
48
  .constructor(EnumConstructors.default)
46
49
 
47
50
  CountryCode = Strict::String
48
- .constrained(included_in: CountryCodes.values)
49
51
  .optional
50
- .constructor(EnumConstructors.sized(2))
52
+ .constructor do |value|
53
+ next value if value.nil? || value == ''
54
+
55
+ # Fortnox only supports Swedish translation of Sweden
56
+ next CountryCodeString.new('SE') if value =~ /^s(e$|we|ve)/i
57
+
58
+ country = ::ISO3166::Country[value] ||
59
+ ::ISO3166::Country.find_country_by_name(value) ||
60
+ ::ISO3166::Country.find_country_by_translated_names(value)
61
+
62
+ raise Dry::Types::ConstraintError.new('value violates constraints', value) if country.nil?
63
+
64
+ CountryCodeString.new(country.alpha2)
65
+ end
66
+
51
67
  Currency = Strict::String
52
68
  .constrained(included_in: Currencies.values)
53
69
  .optional
@@ -63,7 +79,7 @@ module Fortnox
63
79
  .constructor(EnumConstructors.default)
64
80
 
65
81
  Email = Strict::String
66
- .constrained(max_size: 1024, format: /^$|\A[\w+-_.]+@[\w+-_.]+\.[a-z]+\z/i)
82
+ .constrained(max_size: 1024, format: /^$|\A[[[:alnum:]]+-_.]+@[\w+-_.]+\.[a-z]+\z/i)
67
83
  .optional
68
84
  .constructor { |v| v.to_s.downcase unless v.nil? }
69
85
 
@@ -35,29 +35,6 @@ module Fortnox
35
35
  HouseworkTypes = Types::Strict::String.enum(
36
36
  *(CURRENT_HOUSEWORK_TYPES + LEGACY_HOUSEWORK_TYPES)
37
37
  )
38
- CountryCodes = Types::Strict::String.enum(
39
- 'AF', 'AX', 'AL', 'DZ', 'AS', 'AD', 'AO', 'AI', 'AQ', 'AG', 'AR', 'AM',
40
- 'AW', 'AU', 'AT', 'AZ', 'BS', 'BH', 'BD', 'BB', 'BY', 'BE', 'BZ', 'BJ',
41
- 'BM', 'BT', 'BO', 'BQ', 'BA', 'BW', 'BV', 'BR', 'IO', 'BN', 'BG', 'BF',
42
- 'BI', 'CV', 'KH', 'CM', 'CA', 'KY', 'CF', 'TD', 'CL', 'CN', 'CX', 'CC',
43
- 'CO', 'KM', 'CG', 'CD', 'CK', 'CR', 'CI', 'HR', 'CU', 'CW', 'CY', 'CZ',
44
- 'DK', 'DJ', 'DM', 'DO', 'EC', 'EG', 'SV', 'GQ', 'ER', 'EE', 'ET', 'FK',
45
- 'FO', 'FJ', 'FI', 'FR', 'GF', 'PF', 'TF', 'GA', 'GM', 'GE', 'DE', 'GH',
46
- 'GI', 'GR', 'GL', 'GD', 'GP', 'GU', 'GT', 'GG', 'GN', 'GW', 'GY', 'HT',
47
- 'HM', 'VA', 'HN', 'HK', 'HU', 'IS', 'IN', 'ID', 'IR', 'IQ', 'IE', 'IM',
48
- 'IL', 'IT', 'JM', 'JP', 'JE', 'JO', 'KZ', 'KE', 'KI', 'KP', 'KR', 'KW',
49
- 'KG', 'LA', 'LV', 'LB', 'LS', 'LR', 'LY', 'LI', 'LT', 'LU', 'MO', 'MK',
50
- 'MG', 'MW', 'MY', 'MV', 'ML', 'MT', 'MH', 'MQ', 'MR', 'MU', 'YT', 'MX',
51
- 'FM', 'MD', 'MC', 'MN', 'ME', 'MS', 'MA', 'MZ', 'MM', 'NA', 'NR', 'NP',
52
- 'NL', 'NC', 'NZ', 'NI', 'NE', 'NG', 'NU', 'NF', 'MP', 'NO', 'OM', 'PK',
53
- 'PW', 'PS', 'PA', 'PG', 'PY', 'PE', 'PH', 'PN', 'PL', 'PT', 'PR', 'QA',
54
- 'RE', 'RO', 'RU', 'RW', 'BL', 'SH', 'KN', 'LC', 'MF', 'PM', 'VC', 'WS',
55
- 'SM', 'ST', 'SA', 'SN', 'RS', 'SC', 'SL', 'SG', 'SX', 'SK', 'SI', 'SB',
56
- 'SO', 'ZA', 'GS', 'SS', 'ES', 'LK', 'SD', 'SR', 'SJ', 'SZ', 'SE', 'CH',
57
- 'SY', 'TW', 'TJ', 'TZ', 'TH', 'TL', 'TG', 'TK', 'TO', 'TT', 'TN', 'TR',
58
- 'TM', 'TC', 'TV', 'UG', 'UA', 'AE', 'GB', 'US', 'UM', 'UY', 'UZ', 'VU',
59
- 'VE', 'VN', 'VG', 'VI', 'WF', 'EH', 'YE', 'ZM', 'ZW'
60
- )
61
38
  Currencies = Types::Strict::String.enum(
62
39
  'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN',
63
40
  'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BOV',
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fortnox
4
+ module API
5
+ module Types
6
+ class CountryCodeString < String
7
+ end
8
+ end
9
+ end
10
+ end
@@ -21,22 +21,9 @@ module Fortnox
21
21
  end
22
22
 
23
23
  module Float
24
- def self.[](low = nil, high = nil, from: nil, to: nil, range: nil)
25
- if !from.nil? && !to.nil?
26
- # Legacy
27
- Types::Strict::Float.constrained(gteq: low, lteq: high).optional.constructor do |value|
28
- value.to_f unless value.nil?
29
- end
30
- elsif range
31
- # Legacy
32
- Types::Strict::Float.constrained(gteq: low, lteq: high).optional.constructor do |value|
33
- value.to_f unless value.nil?
34
- end
35
- else
36
- # Legacy
37
- Types::Strict::Float.constrained(gteq: low, lteq: high).optional.constructor do |value|
38
- value.to_f unless value.nil?
39
- end
24
+ def self.[](low, high)
25
+ Types::Strict::Float.constrained(gteq: low, lteq: high).optional.constructor do |value|
26
+ value.to_f unless value.nil?
40
27
  end
41
28
  end
42
29
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fortnox
4
4
  module API
5
- VERSION = '0.6.3'
5
+ VERSION = '0.7.0'
6
6
  end
7
7
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'fortnox/api/circular_queue'
5
+
6
+ describe Fortnox::API::CircularQueue do
7
+ describe 'start index' do
8
+ context 'when running several times' do
9
+ let(:test_array) { (0..99).to_a }
10
+ let(:samples) { Array.new(100) { described_class.new(*test_array).next } }
11
+
12
+ subject { Set.new(samples).size }
13
+
14
+ # NOTE: This test is not perfect. We are testing that a random generator
15
+ # with 100 items to choose from does not choose the same item 100 times in a row.
16
+ # Yes, the possibility is low, but I thought I should just mention it :)
17
+ it 'does not start with the same item each time' do
18
+ is_expected.to be > 1
19
+ end
20
+ end
21
+ end
22
+
23
+ describe '#next' do
24
+ context 'when several items in queue' do
25
+ let(:queue) { described_class.new(1,2,3) }
26
+ let(:first_round) { Array.new(3) { queue.next } }
27
+ let(:second_round) { Array.new(3) { queue.next } }
28
+
29
+ it 'circulates the items' do
30
+ first_round
31
+ expect(first_round[0]).to eq(queue.next)
32
+ end
33
+
34
+ it 'circulates the items in the same order each time' do
35
+ expect(first_round).to eq(second_round)
36
+ end
37
+
38
+ it 'circulates through given input' do
39
+ expect(first_round.sort).to eq([1, 2, 3])
40
+ end
41
+ end
42
+
43
+ context 'when only one item in queue' do
44
+ let(:queue) { described_class.new(1) }
45
+
46
+ it 'circulates the item' do
47
+ first_item = queue.next
48
+ expect(first_item).to eq(queue.next)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'fortnox/api/mappers/base/canonical_name_sym'
5
+
6
+ describe Fortnox::API::Mapper::CanonicalNameSym do
7
+ describe '.canonical_name_sym' do
8
+ context 'with simple class' do
9
+ using_test_class do
10
+ class TestClass
11
+ extend Fortnox::API::Mapper::CanonicalNameSym
12
+ end
13
+ end
14
+
15
+ subject { TestClass.canonical_name_sym }
16
+
17
+ it { is_expected.to eq(:testclass) }
18
+ end
19
+
20
+ context 'when class included in module' do
21
+ using_test_class do
22
+ module Something
23
+ class Test
24
+ extend Fortnox::API::Mapper::CanonicalNameSym
25
+ end
26
+ end
27
+ end
28
+
29
+ subject { Something::Test.canonical_name_sym }
30
+
31
+ it { is_expected.to eq(:test) }
32
+ end
33
+ end
34
+ end
@@ -35,6 +35,12 @@ describe Fortnox::API::Mapper::Base do
35
35
  end
36
36
  end
37
37
 
38
+ describe 'integer' do
39
+ include_examples 'identity mapper', :integer do
40
+ let(:value) { Fortnox::API::Types::Nullable::Integer[1337] }
41
+ end
42
+ end
43
+
38
44
  describe 'float' do
39
45
  include_examples 'identity mapper', :float do
40
46
  let(:value) { Fortnox::API::Types::Nullable::Float[13.37] }
@@ -111,57 +117,41 @@ describe Fortnox::API::Mapper::Base do
111
117
  end
112
118
  end
113
119
 
114
- describe 'AccountNumber' do
115
- include_examples 'identity mapper', :account_number do
116
- let(:value) { Fortnox::API::Types::AccountNumber[1234] }
117
- end
118
- end
119
-
120
120
  describe 'CountryCode' do
121
- include_examples 'identity mapper', :country_code do
122
- let(:value) { Fortnox::API::Types::CountryCode['SE'] }
123
- end
124
- end
121
+ subject { mapper.call('GB') }
125
122
 
126
- describe 'Currency' do
127
- include_examples 'identity mapper', :currency do
128
- let(:value) { Fortnox::API::Types::Currency['SEK'] }
129
- end
130
- end
123
+ let(:mapper) { Fortnox::API::Registry[:countrycodestring] }
131
124
 
132
- describe 'CustomerType' do
133
- include_examples 'identity mapper', :customer_type do
134
- let(:value) { Fortnox::API::Types::CustomerType['PRIVATE'] }
135
- end
136
- end
125
+ it { is_expected.to eq('United Kingdom') }
137
126
 
138
- describe 'DiscountType' do
139
- include_examples 'identity mapper', :discount_type do
140
- let(:value) { Fortnox::API::Types::DiscountType['PERCENT'] }
141
- end
142
- end
127
+ describe 'special cases' do
128
+ context 'with SE' do
129
+ subject { mapper.call('SE') }
143
130
 
144
- describe 'Email' do
145
- include_examples 'identity mapper', :email do
146
- let(:value) { Fortnox::API::Types::Email['email@example.com'] }
147
- end
148
- end
131
+ it 'translates code to country name in Swedish' do
132
+ is_expected.to eq('Sverige')
133
+ end
134
+ end
149
135
 
150
- describe 'HouseworkType' do
151
- include_examples 'identity mapper', :housework_type do
152
- let(:value) { Fortnox::API::Types::HouseworkType['CONSTRUCTION'] }
153
- end
154
- end
136
+ context 'with nil value' do
137
+ subject { mapper.call(nil) }
155
138
 
156
- describe 'VATType' do
157
- include_examples 'identity mapper', :vat_type do
158
- let(:value) { Fortnox::API::Types::VATType['SEVAT'] }
159
- end
160
- end
139
+ it { is_expected.to eq(nil) }
140
+ end
161
141
 
162
- describe '#canonical_name_sym' do
163
- subject { described_class.canonical_name_sym }
142
+ context 'with empty string' do
143
+ subject { mapper.call('') }
144
+
145
+ it { is_expected.to eq('') }
146
+ end
164
147
 
165
- it { is_expected.to eq(described_class.name.split('::').last.downcase.to_sym) }
148
+ context 'with nonsense' do
149
+ subject { -> { mapper.call('nonsense') } }
150
+
151
+ it 'is not supported (since input is sanitised) and therefore blows up' do
152
+ raise_error(NoMethodError)
153
+ end
154
+ end
155
+ end
166
156
  end
167
157
  end
@@ -40,7 +40,7 @@ shared_context 'with JSON conversion' do
40
40
  attribute :url, 'strict.string'
41
41
  attribute :name, 'strict.string'
42
42
  attribute :vat, 'strict.float'
43
- attribute :categories, Dry::Types['coercible.array'].member(Test::Category)
43
+ attribute :categories, Dry::Types['coercible.array'].of(Test::Category)
44
44
  attribute :designer, Test::ProductDesigner
45
45
  end
46
46
  end
@@ -26,11 +26,13 @@ describe Fortnox::API::Repository::Article, order: :defined, integration: true d
26
26
  :ean,
27
27
  '5901234123457'
28
28
 
29
- include_examples '.all', 12
29
+ # When recording new VCR cassettes, expected matches must be increased
30
+ include_examples '.all', 26
30
31
 
32
+ # When recording new VCR cassettes, expected matches must be increased
31
33
  include_examples '.find', '1' do
32
34
  let(:find_by_hash_failure) { { description: 'Not Found' } }
33
- let(:single_param_find_by_hash) { { find_hash: { articlenumber: 1 }, matches: 3 } }
35
+ let(:single_param_find_by_hash) { { find_hash: { articlenumber: 1 }, matches: 13 } }
34
36
 
35
37
  let(:multi_param_find_by_hash) do
36
38
  { find_hash: { articlenumber: 1, description: 'Cykelpump' }, matches: 1 }
@@ -6,18 +6,25 @@ require 'fortnox/api'
6
6
  describe Fortnox::API::Repository::Base do
7
7
  using_test_class do
8
8
  module Model
9
- class Test
9
+ class RepositoryBaseTest
10
10
  end
11
11
  end
12
12
  module Repository
13
13
  class Test < Fortnox::API::Repository::Base
14
- MODEL = Model::Test
14
+ MODEL = Model::RepositoryBaseTest
15
15
  end
16
16
  end
17
+ end
18
+
19
+ before do
20
+ Fortnox::API::Registry.register(:repositorybasetest, Model::RepositoryBaseTest)
21
+ end
17
22
 
18
- require 'dry/container/stub'
19
- Fortnox::API::Registry.enable_stubs!
20
- Fortnox::API::Registry.stub(:test, Model::Test)
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')
21
28
  end
22
29
 
23
30
  let(:access_token) { '3f08d038-f380-4893-94a0-a08f6e60e67a' }
@@ -36,7 +43,7 @@ describe Fortnox::API::Repository::Base do
36
43
 
37
44
  describe 'creation' do
38
45
  shared_examples_for 'missing configuration' do
39
- subject { -> { repository } }
46
+ subject { -> { Repository::Test.new(token_store: :store1).get('nonsense') } }
40
47
 
41
48
  let(:error) { Fortnox::API::MissingConfiguration }
42
49
 
@@ -45,14 +52,27 @@ describe Fortnox::API::Repository::Base do
45
52
 
46
53
  context 'without base url' do
47
54
  include_examples 'missing configuration' do
48
- before { Fortnox::API.configure { |conf| conf.base_url = nil } }
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
+
49
63
  let(:message) { 'have to provide a base url' }
50
64
  end
51
65
  end
52
66
 
53
67
  context 'without client secret' do
54
68
  include_examples 'missing configuration' do
55
- before { Fortnox::API.configure { |conf| conf.client_secret = nil } }
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
75
+
56
76
  let(:message) { 'have to provide your client secret' }
57
77
  end
58
78
  end
@@ -38,5 +38,6 @@ describe Fortnox::API::Repository::Customer, order: :defined, integration: true
38
38
  end
39
39
  end
40
40
 
41
- include_examples '.search', :name, 'Test', 23
41
+ # When recording new VCR casettes, expected matches must be increased
42
+ include_examples '.search', :name, 'Test', 30
42
43
  end
@@ -37,7 +37,7 @@ describe Fortnox::API::Repository::Invoice, order: :defined, integration: true d
37
37
 
38
38
  # It is not possible to delete Invoces. Therefore, expected nr of Orders
39
39
  # when running .all will continue to increase (until 100, which is max by default).
40
- include_examples '.all', 60
40
+ include_examples '.all', 97
41
41
 
42
42
  include_examples '.find', 1 do
43
43
  let(:find_by_hash_failure) { { yourreference: 'Not found' } }
@@ -51,7 +51,151 @@ describe Fortnox::API::Repository::Invoice, order: :defined, integration: true d
51
51
  end
52
52
  end
53
53
 
54
- include_examples '.search', :customername, 'Test', 3
54
+ include_examples '.search', :customername, 'Test', 7
55
55
 
56
- include_examples '.only', :fullypaid, 1
56
+ include_examples '.only', :fullypaid, 4
57
+
58
+ describe 'country attribute' do
59
+ def new_invoice(country:)
60
+ described_class::MODEL.new(customer_number: 1, country: country)
61
+ end
62
+
63
+ context 'with valid country' do
64
+ def save_invoice(country:, vcr_cassette: country)
65
+ VCR.use_cassette("#{vcr_dir}/save_new_with_country_#{vcr_cassette}") do
66
+ repository.save(new_invoice(country: country))
67
+ end
68
+ end
69
+
70
+ it 'accepts English country names' do
71
+ expect(save_invoice(country: 'Norway').country).to eq('NO')
72
+ end
73
+
74
+ it 'translates Swedish country names to English' do
75
+ expect(save_invoice(country: 'Norge').country).to eq('NO')
76
+ end
77
+
78
+ it 'skips nil values' do
79
+ expect(save_invoice(country: nil, vcr_cassette: 'nil').country).to eq('')
80
+ end
81
+
82
+ it 'skips empty string values' do
83
+ expect(save_invoice(country: '', vcr_cassette: 'empty_string').country).to eq('')
84
+ end
85
+
86
+ describe 'GB' do
87
+ subject { save_invoice(country: 'GB').country }
88
+
89
+ it { is_expected.to eq('GB') }
90
+ end
91
+
92
+ describe 'KR' do
93
+ subject { save_invoice(country: 'KR').country }
94
+
95
+ it { is_expected.to eq('KR') }
96
+ end
97
+
98
+ describe 'VA' do
99
+ subject { save_invoice(country: 'VA').country }
100
+
101
+ it { is_expected.to eq('VA') }
102
+ end
103
+
104
+ describe 'VI' do
105
+ subject { save_invoice(country: 'VI').country }
106
+
107
+ it { is_expected.to eq('VI') }
108
+ end
109
+
110
+ describe 'special cases Sverige' do
111
+ subject { save_invoice(country: 'Sverige').country }
112
+
113
+ it { is_expected.to eq('SE') }
114
+ end
115
+ end
116
+ end
117
+
118
+ describe 'resetting values in Fortnox' do
119
+ context 'with String values' do
120
+ def new_invoice(comments:)
121
+ described_class::MODEL.new(customer_number: 1, comments: comments)
122
+ end
123
+
124
+ let(:persisted_invoice) do
125
+ VCR.use_cassette("#{vcr_dir}/save_new_with_comments") do
126
+ repository.save(new_invoice(comments: 'A comment to be reset'))
127
+ end
128
+ end
129
+
130
+ before { persisted_invoice }
131
+
132
+ context 'by setting value to nil' do
133
+ let(:updated_persisted_invoice) do
134
+ VCR.use_cassette("#{vcr_dir}/save_old_with_nil_comments") do
135
+ repository.save(persisted_invoice.update(comments: nil))
136
+ end
137
+ end
138
+
139
+ subject { updated_persisted_invoice.comments }
140
+
141
+ pending { is_expected.to eq(nil) }
142
+ end
143
+
144
+ context 'by setting value to empty string' do
145
+ let(:updated_persisted_invoice) do
146
+ VCR.use_cassette("#{vcr_dir}/save_old_with_empty_comments") do
147
+ repository.save(persisted_invoice.update(comments: ''))
148
+ end
149
+ end
150
+
151
+ subject { updated_persisted_invoice.comments }
152
+
153
+ it 'does not reset the value' do
154
+ is_expected.to eq('A comment to be reset')
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'with other values' do
160
+ def new_invoice(country:)
161
+ described_class::MODEL.new(customer_number: 1, country: country)
162
+ end
163
+
164
+ let(:persisted_invoice) do
165
+ VCR.use_cassette("#{vcr_dir}/save_new_with_country") do
166
+ repository.save(new_invoice(country: 'Sverige'))
167
+ end
168
+ end
169
+
170
+ before { persisted_invoice }
171
+
172
+ context 'by setting value to nil' do
173
+ let(:updated_persisted_invoice) do
174
+ # TODO: This VCR cassette needs to be re-recorded again
175
+ # when the we fix #172.
176
+ VCR.use_cassette("#{vcr_dir}/save_old_with_nil_country") do
177
+ repository.save(persisted_invoice.update(country: nil))
178
+ end
179
+ end
180
+
181
+ subject { updated_persisted_invoice.country }
182
+
183
+ pending { is_expected.to eq(nil) }
184
+ end
185
+
186
+ context 'by setting value to empty string' do
187
+ let(:updated_persisted_invoice) do
188
+ VCR.use_cassette("#{vcr_dir}/save_old_with_empty_country") do
189
+ repository.save(persisted_invoice.update(country: ''))
190
+ end
191
+ end
192
+
193
+ subject { updated_persisted_invoice.country }
194
+
195
+ it 'does not reset the country' do
196
+ is_expected.to eq('SE')
197
+ end
198
+ end
199
+ end
200
+ end
57
201
  end