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.
- checksums.yaml +4 -4
- data/.env.template +7 -0
- data/.env.test +11 -3
- data/.gitignore +7 -1
- data/.rubocop.yml +17 -1
- data/.travis.yml +10 -9
- data/CHANGELOG.md +22 -8
- data/CONTRIBUTE.md +21 -9
- data/DEVELOPER_README.md +72 -0
- data/Guardfile +13 -4
- data/README.md +226 -64
- data/Rakefile +128 -0
- data/bin/get_tokens +79 -0
- data/bin/renew_tokens +28 -0
- data/fortnox-api.gemspec +10 -9
- data/lib/fortnox/api/mappers/base/from_json.rb +4 -3
- data/lib/fortnox/api/mappers/base/to_json.rb +2 -3
- data/lib/fortnox/api/models/base.rb +12 -10
- data/lib/fortnox/api/models/customer.rb +55 -55
- data/lib/fortnox/api/models/label.rb +2 -2
- data/lib/fortnox/api/repositories/authentication.rb +61 -0
- data/lib/fortnox/api/repositories/base/savers.rb +3 -1
- data/lib/fortnox/api/repositories/base.rb +21 -35
- data/lib/fortnox/api/repositories.rb +1 -0
- data/lib/fortnox/api/request_handling.rb +30 -18
- data/lib/fortnox/api/types/document_row.rb +3 -3
- data/lib/fortnox/api/types/enums.rb +27 -11
- data/lib/fortnox/api/types/model.rb +1 -4
- data/lib/fortnox/api/types/sized.rb +2 -2
- data/lib/fortnox/api/types.rb +14 -1
- data/lib/fortnox/api/version.rb +1 -1
- data/lib/fortnox/api.rb +12 -32
- data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +4 -4
- data/spec/fortnox/api/mappers/base/from_json_spec.rb +10 -12
- data/spec/fortnox/api/mappers/base/to_json_spec.rb +48 -57
- data/spec/fortnox/api/mappers/base_spec.rb +4 -7
- data/spec/fortnox/api/mappers/contexts/json_conversion.rb +38 -33
- data/spec/fortnox/api/mappers/unit_spec.rb +3 -4
- data/spec/fortnox/api/models/base_spec.rb +27 -16
- data/spec/fortnox/api/models/unit_spec.rb +5 -3
- data/spec/fortnox/api/repositories/article_spec.rb +14 -9
- data/spec/fortnox/api/repositories/authentication_spec.rb +103 -0
- data/spec/fortnox/api/repositories/base_spec.rb +106 -319
- data/spec/fortnox/api/repositories/customer_spec.rb +37 -7
- data/spec/fortnox/api/repositories/examples/all.rb +0 -1
- data/spec/fortnox/api/repositories/examples/find.rb +5 -8
- data/spec/fortnox/api/repositories/examples/only.rb +4 -13
- data/spec/fortnox/api/repositories/examples/save.rb +32 -18
- data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -5
- data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +1 -4
- data/spec/fortnox/api/repositories/examples/search.rb +4 -7
- data/spec/fortnox/api/repositories/invoice_spec.rb +64 -15
- data/spec/fortnox/api/repositories/order_spec.rb +11 -9
- data/spec/fortnox/api/repositories/project_spec.rb +7 -6
- data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +9 -7
- data/spec/fortnox/api/repositories/unit_spec.rb +13 -11
- data/spec/fortnox/api/types/country_spec.rb +1 -1
- data/spec/fortnox/api/types/email_spec.rb +2 -2
- data/spec/fortnox/api/types/examples/document_row.rb +3 -3
- data/spec/fortnox/api/types/examples/enum.rb +4 -4
- data/spec/fortnox/api/types/examples/types.rb +1 -3
- data/spec/fortnox/api/types/housework_types_spec.rb +54 -61
- data/spec/fortnox/api/types/model_spec.rb +3 -27
- data/spec/fortnox/api/types/order_row_spec.rb +2 -2
- data/spec/fortnox/api/types/required_spec.rb +6 -11
- data/spec/fortnox/api/types/sales_account_spec.rb +57 -0
- data/spec/fortnox/api_spec.rb +19 -124
- data/spec/spec_helper.rb +0 -14
- data/spec/support/helpers/configuration_helper.rb +30 -3
- data/spec/support/helpers.rb +1 -1
- data/spec/support/matchers/type/attribute_matcher.rb +2 -2
- data/spec/support/matchers/type/have_nullable_date_matcher.rb +6 -4
- data/spec/support/matchers/type/have_nullable_matcher.rb +1 -1
- data/spec/support/matchers/type/have_nullable_string_matcher.rb +5 -5
- data/spec/support/matchers/type/require_attribute_matcher.rb +5 -5
- data/spec/support/matchers/type/type_matcher.rb +1 -1
- data/spec/support/vcr_setup.rb +16 -0
- data/spec/vcr_cassettes/articles/all.yml +16 -43
- data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/articles/find_failure.yml +10 -12
- data/spec/vcr_cassettes/articles/find_id_1.yml +13 -14
- data/spec/vcr_cassettes/articles/find_new.yml +14 -16
- data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/articles/save_new.yml +13 -15
- data/spec/vcr_cassettes/articles/save_old.yml +14 -16
- data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +13 -15
- data/spec/vcr_cassettes/articles/search_by_name.yml +16 -15
- data/spec/vcr_cassettes/articles/search_miss.yml +10 -12
- data/spec/vcr_cassettes/articles/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +13 -27
- data/spec/vcr_cassettes/authentication/expired_token.yml +54 -0
- data/spec/vcr_cassettes/authentication/invalid_authorization.yml +57 -0
- data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +58 -0
- data/spec/vcr_cassettes/authentication/valid_request.yml +63 -0
- data/spec/vcr_cassettes/customers/all.yml +20 -127
- data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/customers/find_failure.yml +10 -12
- data/spec/vcr_cassettes/customers/find_id_1.yml +14 -15
- data/spec/vcr_cassettes/customers/find_new.yml +13 -15
- data/spec/vcr_cassettes/customers/find_with_sales_account.yml +63 -0
- data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/customers/save_new.yml +12 -14
- data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +12 -14
- data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +63 -0
- data/spec/vcr_cassettes/customers/save_old.yml +13 -15
- data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +12 -14
- data/spec/vcr_cassettes/customers/search_by_name.yml +13 -45
- data/spec/vcr_cassettes/customers/search_miss.yml +10 -12
- data/spec/vcr_cassettes/customers/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/invoices/all.yml +47 -112
- data/spec/vcr_cassettes/invoices/filter_hit.yml +14 -18
- data/spec/vcr_cassettes/invoices/filter_invalid.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_failure.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_id_1.yml +15 -16
- data/spec/vcr_cassettes/invoices/find_new.yml +16 -18
- data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/invoices/row_description_limit.yml +65 -0
- data/spec/vcr_cassettes/invoices/save_new.yml +14 -16
- data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +14 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +65 -0
- data/spec/vcr_cassettes/invoices/save_old.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +16 -17
- data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +16 -17
- data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +14 -15
- data/spec/vcr_cassettes/invoices/search_by_name.yml +13 -21
- data/spec/vcr_cassettes/invoices/search_miss.yml +10 -12
- data/spec/vcr_cassettes/invoices/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/orders/all.yml +19 -113
- data/spec/vcr_cassettes/orders/filter_hit.yml +14 -20
- data/spec/vcr_cassettes/orders/filter_invalid.yml +10 -12
- data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/orders/find_failure.yml +10 -12
- data/spec/vcr_cassettes/orders/find_id_1.yml +17 -17
- data/spec/vcr_cassettes/orders/find_new.yml +16 -18
- data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_construction.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_cooking.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_type_electricity.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_gardening.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_hvac.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_itservices.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_masonry.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_othercare.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +11 -13
- data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/orders/save_new.yml +16 -18
- data/spec/vcr_cassettes/orders/save_old.yml +16 -18
- data/spec/vcr_cassettes/orders/save_with_nested_model.yml +15 -16
- data/spec/vcr_cassettes/orders/search_by_name.yml +13 -17
- data/spec/vcr_cassettes/orders/search_miss.yml +10 -12
- data/spec/vcr_cassettes/orders/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/projects/all.yml +14 -37
- data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/projects/find_failure.yml +10 -12
- data/spec/vcr_cassettes/projects/find_id_1.yml +13 -15
- data/spec/vcr_cassettes/projects/find_new.yml +14 -16
- data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +15 -16
- data/spec/vcr_cassettes/projects/save_new.yml +13 -15
- data/spec/vcr_cassettes/projects/save_old.yml +14 -16
- data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/all.yml +16 -23
- data/spec/vcr_cassettes/termsofpayments/find_failure.yml +10 -12
- data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +13 -16
- data/spec/vcr_cassettes/termsofpayments/find_new.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/save_new.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/save_old.yml +12 -14
- data/spec/vcr_cassettes/units/all.yml +13 -24
- data/spec/vcr_cassettes/units/find_failure.yml +10 -12
- data/spec/vcr_cassettes/units/find_id_1.yml +13 -15
- data/spec/vcr_cassettes/units/find_new.yml +12 -14
- data/spec/vcr_cassettes/units/save_new.yml +12 -14
- data/spec/vcr_cassettes/units/save_old.yml +12 -14
- data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +12 -14
- metadata +39 -230
- data/lib/fortnox/api/circular_queue.rb +0 -39
- data/spec/fortnox/api/circular_queue_spec.rb +0 -52
- data/spec/support/helpers/when_performing_helper.rb +0 -7
- 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.
|
26
|
-
attribute :description, Types::Sized::String[
|
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[
|
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
|
-
|
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
|
-
|
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:
|
37
|
-
CONSTRUCTION
|
38
|
-
|
39
|
+
rot: [
|
40
|
+
'CONSTRUCTION',
|
41
|
+
'ELECTRICITY',
|
42
|
+
'GLASSMETALWORK',
|
43
|
+
'GROUNDDRAINAGEWORK',
|
44
|
+
'HVAC',
|
45
|
+
'MASONRY',
|
46
|
+
'OTHERCOSTS',
|
47
|
+
'PAINTINGWALLPAPERING'
|
39
48
|
],
|
40
|
-
rut:
|
41
|
-
|
42
|
-
|
43
|
-
|
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:
|
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
|
-
|
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
|
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
|
26
|
+
value&.to_f
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
data/lib/fortnox/api/types.rb
CHANGED
@@ -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
|
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'
|
data/lib/fortnox/api/version.rb
CHANGED
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
|
-
|
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 :
|
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
|
-
|
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
|
-
|
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
|
-
|
12
|
-
|
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
|
-
|
18
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
16
|
+
let(:mapper) { Test::ProductMapper.new }
|
17
17
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
55
|
+
specify 'are converted correctly' do
|
56
|
+
expect(inner_hash['Categories']).to eq(expected_nested_model_hash)
|
57
|
+
end
|
58
|
+
end
|
63
59
|
|
64
|
-
|
65
|
-
|
66
|
-
# end
|
67
|
-
# end
|
60
|
+
describe 'nested model' do
|
61
|
+
let(:expected_nested_model_hash) { { 'Name' => 'John Najjar', 'ID' => '23' } }
|
68
62
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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 {
|
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 {
|
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 {
|
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
|