fortnox-api 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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