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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -67
  3. data/lib/mws/feeds/client.rb +15 -12
  4. data/lib/mws/finances/client.rb +12 -11
  5. data/lib/mws/fulfillment_inbound_shipment/client.rb +79 -107
  6. data/lib/mws/fulfillment_inventory/client.rb +5 -5
  7. data/lib/mws/fulfillment_outbound_shipment/client.rb +36 -44
  8. data/lib/mws/merchant_fulfillment/client.rb +11 -17
  9. data/lib/mws/off_amazon_payments/client.rb +38 -68
  10. data/lib/mws/orders/client.rb +28 -24
  11. data/lib/mws/products/client.rb +118 -153
  12. data/lib/mws/recommendations/client.rb +13 -17
  13. data/lib/mws/reports/client.rb +24 -23
  14. data/lib/mws/sellers/client.rb +5 -5
  15. data/lib/mws/subscriptions/client.rb +25 -36
  16. data/lib/peddler/client.rb +47 -124
  17. data/lib/peddler/errors/builder.rb +40 -14
  18. data/lib/peddler/errors/class_generator.rb +34 -0
  19. data/lib/peddler/errors/error.rb +13 -3
  20. data/lib/peddler/headers.rb +27 -11
  21. data/lib/peddler/marketplace.rb +30 -11
  22. data/lib/peddler/vcr_matcher.rb +11 -1
  23. data/lib/peddler/version.rb +1 -1
  24. data/lib/peddler/xml_parser.rb +4 -2
  25. data/lib/peddler/xml_response_parser.rb +1 -1
  26. data/test/helper.rb +0 -1
  27. data/test/integration/test_errors.rb +2 -14
  28. data/test/integration/test_feeds.rb +0 -3
  29. data/test/integration/test_multibyte_queries.rb +1 -1
  30. data/test/integration/test_mws_headers.rb +3 -2
  31. data/test/integration/test_orders.rb +2 -1
  32. data/test/integration/test_products.rb +9 -9
  33. data/test/integration/test_recommendations.rb +1 -1
  34. data/test/integration/test_subscriptions.rb +2 -2
  35. data/test/integration_helper.rb +1 -1
  36. data/test/mws.yml +36 -0
  37. data/test/mws.yml.example +8 -12
  38. data/test/null_client.rb +10 -8
  39. data/test/unit/mws/test_feeds_client.rb +1 -2
  40. data/test/unit/mws/test_fulfillment_outbound_shipment_client.rb +1 -1
  41. data/test/unit/mws/test_off_amazon_payments_client.rb +1 -1
  42. data/test/unit/mws/test_orders_client.rb +7 -6
  43. data/test/unit/mws/test_products_client.rb +13 -28
  44. data/test/unit/mws/test_recommendations_client.rb +1 -2
  45. data/test/unit/mws/test_reports_client.rb +1 -1
  46. data/test/unit/mws/test_subscriptions_client.rb +1 -130
  47. data/test/unit/peddler/errors/test_builder.rb +54 -7
  48. data/test/unit/peddler/errors/test_class_generator.rb +18 -0
  49. data/test/unit/peddler/errors/test_error.rb +7 -2
  50. data/test/unit/peddler/test_client.rb +136 -190
  51. data/test/unit/peddler/test_flat_file_parser.rb +2 -2
  52. data/test/unit/peddler/test_headers.rb +19 -9
  53. data/test/unit/peddler/test_marketplace.rb +18 -5
  54. data/test/unit/peddler/test_vcr_matcher.rb +3 -1
  55. data/test/vcr_cassettes/Feeds.yml +4816 -5224
  56. data/test/vcr_cassettes/Reports.yml +3278 -2604
  57. metadata +8 -9
  58. data/lib/peddler/errors.rb +0 -12
  59. data/lib/peddler/errors/handler.rb +0 -59
  60. data/test/unit/peddler/errors/test_handler.rb +0 -62
  61. data/test/unit/peddler/test_errors.rb +0 -26
@@ -4,15 +4,62 @@ require 'helper'
4
4
  require 'peddler/errors/builder'
5
5
 
6
6
  class TestPeddlerErrorsBuilder < MiniTest::Test
7
- def test_builds_error_class
8
- Peddler::Errors::Builder.build('Foo')
9
- assert Peddler::Errors::Foo
7
+ def setup
8
+ @error = Peddler::Errors::Builder.call(@cause)
10
9
  end
11
10
 
12
- def test_thread_safety
13
- Peddler::Errors::Builder.build('Foo')
14
- assert_output '', '' do
15
- Peddler::Errors::Builder.build('Foo')
11
+ class CausedByHTTPStatusError < TestPeddlerErrorsBuilder
12
+ def setup
13
+ @code = 'FeedProcessingResultNotReady'
14
+ @message = 'Feed Submission Result is not ready for Feed 123'
15
+ body = <<-XML
16
+ <ErrorResponse>
17
+ <Error>
18
+ <Code>#{@code}</Code>
19
+ <Message>#{@message}</Message>
20
+ </Error>
21
+ </ErrorResponse>
22
+ XML
23
+ @cause = Excon::Error::NotFound.new(
24
+ 'Expected(200) <=> Actual(404 Not Found)',
25
+ nil,
26
+ OpenStruct.new(body: body)
27
+ )
28
+ super
29
+ end
30
+
31
+ def test_generates_custom_error
32
+ assert_includes @error.class.name, @code
33
+ end
34
+
35
+ def test_provides_message
36
+ assert_equal @message, @error.message
37
+ end
38
+
39
+ def test_provides_cause
40
+ assert_equal @cause, @error.cause
41
+ end
42
+ end
43
+
44
+ class CausedByInternalServerError < TestPeddlerErrorsBuilder
45
+ def setup
46
+ body = <<-XML
47
+ <ErrorResponse>
48
+ <Error>
49
+ <Code>500</Code>
50
+ </Error>
51
+ </ErrorResponse>
52
+ XML
53
+ @cause = Excon::Error::InternalServerError.new(
54
+ nil,
55
+ nil,
56
+ OpenStruct.new(body: body)
57
+ )
58
+ super
59
+ end
60
+
61
+ def test_returns_nothing
62
+ assert_nil @error
16
63
  end
17
64
  end
18
65
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'helper'
4
+ require 'peddler/errors/class_generator'
5
+
6
+ class TestPeddlerErrorsClassGenerator < MiniTest::Test
7
+ def test_builds_error_class
8
+ Peddler::Errors::ClassGenerator.call('Foo')
9
+ assert Peddler::Errors::Foo
10
+ end
11
+
12
+ def test_thread_safety
13
+ Peddler::Errors::ClassGenerator.call('Foo')
14
+ assert_output '', '' do
15
+ Peddler::Errors::ClassGenerator.call('Foo')
16
+ end
17
+ end
18
+ end
@@ -5,7 +5,8 @@ require 'peddler/errors/error'
5
5
 
6
6
  class TestPeddlerErrorsError < MiniTest::Test
7
7
  def setup
8
- @error = Peddler::Errors::Error.new('message', 'cause')
8
+ @cause = OpenStruct.new(response: 'response')
9
+ @error = Peddler::Errors::Error.new('message', @cause)
9
10
  end
10
11
 
11
12
  def test_sets_message
@@ -13,7 +14,7 @@ class TestPeddlerErrorsError < MiniTest::Test
13
14
  end
14
15
 
15
16
  def test_sets_cause
16
- assert_equal 'cause', @error.cause
17
+ assert_equal @cause, @error.cause
17
18
  end
18
19
 
19
20
  def test_defines_common_errors
@@ -25,4 +26,8 @@ class TestPeddlerErrorsError < MiniTest::Test
25
26
  def test_allows_nil_arguments
26
27
  Peddler::Errors::Error.new
27
28
  end
29
+
30
+ def test_delegates_response_to_cause
31
+ assert_equal @cause.response, @error.response
32
+ end
28
33
  end
@@ -5,10 +5,7 @@ require 'null_client'
5
5
 
6
6
  class TestPeddlerClient < MiniTest::Test
7
7
  def setup
8
- @response_body = 'foo'
9
8
  Excon.defaults[:mock] = true
10
- Excon.stub({}, body: @response_body, status: 200)
11
-
12
9
  @klass = Class.new(Null::Client)
13
10
  @client = @klass.new
14
11
  @client.configure_with_mock_data!
@@ -20,238 +17,187 @@ class TestPeddlerClient < MiniTest::Test
20
17
  Excon.defaults.delete(:mock)
21
18
  end
22
19
 
23
- def test_configures_path
24
- @klass.path('/Foo')
25
- assert @client.aws_endpoint.match(%r{/Foo$})
26
- end
27
-
28
- def test_instance_path_overrides_class_path
29
- @klass.path('/Foo')
30
-
31
- @client.path = '/Foo/Bar'
32
- assert @client.aws_endpoint.match(%r{/Foo/Bar$})
33
- end
34
-
35
- def test_default_path
36
- assert_equal '/', @klass.path
37
- end
38
-
39
- def test_has_user_agent
40
- assert @client.connection.data[:headers].key?('User-Agent')
41
- end
42
-
43
- def test_inherits_parents_params
44
- assert_equal Peddler::Client.params, @klass.params
45
- end
46
-
47
- def test_inherits_parents_path
48
- assert_equal @klass.path, Class.new(@klass).path
49
- end
20
+ class HappyPath < TestPeddlerClient
21
+ def setup
22
+ @response_body = 'foo'
23
+ Excon.stub({}, body: @response_body, status: 200)
24
+ super
25
+ end
50
26
 
51
- def test_inherits_parents_parser
52
- assert_equal @klass.parser, Class.new(@klass).parser
53
- end
27
+ def test_has_user_agent
28
+ assert @client.connection.data[:headers].key?('User-Agent')
29
+ end
54
30
 
55
- def test_params_include_seller_id
56
- assert @klass.params.key?('SellerId')
57
- end
31
+ def test_inherits_parents_params
32
+ assert_equal Peddler::Client.params, @klass.params
33
+ end
58
34
 
59
- def test_params_include_auth_token
60
- @klass.params.key?('MWSAuthToken')
61
- end
35
+ def test_params_include_seller_id
36
+ assert @klass.params.key?('SellerId')
37
+ end
62
38
 
63
- def test_configures
64
- @client.configure do |config|
65
- config.aws_access_key_id = '123'
39
+ def test_params_include_auth_token
40
+ @klass.params.key?('MWSAuthToken')
66
41
  end
67
42
 
68
- assert_equal '123', @client.aws_access_key_id
69
- end
43
+ def test_inherits_parents_parser
44
+ assert_equal @klass.parser, Class.new(@klass).parser
45
+ end
70
46
 
71
- def test_configures_when_initialising
72
- client = @klass.new(aws_access_key_id: '123')
73
- assert_equal '123', client.aws_access_key_id
74
- end
47
+ def test_sets_marketplace
48
+ marketplace = Peddler::Marketplace.find('US')
49
+ @client.marketplace = 'US'
50
+ assert_equal marketplace, @client.marketplace
51
+ @client.marketplace = marketplace
52
+ assert_equal marketplace, @client.marketplace
53
+ end
75
54
 
76
- def test_sets_content_type_header_for_latin_flat_file
77
- @client.body = 'foo'
78
- content_type = @client.headers.fetch('Content-Type')
55
+ def test_configures_when_initialising
56
+ client = @klass.new(aws_access_key_id: '123')
57
+ assert_equal '123', client.aws_access_key_id
58
+ end
79
59
 
80
- assert_equal 'text/tab-separated-values; charset=CP1252', content_type
81
- end
60
+ def test_sets_content_type_header_for_latin_flat_file
61
+ @client.body = 'foo'
62
+ content_type = @client.headers.fetch('Content-Type')
82
63
 
83
- def test_sets_content_type_header_for_chinese_flat_file
84
- @client.primary_marketplace_id = 'AAHKV2X7AFYLW'
85
- @client.body = 'foo'
86
- content_type = @client.headers.fetch('Content-Type')
64
+ assert_equal 'text/tab-separated-values; charset=CP1252', content_type
65
+ end
87
66
 
88
- assert_equal 'text/tab-separated-values; charset=UTF-16', content_type
89
- end
67
+ def test_sets_content_type_header_for_chinese_flat_file
68
+ @client.marketplace = 'CN'
69
+ @client.body = 'foo'
70
+ content_type = @client.headers.fetch('Content-Type')
90
71
 
91
- def test_sets_content_type_header_for_japanese_flat_file
92
- @client.primary_marketplace_id = 'A1VC38T7YXB528'
93
- @client.body = 'foo'
94
- content_type = @client.headers.fetch('Content-Type')
72
+ assert_equal 'text/tab-separated-values; charset=UTF-16', content_type
73
+ end
95
74
 
96
- assert_equal 'text/tab-separated-values; charset=Windows-31J', content_type
97
- end
75
+ def test_sets_content_type_header_for_japanese_flat_file
76
+ @client.marketplace = 'JP'
77
+ @client.body = 'foo'
78
+ content_type = @client.headers.fetch('Content-Type')
98
79
 
99
- def test_sets_content_type_header_for_xml
100
- @client.body = '<?xml version="1.0"?><Foo></Foo>'
101
- content_type = @client.headers.fetch('Content-Type')
80
+ assert_equal 'text/tab-separated-values; charset=Windows-31J', content_type
81
+ end
102
82
 
103
- assert_equal 'text/xml', content_type
104
- end
83
+ def test_sets_content_type_header_for_xml
84
+ @client.body = '<?xml version="1.0"?><Foo></Foo>'
85
+ content_type = @client.headers.fetch('Content-Type')
105
86
 
106
- def test_encodes_body_for_latin_flat_file
107
- @client.body = 'foo'
108
- assert_equal 'Windows-1252', @client.body.encoding.to_s
109
- end
87
+ assert_equal 'text/xml', content_type
88
+ end
110
89
 
111
- def test_encodes_body_for_chinese_flat_file
112
- @client.primary_marketplace_id = 'AAHKV2X7AFYLW'
113
- @client.body = 'foo'
114
- assert_equal 'UTF-16', @client.body.encoding.to_s
115
- end
90
+ def test_encodes_body_for_latin_flat_file
91
+ @client.body = 'foo'
92
+ assert_equal 'Windows-1252', @client.body.encoding.to_s
93
+ end
116
94
 
117
- def test_encodes_body_for_japanese_flat_file
118
- @client.primary_marketplace_id = 'A1VC38T7YXB528'
119
- @client.body = 'foo'
120
- assert_equal 'Windows-31J', @client.body.encoding.to_s
121
- end
95
+ def test_encodes_body_for_chinese_flat_file
96
+ @client.marketplace = 'CN'
97
+ @client.body = 'foo'
98
+ assert_equal 'UTF-16', @client.body.encoding.to_s
99
+ end
122
100
 
123
- def test_runs_a_request
124
- res = @client.run
125
- assert_equal @response_body, res.body
126
- end
101
+ def test_encodes_body_for_japanese_flat_file
102
+ @client.marketplace = 'JP'
103
+ @client.body = 'foo'
104
+ assert_equal 'Windows-31J', @client.body.encoding.to_s
105
+ end
127
106
 
128
- def test_clears_body_when_run_succeeds
129
- @client.body = 'foo'
130
- @client.run
131
- assert_nil @client.body
132
- end
107
+ def test_runs_a_request
108
+ res = @client.run
109
+ assert_equal @response_body, res.body
110
+ end
133
111
 
134
- def test_does_not_clear_body_when_run_fails
135
- Excon.stub({}, status: 503)
136
- @client.body = 'foo'
137
- assert_raises(Excon::Error::ServiceUnavailable) do
112
+ def test_clears_body_when_run_succeeds
113
+ @client.body = 'foo'
138
114
  @client.run
115
+ assert_nil @client.body
139
116
  end
140
- refute_nil @client.body
141
- end
142
-
143
- def test_streams_response
144
- chunks = ''
145
- streamer = ->(chunk, _, _) { chunks += chunk }
146
- @client.run(&streamer)
147
-
148
- assert_equal @response_body, chunks
149
- end
150
117
 
151
- class Instrumentor
152
- class << self
153
- attr_accessor :events
118
+ def test_streams_response
119
+ chunks = ''
120
+ streamer = ->(chunk, _, _) { chunks += chunk }
121
+ @client.run(&streamer)
154
122
 
155
- def instrument(name, params = {})
156
- events.update(name => params)
157
- yield if block_given?
158
- end
123
+ assert_equal @response_body, chunks
159
124
  end
160
125
 
161
- @events = {}
162
- end
126
+ class Instrumentor
127
+ class << self
128
+ attr_accessor :events
163
129
 
164
- def test_request_preserves_user_agent
165
- @client.defaults.update(instrumentor: Instrumentor)
166
- @client.run
167
- headers = Instrumentor.events['excon.request'][:headers]
168
-
169
- assert headers.key?('User-Agent')
170
- end
130
+ def instrument(name, params = {})
131
+ events.update(name => params)
132
+ yield if block_given?
133
+ end
134
+ end
171
135
 
172
- def test_error_callback_on_class
173
- Excon.stub({}, status: 503)
136
+ @events = {}
137
+ end
174
138
 
175
- assert_raises(Excon::Error::ServiceUnavailable) do
139
+ def test_request_preserves_user_agent
140
+ @client.defaults.update(instrumentor: Instrumentor)
176
141
  @client.run
177
- end
142
+ headers = Instrumentor.events['excon.request'][:headers]
178
143
 
179
- @klass.on_error do |e|
180
- assert_equal 503, e.response.status
144
+ assert headers.key?('User-Agent')
181
145
  end
182
- @client.run # no longer raises
183
-
184
- Excon.stubs.clear
185
146
  end
186
147
 
187
- def test_error_callback_on_instance
188
- Excon.stub({}, status: 503)
189
-
190
- assert_raises(Excon::Error::ServiceUnavailable) do
191
- @client.run
148
+ class MWSErrorPath < TestPeddlerClient
149
+ def setup
150
+ body = <<-XML
151
+ <ErrorResponse>
152
+ <Error>
153
+ <Code>RequestThrottled</Code>
154
+ </Error>
155
+ </ErrorResponse>
156
+ XML
157
+ Excon.stub({}, body: body, status: 503)
158
+ super
192
159
  end
193
160
 
194
- @client.on_error do |e|
195
- assert_equal 503, e.response.status
161
+ def test_default_error_handling
162
+ assert_raises Peddler::Errors::RequestThrottled do
163
+ @client.run
164
+ end
196
165
  end
197
- @client.run
198
166
 
199
- Excon.stubs.clear
200
- end
201
-
202
- def test_error_callback_on_client_ancestor
203
- Excon.stub({}, status: 503)
204
-
205
- @klass.on_error do |e|
206
- assert_equal 503, e.response.status
207
- end
208
- @client.run # no longer raises
209
-
210
- klass = Class.new(Null::Client)
211
- other_client = klass.new
212
- other_client.configure_with_mock_data!
213
- other_client.operation('Foo')
214
- assert_raises(Excon::Error::ServiceUnavailable) do
215
- other_client.run
167
+ def test_does_not_clear_body_when_run_fails
168
+ @client.body = 'foo'
169
+ assert_raises Peddler::Errors::RequestThrottled do
170
+ @client.run
171
+ end
172
+ refute_nil @client.body
216
173
  end
217
-
218
- Excon.stubs.clear
219
174
  end
220
175
 
221
- def test_decorates_error_response
222
- res = {
223
- body: '<ErrorResponse><Error>Foo</Error></ErrorResponse>',
224
- status: 503
225
- }
226
- Excon.stub({}, res)
227
- e = nil
228
-
229
- begin
230
- @client.run
231
- rescue StandardError => e
232
- assert e.response.parse
176
+ class OtherHTTPStatusErrorPath < TestPeddlerClient
177
+ def setup
178
+ body = <<-XML
179
+ <ErrorResponse>
180
+ <Error>
181
+ <Code>500</Code>
182
+ </Error>
183
+ </ErrorResponse>
184
+ XML
185
+ Excon.stub({}, body: body, status: 500)
186
+ super
233
187
  end
234
188
 
235
- assert e
236
- end
237
-
238
- def test_deprecated_error_callback
239
- Excon.stub({}, status: 503)
240
-
241
- @client.on_error do |_, res|
242
- assert_equal 503, res.status
243
- end
244
- assert_output nil, /DEPRECATION/ do
245
- @client.run
189
+ def test_error_handling
190
+ assert_raises Excon::Error::InternalServerError do
191
+ @client.run
192
+ end
246
193
  end
247
194
 
248
- Excon.stubs.clear
249
- end
250
-
251
- def test_deprecated_marketplace_id_accessor
252
- refute_nil @client.marketplace_id
253
- @client.marketplace_id = '123'
254
- assert_equal '123', @client.marketplace_id
255
- assert_equal @client.primary_marketplace_id, @client.marketplace_id
195
+ def test_does_not_clear_body_when_run_fails
196
+ @client.body = 'foo'
197
+ assert_raises Excon::Error::InternalServerError do
198
+ @client.run
199
+ end
200
+ refute_nil @client.body
201
+ end
256
202
  end
257
203
  end