peddler 1.6.7 → 2.0.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/README.md +69 -67
- data/lib/mws/feeds/client.rb +15 -12
- data/lib/mws/finances/client.rb +12 -11
- data/lib/mws/fulfillment_inbound_shipment/client.rb +79 -107
- data/lib/mws/fulfillment_inventory/client.rb +5 -5
- data/lib/mws/fulfillment_outbound_shipment/client.rb +36 -44
- data/lib/mws/merchant_fulfillment/client.rb +11 -17
- data/lib/mws/off_amazon_payments/client.rb +38 -68
- data/lib/mws/orders/client.rb +28 -24
- data/lib/mws/products/client.rb +118 -153
- data/lib/mws/recommendations/client.rb +13 -17
- data/lib/mws/reports/client.rb +24 -23
- data/lib/mws/sellers/client.rb +5 -5
- data/lib/mws/subscriptions/client.rb +25 -36
- data/lib/peddler/client.rb +47 -124
- data/lib/peddler/errors/builder.rb +40 -14
- data/lib/peddler/errors/class_generator.rb +34 -0
- data/lib/peddler/errors/error.rb +13 -3
- data/lib/peddler/headers.rb +27 -11
- data/lib/peddler/marketplace.rb +30 -11
- data/lib/peddler/vcr_matcher.rb +11 -1
- data/lib/peddler/version.rb +1 -1
- data/lib/peddler/xml_parser.rb +4 -2
- data/lib/peddler/xml_response_parser.rb +1 -1
- data/test/helper.rb +0 -1
- data/test/integration/test_errors.rb +2 -14
- data/test/integration/test_feeds.rb +0 -3
- data/test/integration/test_multibyte_queries.rb +1 -1
- data/test/integration/test_mws_headers.rb +3 -2
- data/test/integration/test_orders.rb +2 -1
- data/test/integration/test_products.rb +9 -9
- data/test/integration/test_recommendations.rb +1 -1
- data/test/integration/test_subscriptions.rb +2 -2
- data/test/integration_helper.rb +1 -1
- data/test/mws.yml +36 -0
- data/test/mws.yml.example +8 -12
- data/test/null_client.rb +10 -8
- data/test/unit/mws/test_feeds_client.rb +1 -2
- data/test/unit/mws/test_fulfillment_outbound_shipment_client.rb +1 -1
- data/test/unit/mws/test_off_amazon_payments_client.rb +1 -1
- data/test/unit/mws/test_orders_client.rb +7 -6
- data/test/unit/mws/test_products_client.rb +13 -28
- data/test/unit/mws/test_recommendations_client.rb +1 -2
- data/test/unit/mws/test_reports_client.rb +1 -1
- data/test/unit/mws/test_subscriptions_client.rb +1 -130
- data/test/unit/peddler/errors/test_builder.rb +54 -7
- data/test/unit/peddler/errors/test_class_generator.rb +18 -0
- data/test/unit/peddler/errors/test_error.rb +7 -2
- data/test/unit/peddler/test_client.rb +136 -190
- data/test/unit/peddler/test_flat_file_parser.rb +2 -2
- data/test/unit/peddler/test_headers.rb +19 -9
- data/test/unit/peddler/test_marketplace.rb +18 -5
- data/test/unit/peddler/test_vcr_matcher.rb +3 -1
- data/test/vcr_cassettes/Feeds.yml +4816 -5224
- data/test/vcr_cassettes/Reports.yml +3278 -2604
- metadata +8 -9
- data/lib/peddler/errors.rb +0 -12
- data/lib/peddler/errors/handler.rb +0 -59
- data/test/unit/peddler/errors/test_handler.rb +0 -62
- data/test/unit/peddler/test_errors.rb +0 -26
@@ -1,33 +1,59 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
3
|
+
require 'excon'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'peddler/errors/class_generator'
|
6
|
+
require 'peddler/errors/parser'
|
5
7
|
|
6
8
|
module Peddler
|
7
9
|
module Errors
|
8
10
|
# @api private
|
9
11
|
class Builder
|
10
|
-
|
12
|
+
extend Forwardable
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
DIGIT_AS_FIRST_CHAR = /^\d/
|
15
|
+
private_constant :DIGIT_AS_FIRST_CHAR
|
16
|
+
|
17
|
+
def_delegator :error, :response
|
18
|
+
|
19
|
+
def self.call(error)
|
20
|
+
new(error).build
|
14
21
|
end
|
15
22
|
|
16
|
-
|
17
|
-
|
23
|
+
attr_reader :error
|
24
|
+
|
25
|
+
def initialize(error)
|
26
|
+
@error = error
|
18
27
|
end
|
19
28
|
|
20
|
-
def build
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
29
|
+
def build
|
30
|
+
parse_original_response
|
31
|
+
return if bad_class_name?
|
32
|
+
error_class.new(error_message, error)
|
25
33
|
end
|
26
34
|
|
27
35
|
private
|
28
36
|
|
29
|
-
def
|
30
|
-
|
37
|
+
def bad_class_name?
|
38
|
+
error_name =~ DIGIT_AS_FIRST_CHAR
|
39
|
+
end
|
40
|
+
|
41
|
+
def error_class
|
42
|
+
Errors.const_get(error_name)
|
43
|
+
rescue NameError
|
44
|
+
ClassGenerator.call(error_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def error_name
|
48
|
+
response.code
|
49
|
+
end
|
50
|
+
|
51
|
+
def error_message
|
52
|
+
response.message
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_original_response
|
56
|
+
error.instance_variable_set :@response, Parser.new(error.response)
|
31
57
|
end
|
32
58
|
end
|
33
59
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'peddler/errors/error'
|
5
|
+
|
6
|
+
module Peddler
|
7
|
+
module Errors
|
8
|
+
# @api private
|
9
|
+
class ClassGenerator
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
def self.call(name)
|
13
|
+
instance.generate(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@mutex = Mutex.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate(name)
|
21
|
+
with_mutex do
|
22
|
+
return Errors.const_get(name) if Errors.const_defined?(name)
|
23
|
+
Errors.const_set(name, Class.new(Error))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def with_mutex
|
30
|
+
@mutex.synchronize { yield }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/peddler/errors/error.rb
CHANGED
@@ -1,27 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
4
|
+
|
3
5
|
module Peddler
|
4
6
|
# @api private
|
5
7
|
module Errors
|
6
|
-
#
|
7
|
-
#
|
8
|
+
# These error codes are common to all Amazon MWS API sections.
|
9
|
+
#
|
10
|
+
# @see https://docs.developer.amazonservices.com/en_US/dev_guide/DG_Errors.html
|
8
11
|
CODES = %w[
|
9
12
|
AccessDenied
|
13
|
+
InputStreamDisconnected
|
10
14
|
InternalError
|
11
15
|
InvalidAccessKeyId
|
12
|
-
|
16
|
+
InvalidAddress
|
17
|
+
InvalidParameterValue
|
13
18
|
QuotaExceeded
|
14
19
|
RequestThrottled
|
20
|
+
SignatureDoesNotMatch
|
15
21
|
].freeze
|
16
22
|
|
17
23
|
# @api private
|
18
24
|
class Error < StandardError
|
25
|
+
extend Forwardable
|
26
|
+
|
19
27
|
attr_reader :cause
|
20
28
|
|
21
29
|
def initialize(msg = nil, cause = nil)
|
22
30
|
@cause = cause
|
23
31
|
super msg
|
24
32
|
end
|
33
|
+
|
34
|
+
def_delegator :cause, :response
|
25
35
|
end
|
26
36
|
|
27
37
|
CODES.each do |name|
|
data/lib/peddler/headers.rb
CHANGED
@@ -3,27 +3,43 @@
|
|
3
3
|
module Peddler
|
4
4
|
# Parses MWS-specific headers
|
5
5
|
module Headers
|
6
|
-
|
6
|
+
# The max hourly request quota for the requested operation
|
7
|
+
# @return [Integer, nil]
|
8
|
+
def mws_quota_max
|
9
|
+
return unless headers['x-mws-quota-max']
|
10
|
+
headers['x-mws-quota-max'].to_i
|
11
|
+
end
|
7
12
|
|
8
|
-
|
9
|
-
|
13
|
+
# The remaining hourly request quota for the requested operation
|
14
|
+
# @return [Integer, nil]
|
15
|
+
def mws_quota_remaining
|
16
|
+
return unless headers['x-mws-quota-remaining']
|
17
|
+
headers['x-mws-quota-remaining'].to_i
|
18
|
+
end
|
10
19
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
)
|
20
|
+
# When the hourly request quota for the requested operation resets
|
21
|
+
# @return [Time, nil]
|
22
|
+
def mws_quota_resets_on
|
23
|
+
return unless headers['x-mws-quota-resetsOn']
|
24
|
+
Time.parse(headers['x-mws-quota-resetsOn'])
|
16
25
|
end
|
17
26
|
|
18
|
-
|
27
|
+
# The ID of the request
|
28
|
+
# @return [String, nil]
|
29
|
+
def mws_request_id
|
19
30
|
headers['x-mws-request-id']
|
20
31
|
end
|
21
32
|
|
22
|
-
|
33
|
+
# The timestamp of the request
|
34
|
+
# @return [Time, nil]
|
35
|
+
def mws_timestamp
|
36
|
+
return unless headers['x-mws-timestamp']
|
23
37
|
Time.parse(headers['x-mws-timestamp'])
|
24
38
|
end
|
25
39
|
|
26
|
-
|
40
|
+
# The context of the response
|
41
|
+
# @return [String, nil]
|
42
|
+
def mws_response_context
|
27
43
|
headers['x-mws-response-context']
|
28
44
|
end
|
29
45
|
end
|
data/lib/peddler/marketplace.rb
CHANGED
@@ -7,16 +7,35 @@ module Peddler
|
|
7
7
|
class << self
|
8
8
|
attr_reader :all
|
9
9
|
|
10
|
-
def find(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
def find(key)
|
11
|
+
marketplace = if key.nil?
|
12
|
+
missing_key!
|
13
|
+
elsif key.size == 2
|
14
|
+
find_by_country_code(key)
|
15
|
+
else
|
16
|
+
find_by_id(key)
|
17
|
+
end
|
18
|
+
|
19
|
+
marketplace || not_found!(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def find_by_country_code(country_code)
|
25
|
+
country_code = 'GB' if country_code == 'UK'
|
26
|
+
all.find { |marketplace| marketplace.country_code == country_code }
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_by_id(id)
|
30
|
+
all.find { |marketplace| marketplace.id == id }
|
31
|
+
end
|
32
|
+
|
33
|
+
def missing_key!
|
34
|
+
raise ArgumentError, 'missing marketplace'
|
35
|
+
end
|
36
|
+
|
37
|
+
def not_found!(country_code)
|
38
|
+
raise ArgumentError, %("#{country_code}" is not a valid marketplace)
|
20
39
|
end
|
21
40
|
end
|
22
41
|
|
@@ -44,7 +63,7 @@ module Peddler
|
|
44
63
|
['A1RKKUPIHCS9HS', 'ES', 'mws-eu.amazonservices.com'],
|
45
64
|
['A13V1IB3VIYZZH', 'FR', 'mws-eu.amazonservices.com'],
|
46
65
|
['APJ6JRA9NG5V4', 'IT', 'mws-eu.amazonservices.com'],
|
47
|
-
['A1F83G8C2ARO7P', '
|
66
|
+
['A1F83G8C2ARO7P', 'GB', 'mws-eu.amazonservices.com'],
|
48
67
|
['A21TJRUUN4KGV', 'IN', 'mws.amazonservices.in'],
|
49
68
|
['A39IBJ37TRP1C6', 'AU', 'mws.amazonservices.com.au'],
|
50
69
|
['A1VC38T7YXB528', 'JP', 'mws.amazonservices.jp'],
|
data/lib/peddler/vcr_matcher.rb
CHANGED
@@ -2,36 +2,46 @@
|
|
2
2
|
|
3
3
|
module Peddler
|
4
4
|
# A custom matcher that can be used to record MWS interactions when
|
5
|
-
# integration
|
5
|
+
# writing integration tests
|
6
6
|
class VCRMatcher
|
7
|
+
# @api private
|
7
8
|
TRANSIENT_PARAMS = %w[
|
8
9
|
Signature Timestamp StartDate CreatedAfter QueryStartDateTime
|
9
10
|
].freeze
|
10
11
|
|
12
|
+
# @api private
|
11
13
|
SELLER_PARAMS = %w[
|
12
14
|
AWSAccessKeyId SellerId
|
13
15
|
].freeze
|
14
16
|
|
15
17
|
class << self
|
18
|
+
# @api private
|
16
19
|
def call(*requests)
|
17
20
|
new(*requests).compare
|
18
21
|
end
|
19
22
|
|
23
|
+
# @api private
|
20
24
|
def ignored_params
|
21
25
|
@ignored_params ||= TRANSIENT_PARAMS.dup
|
22
26
|
end
|
23
27
|
|
28
|
+
# Ignore seller specific attributes when recording
|
29
|
+
# @return [void]
|
24
30
|
def ignore_seller!
|
25
31
|
ignored_params.concat(SELLER_PARAMS)
|
32
|
+
ignored_params.uniq!
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
36
|
+
# @api private
|
29
37
|
attr_reader :requests
|
30
38
|
|
39
|
+
# @api private
|
31
40
|
def initialize(*requests)
|
32
41
|
@requests = requests
|
33
42
|
end
|
34
43
|
|
44
|
+
# @api private
|
35
45
|
def compare
|
36
46
|
compare_uris && compare_bodies
|
37
47
|
end
|
data/lib/peddler/version.rb
CHANGED
data/lib/peddler/xml_parser.rb
CHANGED
@@ -11,12 +11,14 @@ module Peddler
|
|
11
11
|
extend Forwardable
|
12
12
|
include Headers
|
13
13
|
|
14
|
-
def_delegator :parse, :dig
|
14
|
+
def_delegator :parse, :dig
|
15
15
|
|
16
|
-
def
|
16
|
+
def data
|
17
17
|
@data ||= find_data
|
18
18
|
end
|
19
19
|
|
20
|
+
alias parse data
|
21
|
+
|
20
22
|
def xml
|
21
23
|
MultiXml.parse(body)
|
22
24
|
end
|
data/test/helper.rb
CHANGED
@@ -2,25 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'integration_helper'
|
4
4
|
require 'mws/orders'
|
5
|
-
require 'peddler/errors/handler'
|
6
5
|
|
7
6
|
class TestErrors < IntegrationTest
|
8
7
|
use 'Orders'
|
9
8
|
|
10
|
-
def setup
|
11
|
-
@previous_error_handler = MWS::Orders::Client.error_handler
|
12
|
-
MWS::Orders::Client.error_handler = Peddler::Errors::Handler
|
13
|
-
super
|
14
|
-
end
|
15
|
-
|
16
|
-
def teardown
|
17
|
-
MWS::Orders::Client.error_handler = @previous_error_handler
|
18
|
-
super
|
19
|
-
end
|
20
|
-
|
21
9
|
def test_invalid_key
|
22
10
|
clients.each do |client|
|
23
|
-
assert_raises
|
11
|
+
assert_raises Peddler::Errors::InvalidAccessKeyId do
|
24
12
|
client.aws_access_key_id = 'foo'
|
25
13
|
client.get_order('bar')
|
26
14
|
end
|
@@ -29,7 +17,7 @@ class TestErrors < IntegrationTest
|
|
29
17
|
|
30
18
|
def test_request_throttled
|
31
19
|
clients.each do |client|
|
32
|
-
assert_raises
|
20
|
+
assert_raises Peddler::Errors::RequestThrottled do
|
33
21
|
client.get_order('foo')
|
34
22
|
end
|
35
23
|
end
|
@@ -27,9 +27,6 @@ class TestFeeds < IntegrationTest
|
|
27
27
|
feed_submission_id = res.dig('FeedSubmissionInfo', 'FeedSubmissionId')
|
28
28
|
assert feed_submission_id
|
29
29
|
|
30
|
-
res = client.get_feed_submission_result(feed_submission_id)
|
31
|
-
assert res.records_count
|
32
|
-
|
33
30
|
# Clean up
|
34
31
|
client.cancel_feed_submissions(
|
35
32
|
feed_submission_id: feed_submission_id,
|
@@ -8,7 +8,7 @@ class TestMultibyteQueries < IntegrationTest
|
|
8
8
|
|
9
9
|
def test_posts_multibyte_queries_properly
|
10
10
|
ret = clients.map do |client|
|
11
|
-
res = client.list_matching_products('félix guattari machinic eros')
|
11
|
+
res = client.list_matching_products(client.marketplace.id, 'félix guattari machinic eros')
|
12
12
|
res.body.force_encoding 'UTF-8' if defined? Ox # Ox workaround
|
13
13
|
res.parse
|
14
14
|
end
|
@@ -8,8 +8,9 @@ class TestMWSHeaders < IntegrationTest
|
|
8
8
|
|
9
9
|
def test_parses_headers
|
10
10
|
clients.each do |client|
|
11
|
-
res = client.get_lowest_priced_offers_for_asin(
|
12
|
-
|
11
|
+
res = client.get_lowest_priced_offers_for_asin(client.marketplace.id,
|
12
|
+
'1780935374', 'New')
|
13
|
+
refute_nil res.mws_quota_max
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
@@ -6,7 +6,8 @@ require 'mws/orders'
|
|
6
6
|
class TestOrders < IntegrationTest
|
7
7
|
def test_gets_orders
|
8
8
|
clients.each do |client|
|
9
|
-
order_ids = client.list_orders(
|
9
|
+
order_ids = client.list_orders(client.marketplace.id,
|
10
|
+
created_after: Date.new(2015),
|
10
11
|
max_results_per_page: 5)
|
11
12
|
.dig('Orders', 'Order')
|
12
13
|
.map { |order| order['AmazonOrderId'] }
|
@@ -6,49 +6,49 @@ require 'mws/products'
|
|
6
6
|
class TestProducts < IntegrationTest
|
7
7
|
def test_lists_matching_products
|
8
8
|
clients.each do |client|
|
9
|
-
res = client.list_matching_products('architecture')
|
9
|
+
res = client.list_matching_products(client.marketplace.id, 'architecture')
|
10
10
|
refute_empty res.parse
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_gets_matching_product
|
15
15
|
clients.each do |client|
|
16
|
-
res = client.get_matching_product('1780935374')
|
16
|
+
res = client.get_matching_product(client.marketplace.id, '1780935374')
|
17
17
|
refute_empty res.parse
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_gets_matching_product_for_id
|
22
22
|
clients.each do |client|
|
23
|
-
res = client.get_matching_product_for_id('ISBN', '9781780935379')
|
23
|
+
res = client.get_matching_product_for_id(client.marketplace.id, 'ISBN', '9781780935379')
|
24
24
|
refute_empty res.parse
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def test_gets_competitive_pricing_for_asin
|
29
29
|
clients.each do |client|
|
30
|
-
res = client.get_competitive_pricing_for_asin('1780935374')
|
30
|
+
res = client.get_competitive_pricing_for_asin(client.marketplace.id, '1780935374')
|
31
31
|
refute_empty res.parse
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def test_gets_lowest_offer_listings_for_asin
|
36
36
|
clients.each do |client|
|
37
|
-
res = client.get_lowest_offer_listings_for_asin('1780935374')
|
37
|
+
res = client.get_lowest_offer_listings_for_asin(client.marketplace.id, '1780935374')
|
38
38
|
refute_empty res.parse
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_gets_lowest_priced_offers_for_asin
|
43
43
|
clients.each do |client|
|
44
|
-
res = client.get_lowest_priced_offers_for_asin('1780935374', 'New')
|
44
|
+
res = client.get_lowest_priced_offers_for_asin(client.marketplace.id, '1780935374', 'New')
|
45
45
|
refute_empty res.parse
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
49
|
def test_gets_product_categories_for_asin
|
50
50
|
clients.each do |client|
|
51
|
-
res = client.get_product_categories_for_asin('1780935374')
|
51
|
+
res = client.get_product_categories_for_asin(client.marketplace.id, '1780935374')
|
52
52
|
refute_empty res.parse
|
53
53
|
end
|
54
54
|
end
|
@@ -56,12 +56,12 @@ class TestProducts < IntegrationTest
|
|
56
56
|
def test_gets_my_fees_estimate
|
57
57
|
clients.each do |client|
|
58
58
|
res = client.get_my_fees_estimate(
|
59
|
-
marketplace_id: client.
|
59
|
+
marketplace_id: client.marketplace.id,
|
60
60
|
id_type: 'ASIN',
|
61
61
|
id_value: '1780935374',
|
62
62
|
price_to_estimate_fees: {
|
63
63
|
listing_price: {
|
64
|
-
currency_code: currency_code_for(client.
|
64
|
+
currency_code: currency_code_for(client.marketplace.id),
|
65
65
|
amount: 100
|
66
66
|
}
|
67
67
|
},
|