ledger_sync 1.5.0 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/ledger_sync/test/support/test_ledger/client.rb +21 -0
- data/lib/ledger_sync/test/support/test_ledger/config.rb +13 -0
- data/lib/ledger_sync/test/support/test_ledger/customer/deserializer.rb +20 -0
- data/lib/ledger_sync/test/support/test_ledger/customer/operations/create.rb +25 -0
- data/lib/ledger_sync/test/support/test_ledger/customer/operations/find.rb +21 -0
- data/lib/ledger_sync/test/support/test_ledger/customer/operations/update.rb +25 -0
- data/lib/ledger_sync/test/support/test_ledger/customer/searcher.rb +15 -0
- data/lib/ledger_sync/test/support/test_ledger/customer/serializer.rb +21 -0
- data/lib/ledger_sync/test/support/test_ledger/deserializer.rb +15 -0
- data/lib/ledger_sync/test/support/test_ledger/operation/create.rb +24 -0
- data/lib/ledger_sync/test/support/test_ledger/operation/find.rb +25 -0
- data/lib/ledger_sync/test/support/test_ledger/operation/update.rb +24 -0
- data/lib/ledger_sync/test/support/test_ledger/operation.rb +53 -0
- data/lib/ledger_sync/test/support/test_ledger/resource.rb +10 -0
- data/lib/ledger_sync/test/support/test_ledger/resources/customer.rb +18 -0
- data/lib/ledger_sync/test/support/test_ledger/resources/subsidiary.rb +12 -0
- data/lib/ledger_sync/test/support/test_ledger/searcher.rb +60 -0
- data/lib/ledger_sync/test/support/test_ledger/serializer.rb +71 -0
- data/lib/ledger_sync/test/support/test_ledger/subsidiary/deserializer.rb +17 -0
- data/lib/ledger_sync/test/support/test_ledger/subsidiary/searcher.rb +12 -0
- data/lib/ledger_sync/test/support/test_ledger/subsidiary/searcher_deserializer.rb +15 -0
- data/lib/ledger_sync/test/support/test_ledger/subsidiary/serializer.rb +15 -0
- data/lib/ledger_sync/test/support/test_ledger/util/error_matcher.rb +57 -0
- data/lib/ledger_sync/test/support/test_ledger/util/error_parser.rb +27 -0
- data/lib/ledger_sync/test/support/test_ledger/util/ledger_error_parser.rb +103 -0
- data/lib/ledger_sync/test/support/test_ledger/util/operation_error_parser.rb +99 -0
- data/lib/ledger_sync/test/support/test_ledger/webhook.rb +58 -0
- data/lib/ledger_sync/test/support/test_ledger/webhook_event.rb +81 -0
- data/lib/ledger_sync/test/support/test_ledger/webhook_notification.rb +43 -0
- data/lib/ledger_sync/test/support.rb +1 -1
- data/lib/ledger_sync/version.rb +1 -1
- metadata +30 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efcf3cc57a3f68ad9612fde0eb59ac3e06acd8d644a579397c2c1a17046d4be1
|
4
|
+
data.tar.gz: db61863e31a0fc7004e020b1c0672b52a3de117edd7b57a5ff874edf6505e20c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 777f81a8164db6a0e8d82081bcd70d01bdf34542ace2ae494fa475e632201df1e23211308d6df18f97eab5bf49fe1743bcce52bfc44b0e2aebf8d468ce3b8e21
|
7
|
+
data.tar.gz: 3398a7232feb2f4659061705f7aefe960fcf2459a84f156d6ab86f2be8967f0a28c6a0035b4688780dcca99d6937a23b1f0de38bffc0f2e1031c577056fbcef6
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Client
|
7
|
+
include Ledgers::Client::Mixin
|
8
|
+
|
9
|
+
attr_reader :api_key
|
10
|
+
|
11
|
+
def initialize(args = {})
|
12
|
+
@api_key = args.fetch(:api_key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.ledger_attributes_to_save
|
16
|
+
%i[api_key]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'client'
|
4
|
+
|
5
|
+
args = {
|
6
|
+
base_module: LedgerSync::Ledgers::TestLedger,
|
7
|
+
root_path: File.join(LedgerSync.root, 'lib/ledger_sync/test/support/test_ledger')
|
8
|
+
}
|
9
|
+
|
10
|
+
LedgerSync.register_ledger(:test_ledger, args) do |config|
|
11
|
+
config.name = 'Test Ledger'
|
12
|
+
config.add_alias :test
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Customer
|
7
|
+
class Deserializer < TestLedger::Deserializer
|
8
|
+
id
|
9
|
+
|
10
|
+
attribute :name
|
11
|
+
attribute :email
|
12
|
+
attribute :date
|
13
|
+
|
14
|
+
references_one :subsidiary, deserializer: Subsidiary::Deserializer
|
15
|
+
references_many :subsidiaries, deserializer: Subsidiary::Deserializer
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Customer
|
7
|
+
module Operations
|
8
|
+
class Create < TestLedger::Operation::Create
|
9
|
+
class Contract < LedgerSync::Ledgers::Contract
|
10
|
+
params do
|
11
|
+
required(:external_id).maybe(:string)
|
12
|
+
required(:ledger_id).value(:nil)
|
13
|
+
optional(:name).filled(:string)
|
14
|
+
optional(:email).maybe(:string)
|
15
|
+
optional(:date).maybe(:string)
|
16
|
+
required(:subsidiaries).maybe(:hash, Types::Reference)
|
17
|
+
required(:subsidiary).maybe(:hash, Types::Reference)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Customer
|
7
|
+
module Operations
|
8
|
+
class Find < TestLedger::Operation::Find
|
9
|
+
class Contract < LedgerSync::Ledgers::Contract
|
10
|
+
params do
|
11
|
+
required(:name).maybe(:string)
|
12
|
+
required(:email).filled(:string)
|
13
|
+
required(:subsidiary).maybe(:hash, Types::Reference)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Customer
|
7
|
+
module Operations
|
8
|
+
class Update < TestLedger::Operation::Update
|
9
|
+
class Contract < LedgerSync::Ledgers::Contract
|
10
|
+
params do
|
11
|
+
required(:external_id).maybe(:string)
|
12
|
+
required(:ledger_id).filled(:string)
|
13
|
+
optional(:name).filled(:string)
|
14
|
+
optional(:email).maybe(:string)
|
15
|
+
optional(:date).maybe(:string)
|
16
|
+
required(:subsidiaries).maybe(:hash, Types::Reference)
|
17
|
+
required(:subsidiary).maybe(:hash, Types::Reference)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Customer
|
7
|
+
class Searcher < LedgerSync::Ledgers::TestLedger::Searcher
|
8
|
+
def query_string
|
9
|
+
"DisplayName LIKE '%#{query}%'"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../subsidiary/serializer'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Customer
|
9
|
+
class Serializer < TestLedger::Serializer
|
10
|
+
id
|
11
|
+
|
12
|
+
attribute :name
|
13
|
+
attribute :email
|
14
|
+
|
15
|
+
references_one :subsidiary
|
16
|
+
references_many :subsidiaries
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Gem.find_files('ledger_sync/core/ledgers/test_ledger/serializer_type/**/*.rb').each { |path| require path }
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Deserializer < LedgerSync::Deserializer
|
9
|
+
def self.id
|
10
|
+
attribute(:ledger_id, hash_attribute: 'Id')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Operation
|
9
|
+
class Create
|
10
|
+
include TestLedger::Operation::Mixin
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def operate
|
15
|
+
success(
|
16
|
+
resource: resource.dup,
|
17
|
+
response: response
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Operation
|
9
|
+
class Find
|
10
|
+
include TestLedger::Operation::Mixin
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def operate
|
15
|
+
response_to_operation_result(
|
16
|
+
response: client.find(
|
17
|
+
path: ledger_resource_path
|
18
|
+
)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../operation'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Operation
|
9
|
+
class Update
|
10
|
+
include TestLedger::Operation::Mixin
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def operate
|
15
|
+
success(
|
16
|
+
resource: resource.dup.assign_attributes(name: 'New Name'),
|
17
|
+
response: response
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Operation
|
7
|
+
module Mixin
|
8
|
+
def self.included(base)
|
9
|
+
base.include Ledgers::Operation::Mixin
|
10
|
+
base.include InstanceMethods # To ensure these override parent methods
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
def deserialized_resource(response:)
|
15
|
+
deserializer.deserialize(
|
16
|
+
hash: response.body[test_ledger_resource_type.to_s.camelize],
|
17
|
+
resource: resource
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ledger_resource_path
|
22
|
+
@ledger_resource_path ||= "#{ledger_resource_type_for_path}/#{resource.ledger_id}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def ledger_resource_type_for_path
|
26
|
+
test_ledger_resource_type.tr('_', '')
|
27
|
+
end
|
28
|
+
|
29
|
+
def response_to_operation_result(response:)
|
30
|
+
if response.success?
|
31
|
+
success(
|
32
|
+
resource: deserialized_resource(response: response),
|
33
|
+
response: response
|
34
|
+
)
|
35
|
+
else
|
36
|
+
failure(
|
37
|
+
Error::OperationError.new(
|
38
|
+
operation: self,
|
39
|
+
response: response
|
40
|
+
)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_ledger_resource_type
|
46
|
+
@test_ledger_resource_type ||= client.class.ledger_resource_type_for(resource_class: resource.class)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'subsidiary'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Customer < TestLedger::Resource
|
9
|
+
attribute :name, type: LedgerSync::Type::String
|
10
|
+
attribute :email, type: LedgerSync::Type::String
|
11
|
+
attribute :date, type: LedgerSync::Type::Date
|
12
|
+
|
13
|
+
references_one :subsidiary, to: Subsidiary
|
14
|
+
references_many :subsidiaries, to: Subsidiary
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Subsidiary < TestLedger::Resource
|
7
|
+
attribute :name, type: LedgerSync::Type::String
|
8
|
+
attribute :state, type: LedgerSync::Type::String
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Searcher < Ledgers::Searcher
|
7
|
+
include Mixins::OffsetAndLimitPaginationSearcherMixin
|
8
|
+
|
9
|
+
def query_string
|
10
|
+
''
|
11
|
+
end
|
12
|
+
|
13
|
+
def resources
|
14
|
+
resource_class = self.class.inferred_resource_class
|
15
|
+
|
16
|
+
response = client.query(
|
17
|
+
limit: limit,
|
18
|
+
offset: offset,
|
19
|
+
query: query_string,
|
20
|
+
resource_class: resource_class
|
21
|
+
)
|
22
|
+
return [] if response.body.blank?
|
23
|
+
|
24
|
+
(response.body.dig(
|
25
|
+
'QueryResponse',
|
26
|
+
client.class.ledger_resource_type_for(
|
27
|
+
resource_class: resource_class
|
28
|
+
).classify
|
29
|
+
) || []).map do |c|
|
30
|
+
self.class.inferred_deserializer_class.new.deserialize(
|
31
|
+
hash: c,
|
32
|
+
resource: resource_class.new
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def search
|
38
|
+
super
|
39
|
+
rescue OAuth2::Error => e
|
40
|
+
@response = e # TODO: Better catch/raise errors as LedgerSync::Error
|
41
|
+
failure
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Pagination uses notation of limit and offset
|
47
|
+
# limit: number of results per page
|
48
|
+
#
|
49
|
+
# offset: position of first result in a list.
|
50
|
+
# starts from 1, not 0
|
51
|
+
#
|
52
|
+
# More here:
|
53
|
+
# https://developer.intuit.com/app/developer/qbo/docs/develop/explore-the-quickbooks-online-api/data-queries#pagination
|
54
|
+
def default_offset
|
55
|
+
1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Gem.find_files('ledger_sync/core/ledgers/test_ledger/serializer_type/**/*.rb').each { |path| require path }
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Serializer < LedgerSync::Serializer
|
9
|
+
def serialize(args = {})
|
10
|
+
deep_merge_unmapped_values = args.fetch(:deep_merge_unmapped_values, {})
|
11
|
+
only_changes = args.fetch(:only_changes, false)
|
12
|
+
resource = args.fetch(:resource)
|
13
|
+
|
14
|
+
ret = super(
|
15
|
+
only_changes: only_changes,
|
16
|
+
resource: resource
|
17
|
+
)
|
18
|
+
return ret unless deep_merge_unmapped_values.any?
|
19
|
+
|
20
|
+
deep_merge_if_not_mapped(
|
21
|
+
current_hash: ret,
|
22
|
+
hash_to_search: deep_merge_unmapped_values
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.amount(hash_attribute, args = {})
|
27
|
+
attribute(
|
28
|
+
hash_attribute,
|
29
|
+
{
|
30
|
+
type: Serialization::Type::IntegerToAmountFloatType.new
|
31
|
+
}.merge(args)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.date(hash_attribute, args = {})
|
36
|
+
attribute(
|
37
|
+
hash_attribute,
|
38
|
+
{
|
39
|
+
type: LedgerSync::Serialization::Type::FormatDateType.new(format: '%Y-%m-%d')
|
40
|
+
}.merge(args)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.id
|
45
|
+
attribute('Id', resource_attribute: :ledger_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def deep_merge_if_not_mapped(current_hash:, hash_to_search:)
|
51
|
+
hash_to_search.each do |key, value|
|
52
|
+
current_hash[key] = if current_hash.key?(key)
|
53
|
+
if value.is_a?(Hash) && current_hash[key].present?
|
54
|
+
deep_merge_if_not_mapped(
|
55
|
+
current_hash: current_hash[key],
|
56
|
+
hash_to_search: value
|
57
|
+
)
|
58
|
+
else
|
59
|
+
current_hash[key]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
current_hash
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LedgerSync
|
4
|
+
module Ledgers
|
5
|
+
module TestLedger
|
6
|
+
class Util
|
7
|
+
class ErrorMatcher
|
8
|
+
attr_reader :error,
|
9
|
+
:message
|
10
|
+
|
11
|
+
def initialize(error:)
|
12
|
+
@error = error
|
13
|
+
@message = error.message.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def body
|
17
|
+
error.response.body
|
18
|
+
rescue NoMethodError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def error_class
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def error_message # rubocop:disable Metrics/CyclomaticComplexity
|
27
|
+
return error.message unless body
|
28
|
+
|
29
|
+
parsed_body = JSON.parse(body)
|
30
|
+
|
31
|
+
parsed_body.dig('fault', 'error')&.first&.fetch('message') ||
|
32
|
+
parsed_body.dig('Fault', 'Error')&.first&.fetch('Message') ||
|
33
|
+
parsed_body['error']
|
34
|
+
end
|
35
|
+
|
36
|
+
def detail # rubocop:disable Metrics/CyclomaticComplexity
|
37
|
+
(body && JSON.parse(body).dig('fault', 'error')&.first&.fetch('detail')) ||
|
38
|
+
(body && JSON.parse(body).dig('Fault', 'Error')&.first&.fetch('Detail'))
|
39
|
+
end
|
40
|
+
|
41
|
+
def code # rubocop:disable Metrics/CyclomaticComplexity
|
42
|
+
((body && JSON.parse(body).dig('fault', 'error')&.first&.fetch('code')) ||
|
43
|
+
(body && JSON.parse(body).dig('Fault', 'Error')&.first&.fetch('code'))).to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def match?
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
def output_message
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'error_matcher'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Util
|
9
|
+
class ErrorParser
|
10
|
+
attr_reader :error
|
11
|
+
|
12
|
+
def initialize(error:)
|
13
|
+
@error = error
|
14
|
+
end
|
15
|
+
|
16
|
+
def error_class
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'error_parser'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Util
|
9
|
+
class LedgerErrorParser < ErrorParser
|
10
|
+
class ThrottleMatcher < ErrorMatcher
|
11
|
+
def error_class
|
12
|
+
Error::LedgerError::ThrottleError
|
13
|
+
end
|
14
|
+
|
15
|
+
def output_message
|
16
|
+
"Request throttle with: #{error_message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def match?
|
20
|
+
message.include?('source=throttling policy') ||
|
21
|
+
message.include?('errorcode=003001')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AuthenticationMatcher < ErrorMatcher
|
26
|
+
def error_class
|
27
|
+
Error::LedgerError::AuthenticationError
|
28
|
+
end
|
29
|
+
|
30
|
+
def output_message
|
31
|
+
"Authentication Failed with: #{error_message}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def match?
|
35
|
+
code == 3200 ||
|
36
|
+
message.include?('authenticationfailed') ||
|
37
|
+
message.include?('errorcode=003200')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class AuthorizationMatcher < ErrorMatcher
|
42
|
+
def error_class
|
43
|
+
Error::LedgerError::AuthorizationError
|
44
|
+
end
|
45
|
+
|
46
|
+
def output_message
|
47
|
+
"Authorization Failed with: #{error_message}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def match?
|
51
|
+
code == 3100 ||
|
52
|
+
message.include?('authorizationfailed') ||
|
53
|
+
message.include?('errorcode=003100')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ClientMatcher < ErrorMatcher
|
58
|
+
def error_class
|
59
|
+
Error::LedgerError::ConfigurationError
|
60
|
+
end
|
61
|
+
|
62
|
+
def output_message
|
63
|
+
"Missing Configuration: #{error_message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def match?
|
67
|
+
message.include?('invalid_client') ||
|
68
|
+
message.include?('invalid_grant')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
PARSERS = [
|
73
|
+
AuthenticationMatcher,
|
74
|
+
AuthorizationMatcher,
|
75
|
+
ClientMatcher,
|
76
|
+
ThrottleMatcher
|
77
|
+
].freeze
|
78
|
+
|
79
|
+
attr_reader :client
|
80
|
+
|
81
|
+
def initialize(client:, error:)
|
82
|
+
@client = client
|
83
|
+
super(error: error)
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse
|
87
|
+
PARSERS.map do |parser|
|
88
|
+
matcher = parser.new(error: error)
|
89
|
+
next unless matcher.match?
|
90
|
+
|
91
|
+
return matcher.error_class.new(
|
92
|
+
client: client,
|
93
|
+
message: matcher.output_message,
|
94
|
+
response: error
|
95
|
+
)
|
96
|
+
end
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'error_parser'
|
4
|
+
|
5
|
+
module LedgerSync
|
6
|
+
module Ledgers
|
7
|
+
module TestLedger
|
8
|
+
class Util
|
9
|
+
class OperationErrorParser < ErrorParser
|
10
|
+
class DuplicateNameMatcher < ErrorMatcher
|
11
|
+
def error_class
|
12
|
+
Error::OperationError::DuplicateLedgerResourceError
|
13
|
+
end
|
14
|
+
|
15
|
+
def output_message
|
16
|
+
"Resource with same name already exists: #{error_message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def match?
|
20
|
+
code == 6240 ||
|
21
|
+
message.include?('the name supplied already exists')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NotFoundMatcher < ErrorMatcher
|
26
|
+
def error_class
|
27
|
+
Error::OperationError::NotFoundError
|
28
|
+
end
|
29
|
+
|
30
|
+
def output_message
|
31
|
+
"Unable to find in ledger with: #{error_message}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def match?
|
35
|
+
code == 610 ||
|
36
|
+
message.include?('object not found')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ValidationError < ErrorMatcher
|
41
|
+
def error_class
|
42
|
+
Error::OperationError::LedgerValidationError
|
43
|
+
end
|
44
|
+
|
45
|
+
def output_message
|
46
|
+
"Ledger object is not valid: #{error_message}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def match?
|
50
|
+
code == 6080
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class GenericMatcher < ErrorMatcher
|
55
|
+
def error_class
|
56
|
+
Error::OperationError
|
57
|
+
end
|
58
|
+
|
59
|
+
def output_message
|
60
|
+
"Something went wrong: #{error_message}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def match?
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# ! always keep GenericMatcher as last
|
69
|
+
PARSERS = [
|
70
|
+
DuplicateNameMatcher,
|
71
|
+
NotFoundMatcher,
|
72
|
+
ValidationError,
|
73
|
+
GenericMatcher
|
74
|
+
].freeze
|
75
|
+
|
76
|
+
attr_reader :operation
|
77
|
+
|
78
|
+
def initialize(error:, operation: nil)
|
79
|
+
@operation = operation
|
80
|
+
super(error: error)
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse
|
84
|
+
PARSERS.map do |parser|
|
85
|
+
matcher = parser.new(error: error)
|
86
|
+
next unless matcher.match?
|
87
|
+
|
88
|
+
return matcher.error_class.new(
|
89
|
+
operation: operation,
|
90
|
+
message: matcher.output_message,
|
91
|
+
response: error
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'webhook_event'
|
4
|
+
|
5
|
+
# ref: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications#validating-the-notification
|
6
|
+
module LedgerSync
|
7
|
+
module Ledgers
|
8
|
+
module TestLedger
|
9
|
+
class Webhook
|
10
|
+
attr_reader :notifications,
|
11
|
+
:original_payload,
|
12
|
+
:payload
|
13
|
+
|
14
|
+
def initialize(payload:)
|
15
|
+
@original_payload = payload
|
16
|
+
@payload = payload.is_a?(String) ? JSON.parse(payload) : payload
|
17
|
+
|
18
|
+
event_notifications_payload = @payload['eventNotifications']
|
19
|
+
raise 'Invalid payload: Could not find eventNotifications' unless event_notifications_payload.is_a?(Array)
|
20
|
+
|
21
|
+
@notifications = []
|
22
|
+
|
23
|
+
event_notifications_payload.each do |event_notification_payload|
|
24
|
+
@notifications << WebhookNotification.new(
|
25
|
+
payload: event_notification_payload,
|
26
|
+
webhook: self
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def events
|
32
|
+
notifications.map(&:events)
|
33
|
+
end
|
34
|
+
|
35
|
+
def resources
|
36
|
+
@resources ||= notifications.map(&:resources).flatten.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid?(signature:, verification_token:)
|
40
|
+
self.class.valid?(
|
41
|
+
payload: payload.to_json,
|
42
|
+
signature: signature,
|
43
|
+
verification_token: verification_token
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.valid?(payload:, signature:, verification_token:)
|
48
|
+
raise 'Cannot verify non-String payload' unless payload.is_a?(String)
|
49
|
+
|
50
|
+
digest = OpenSSL::Digest.new('sha256')
|
51
|
+
hmac = OpenSSL::HMAC.digest(digest, verification_token, payload)
|
52
|
+
base64 = Base64.encode64(hmac).strip
|
53
|
+
base64 == signature
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ref: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications#validating-the-notification
|
4
|
+
module LedgerSync
|
5
|
+
module Ledgers
|
6
|
+
module TestLedger
|
7
|
+
class WebhookEvent
|
8
|
+
attr_reader :deleted_id,
|
9
|
+
:event_operation,
|
10
|
+
:last_updated_at,
|
11
|
+
:ledger_id,
|
12
|
+
:original_payload,
|
13
|
+
:payload,
|
14
|
+
:test_ledger_resource_type,
|
15
|
+
:webhook_notification,
|
16
|
+
:webhook
|
17
|
+
|
18
|
+
def initialize(payload:, webhook_notification: nil)
|
19
|
+
@original_payload = payload
|
20
|
+
@payload = payload.is_a?(String) ? JSON.parse(payload) : payload
|
21
|
+
|
22
|
+
@deleted_id = @payload['deletedId']
|
23
|
+
|
24
|
+
@event_operation = @payload['operation']
|
25
|
+
raise 'Invalid payload: Could not find operation' if @event_operation.blank?
|
26
|
+
|
27
|
+
@last_updated_at = @payload['lastUpdated']
|
28
|
+
raise 'Invalid payload: Could not find lastUpdated' if @last_updated_at.blank?
|
29
|
+
|
30
|
+
@last_updated_at = Time.parse(@last_updated_at)
|
31
|
+
|
32
|
+
@ledger_id = @payload['id']
|
33
|
+
raise 'Invalid payload: Could not find id' if @ledger_id.blank?
|
34
|
+
|
35
|
+
@test_ledger_resource_type = @payload['name']
|
36
|
+
raise 'Invalid payload: Could not find name' if @test_ledger_resource_type.blank?
|
37
|
+
|
38
|
+
@webhook_notification = webhook_notification
|
39
|
+
@webhook = webhook_notification.try(:webhook)
|
40
|
+
end
|
41
|
+
|
42
|
+
def find(client:)
|
43
|
+
find_operation(client: client).perform
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_operation(client:)
|
47
|
+
find_operation_class(client: client).new(
|
48
|
+
client: client,
|
49
|
+
resource: resource_class.new(ledger_id: ledger_id)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_operation_class(client:)
|
54
|
+
client.class.base_operations_module_for(resource_class: resource_class)::Find
|
55
|
+
end
|
56
|
+
|
57
|
+
def local_resource_type
|
58
|
+
@local_resource_type ||= resource_class.resource_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def resource
|
62
|
+
return unless resource_class.present?
|
63
|
+
|
64
|
+
resource_class.new(ledger_id: ledger_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def resource!
|
68
|
+
if resource.nil?
|
69
|
+
raise "Resource class does not exist for QuickBooks Online object: #{test_ledger_resource_type}"
|
70
|
+
end
|
71
|
+
|
72
|
+
resource
|
73
|
+
end
|
74
|
+
|
75
|
+
def resource_class
|
76
|
+
@resource_class ||= Client.resource_from_ledger_type(type: test_ledger_resource_type.downcase)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'webhook_event'
|
4
|
+
|
5
|
+
# ref: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/managing-webhooks-notifications#validating-the-notification
|
6
|
+
module LedgerSync
|
7
|
+
module Ledgers
|
8
|
+
module TestLedger
|
9
|
+
class WebhookNotification
|
10
|
+
attr_reader :events,
|
11
|
+
:original_payload,
|
12
|
+
:payload,
|
13
|
+
:realm_id,
|
14
|
+
:webhook
|
15
|
+
|
16
|
+
def initialize(args = {})
|
17
|
+
@original_payload = args.fetch(:payload)
|
18
|
+
@webhook = args.fetch(:webhook, nil)
|
19
|
+
@payload = original_payload.is_a?(String) ? JSON.parse(original_payload) : original_payload
|
20
|
+
|
21
|
+
@realm_id = @payload['realmId']
|
22
|
+
raise 'Invalid payload: Could not find realmId' if @realm_id.blank?
|
23
|
+
|
24
|
+
events_payload = @payload.dig('dataChangeEvent', 'entities')
|
25
|
+
raise 'Invalid payload: Could not find dataChangeEvent -> entities' unless events_payload.is_a?(Array)
|
26
|
+
|
27
|
+
@events = []
|
28
|
+
|
29
|
+
events_payload.each do |event_payload|
|
30
|
+
@events << WebhookEvent.new(
|
31
|
+
payload: event_payload,
|
32
|
+
webhook_notification: self
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def resources
|
38
|
+
@resources ||= events.map(&:resource).compact
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/ledger_sync/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ledger_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Jackson
|
@@ -1065,6 +1065,35 @@ files:
|
|
1065
1065
|
- lib/ledger_sync/serializer.rb
|
1066
1066
|
- lib/ledger_sync/test/support.rb
|
1067
1067
|
- lib/ledger_sync/test/support/factory_bot.rb
|
1068
|
+
- lib/ledger_sync/test/support/test_ledger/client.rb
|
1069
|
+
- lib/ledger_sync/test/support/test_ledger/config.rb
|
1070
|
+
- lib/ledger_sync/test/support/test_ledger/customer/deserializer.rb
|
1071
|
+
- lib/ledger_sync/test/support/test_ledger/customer/operations/create.rb
|
1072
|
+
- lib/ledger_sync/test/support/test_ledger/customer/operations/find.rb
|
1073
|
+
- lib/ledger_sync/test/support/test_ledger/customer/operations/update.rb
|
1074
|
+
- lib/ledger_sync/test/support/test_ledger/customer/searcher.rb
|
1075
|
+
- lib/ledger_sync/test/support/test_ledger/customer/serializer.rb
|
1076
|
+
- lib/ledger_sync/test/support/test_ledger/deserializer.rb
|
1077
|
+
- lib/ledger_sync/test/support/test_ledger/operation.rb
|
1078
|
+
- lib/ledger_sync/test/support/test_ledger/operation/create.rb
|
1079
|
+
- lib/ledger_sync/test/support/test_ledger/operation/find.rb
|
1080
|
+
- lib/ledger_sync/test/support/test_ledger/operation/update.rb
|
1081
|
+
- lib/ledger_sync/test/support/test_ledger/resource.rb
|
1082
|
+
- lib/ledger_sync/test/support/test_ledger/resources/customer.rb
|
1083
|
+
- lib/ledger_sync/test/support/test_ledger/resources/subsidiary.rb
|
1084
|
+
- lib/ledger_sync/test/support/test_ledger/searcher.rb
|
1085
|
+
- lib/ledger_sync/test/support/test_ledger/serializer.rb
|
1086
|
+
- lib/ledger_sync/test/support/test_ledger/subsidiary/deserializer.rb
|
1087
|
+
- lib/ledger_sync/test/support/test_ledger/subsidiary/searcher.rb
|
1088
|
+
- lib/ledger_sync/test/support/test_ledger/subsidiary/searcher_deserializer.rb
|
1089
|
+
- lib/ledger_sync/test/support/test_ledger/subsidiary/serializer.rb
|
1090
|
+
- lib/ledger_sync/test/support/test_ledger/util/error_matcher.rb
|
1091
|
+
- lib/ledger_sync/test/support/test_ledger/util/error_parser.rb
|
1092
|
+
- lib/ledger_sync/test/support/test_ledger/util/ledger_error_parser.rb
|
1093
|
+
- lib/ledger_sync/test/support/test_ledger/util/operation_error_parser.rb
|
1094
|
+
- lib/ledger_sync/test/support/test_ledger/webhook.rb
|
1095
|
+
- lib/ledger_sync/test/support/test_ledger/webhook_event.rb
|
1096
|
+
- lib/ledger_sync/test/support/test_ledger/webhook_notification.rb
|
1068
1097
|
- lib/ledger_sync/type/boolean.rb
|
1069
1098
|
- lib/ledger_sync/type/date.rb
|
1070
1099
|
- lib/ledger_sync/type/float.rb
|