mws-connect 0.0.1
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.
- data/.gitignore +19 -0
- data/.sublime-project +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +111 -0
- data/Rakefile +1 -0
- data/lib/mws.rb +34 -0
- data/lib/mws/apis.rb +6 -0
- data/lib/mws/apis/feeds.rb +20 -0
- data/lib/mws/apis/feeds/api.rb +103 -0
- data/lib/mws/apis/feeds/distance.rb +23 -0
- data/lib/mws/apis/feeds/feed.rb +114 -0
- data/lib/mws/apis/feeds/image_listing.rb +44 -0
- data/lib/mws/apis/feeds/inventory.rb +77 -0
- data/lib/mws/apis/feeds/measurement.rb +32 -0
- data/lib/mws/apis/feeds/money.rb +31 -0
- data/lib/mws/apis/feeds/price_listing.rb +48 -0
- data/lib/mws/apis/feeds/product.rb +173 -0
- data/lib/mws/apis/feeds/sale_price.rb +31 -0
- data/lib/mws/apis/feeds/shipping.rb +160 -0
- data/lib/mws/apis/feeds/submission_info.rb +45 -0
- data/lib/mws/apis/feeds/submission_result.rb +87 -0
- data/lib/mws/apis/feeds/transaction.rb +37 -0
- data/lib/mws/apis/feeds/weight.rb +19 -0
- data/lib/mws/apis/orders.rb +23 -0
- data/lib/mws/connection.rb +84 -0
- data/lib/mws/enum.rb +81 -0
- data/lib/mws/errors.rb +32 -0
- data/lib/mws/query.rb +45 -0
- data/lib/mws/serializer.rb +81 -0
- data/lib/mws/signer.rb +20 -0
- data/lib/mws/utils.rb +50 -0
- data/mws.gemspec +25 -0
- data/scripts/catalog-workflow +136 -0
- data/spec/mws/apis/feeds/api_spec.rb +229 -0
- data/spec/mws/apis/feeds/distance_spec.rb +43 -0
- data/spec/mws/apis/feeds/feed_spec.rb +92 -0
- data/spec/mws/apis/feeds/image_listing_spec.rb +109 -0
- data/spec/mws/apis/feeds/inventory_spec.rb +135 -0
- data/spec/mws/apis/feeds/measurement_spec.rb +84 -0
- data/spec/mws/apis/feeds/money_spec.rb +43 -0
- data/spec/mws/apis/feeds/price_listing_spec.rb +90 -0
- data/spec/mws/apis/feeds/product_spec.rb +264 -0
- data/spec/mws/apis/feeds/shipping_spec.rb +78 -0
- data/spec/mws/apis/feeds/submission_info_spec.rb +111 -0
- data/spec/mws/apis/feeds/submission_result_spec.rb +157 -0
- data/spec/mws/apis/feeds/transaction_spec.rb +64 -0
- data/spec/mws/apis/feeds/weight_spec.rb +43 -0
- data/spec/mws/apis/orders_spec.rb +9 -0
- data/spec/mws/connection_spec.rb +331 -0
- data/spec/mws/enum_spec.rb +166 -0
- data/spec/mws/query_spec.rb +104 -0
- data/spec/mws/serializer_spec.rb +187 -0
- data/spec/mws/signer_spec.rb +67 -0
- data/spec/mws/utils_spec.rb +147 -0
- data/spec/spec_helper.rb +10 -0
- metadata +220 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mws
|
4
|
+
|
5
|
+
describe Enum do
|
6
|
+
|
7
|
+
let (:options) do
|
8
|
+
{
|
9
|
+
pending: 'Pending',
|
10
|
+
unshipped: [ 'Unshipped', 'PartiallyShipped' ],
|
11
|
+
shipped: 'Shipped',
|
12
|
+
invoice_unconfirmed: 'InvoiceUnconfirmed',
|
13
|
+
cancelled: 'Cancelled',
|
14
|
+
unfulfillable: 'Unfulfillable'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
before(:all) do
|
19
|
+
OrderStatus = Enum.for options
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should not allow instance creation via new' do
|
23
|
+
expect { Enum.new }.to raise_error NoMethodError
|
24
|
+
end
|
25
|
+
|
26
|
+
context '.for' do
|
27
|
+
|
28
|
+
it 'should construct a pseudo-constant accessor for each provided symbol' do
|
29
|
+
options.each do | key, value |
|
30
|
+
OrderStatus.send(key.to_s.upcase.to_sym).should_not be nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should not share pseudo-constants between enumeration instances' do
|
35
|
+
EnumOne = Enum.for( foo: 'Foo', bar: 'Bar', baz: 'Baz' )
|
36
|
+
EnumTwo = Enum.for( bar: 'BAR', baz: 'BAZ', quk: 'QUK' )
|
37
|
+
expect { EnumOne.QUK }.to raise_error NoMethodError
|
38
|
+
expect { EnumTwo.FOO }.to raise_error NoMethodError
|
39
|
+
EnumOne.BAR.should_not == EnumTwo.BAR
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
context '.sym_reader' do
|
45
|
+
|
46
|
+
class HasEnumAttrs
|
47
|
+
|
48
|
+
EnumOne = Enum.for( foo: 'Foo', bar: 'Bar', baz: 'Baz' )
|
49
|
+
|
50
|
+
EnumTwo = Enum.for( bar: 'BAR', baz: 'BAZ', quk: 'QUK' )
|
51
|
+
|
52
|
+
Enum.sym_reader self, :one, :two
|
53
|
+
|
54
|
+
def initialize(one, two)
|
55
|
+
@one = EnumOne.for(one)
|
56
|
+
@two = EnumTwo.for(two)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should synthesize a attr_reader that exposes an enum entry as a symbol' do
|
62
|
+
it = HasEnumAttrs.new(:foo, :quk)
|
63
|
+
it.send(:instance_variable_get, '@one').should == HasEnumAttrs::EnumOne.FOO
|
64
|
+
it.one.should == :foo
|
65
|
+
it.send(:instance_variable_get, '@two').should == HasEnumAttrs::EnumTwo.QUK
|
66
|
+
it.two.should == :quk
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should synthesize attr_readers that are null safe' do
|
70
|
+
it = HasEnumAttrs.new(:quk, :foo)
|
71
|
+
it.one.should be nil
|
72
|
+
it.two.should be nil
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context '#for' do
|
78
|
+
|
79
|
+
it 'should be able to find an enum entry from a symbol' do
|
80
|
+
OrderStatus.for(:pending).should == OrderStatus.PENDING
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should be able to find an enum entry from a string' do
|
84
|
+
OrderStatus.for('Pending').should == OrderStatus.PENDING
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should be able to find an enum entry from an enum entry' do
|
88
|
+
OrderStatus.for(OrderStatus.PENDING).should == OrderStatus.PENDING
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
context '#sym' do
|
94
|
+
|
95
|
+
it 'should return nil for nil value' do
|
96
|
+
OrderStatus.sym(nil).should be nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should return nil for an unknown value' do
|
100
|
+
OrderStatus.sym('UnknownValue').should be nil
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should provide the symbol for a given value' do
|
104
|
+
OrderStatus.sym('Pending').should == :pending
|
105
|
+
OrderStatus.sym('Unshipped').should == :unshipped
|
106
|
+
OrderStatus.sym('PartiallyShipped').should == :unshipped
|
107
|
+
OrderStatus.sym('Shipped').should == :shipped
|
108
|
+
OrderStatus.sym('Cancelled').should == :cancelled
|
109
|
+
OrderStatus.sym('Unfulfillable').should == :unfulfillable
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
context '#val' do
|
115
|
+
|
116
|
+
it 'should return nil for nil symbol' do
|
117
|
+
OrderStatus.val(nil).should be nil
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should return nil for an unknown sumbol' do
|
121
|
+
OrderStatus.val(:unknown).should be nil
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should provide the value for a given symbol' do
|
125
|
+
OrderStatus.val(:pending).should == 'Pending'
|
126
|
+
OrderStatus.val(:unshipped).should == [ 'Unshipped', 'PartiallyShipped' ]
|
127
|
+
OrderStatus.val(:shipped).should == 'Shipped'
|
128
|
+
OrderStatus.val(:cancelled).should == 'Cancelled'
|
129
|
+
OrderStatus.val(:unfulfillable).should == 'Unfulfillable'
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
context '#syms' do
|
135
|
+
|
136
|
+
it 'should provide the set of symbols' do
|
137
|
+
OrderStatus.syms.should == options.keys
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
context '#vals' do
|
143
|
+
|
144
|
+
it 'should provide the list of values' do
|
145
|
+
OrderStatus.vals.should == options.values.flatten
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should be able to provide a symbol for an entry' do
|
151
|
+
OrderStatus.PENDING.sym.should == :pending
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should be able to provide a value for an enum entry' do
|
155
|
+
OrderStatus.PENDING.val.should == 'Pending'
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should be able to handle multivalued enum entries' do
|
159
|
+
OrderStatus.for(:unshipped).should == OrderStatus.UNSHIPPED
|
160
|
+
OrderStatus.for('Unshipped').should == OrderStatus.UNSHIPPED
|
161
|
+
OrderStatus.for('PartiallyShipped').should == OrderStatus.UNSHIPPED
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mws
|
4
|
+
|
5
|
+
class Query
|
6
|
+
attr_reader :params
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Query do
|
10
|
+
|
11
|
+
let(:defaults) do
|
12
|
+
{
|
13
|
+
access: 'Q6K3SCWMLYAKIAJXAAYQ',
|
14
|
+
merchant: 'J4UBGSWCA31UTJ',
|
15
|
+
markets: [ 'ATVPDKIKX0DER', 'KIKX0DERATVPD' ],
|
16
|
+
last_updated_after: 4.hours.ago
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:query) { Query.new defaults }
|
21
|
+
|
22
|
+
it 'should default SignatureMethod to HmacSHA256' do
|
23
|
+
query.params['SignatureMethod'].should == 'HmacSHA256'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should default SignatureVersion to 2' do
|
27
|
+
query.params['SignatureVersion'].should == '2'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should default Timestamp to now in iso8601 format' do
|
31
|
+
time = URI.decode(query.params['Timestamp']).should == Time.now.iso8601
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should accept overrides to SignatureMethod' do
|
35
|
+
Query.new(defaults.merge(signature_method: 'HmacSHA1')).params['SignatureMethod'].should == 'HmacSHA1'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should accept overrides to SignatureVersion' do
|
39
|
+
Query.new(defaults.merge(signature_version: 3)).params['SignatureVersion'].should == '3'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should accept overrides to Timestamp' do
|
43
|
+
time = 4.hours.ago
|
44
|
+
query = Query.new(defaults.merge(timestamp: time))
|
45
|
+
URI.decode(query.params['Timestamp']).should == time.iso8601
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should translate access to AWSAccessKeyId' do
|
49
|
+
access_key = 'Q6K3SCWMLYAKIAJXAAYQ'
|
50
|
+
Query.new(defaults.merge(access: access_key)).params['AWSAccessKeyId'].should == access_key
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should translate merchant or seller to seller_id' do
|
54
|
+
merchant = 'J4UBGSWCA31UTJ'
|
55
|
+
queries = [ Query.new(defaults.merge(merchant: merchant)), Query.new(defaults.merge(seller: merchant)) ]
|
56
|
+
queries.each do | query |
|
57
|
+
query.params['SellerId'].should == merchant
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should gracefully handle empty markets list' do
|
62
|
+
Query.new(defaults.merge(markets: [])).params['MarketplaceIdList.Id.1'].should be nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should translate single market to MarketplaceIdList.Id.1' do
|
66
|
+
market = 'ATVPDKIKX0DER'
|
67
|
+
Query.new(defaults.merge(markets: [ market ])).params['MarketplaceIdList.Id.1'].should == market
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should translate multiple markets to MarketplaceIdList.Id.*' do
|
71
|
+
markets = [ 'ATVPDKIKX0DER', 'KIKX0DERATVPD' ]
|
72
|
+
query = Query.new defaults.merge(markets: markets)
|
73
|
+
markets.each_with_index do | market, index |
|
74
|
+
query.params["MarketplaceIdList.Id.#{index + 1}"].should == market
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should allow for overriding the list representation strategy via list_pattern' do
|
79
|
+
markets = [ 'ATVPDKIKX0DER', 'KIKX0DERATVPD' ]
|
80
|
+
list_pattern = '%{key}[%<index>d]'
|
81
|
+
query = Query.new defaults.merge(markets: markets, list_pattern: list_pattern)
|
82
|
+
markets.each_with_index do | market, index |
|
83
|
+
query.params["MarketplaceId[#{index + 1}]"].should == market
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should sort query parameters lexicographically' do
|
88
|
+
query.params.inject('') do | prev, entry |
|
89
|
+
entry.first.should be > prev
|
90
|
+
entry.first
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should convert to a compliant query string' do
|
95
|
+
query_string = query.to_s
|
96
|
+
query_string.split('&').each do | entry |
|
97
|
+
key, value = entry.split '='
|
98
|
+
query.params[key].should == value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mws
|
4
|
+
|
5
|
+
describe Serializer do
|
6
|
+
|
7
|
+
let(:from) { 1.day.from_now }
|
8
|
+
let(:to) { 3.months.from_now }
|
9
|
+
let(:regular_price) { 21.99 }
|
10
|
+
let(:sale_price) { 14.99 }
|
11
|
+
|
12
|
+
context '.tree' do
|
13
|
+
|
14
|
+
it 'should properly serialize without a parent' do
|
15
|
+
expected = Nokogiri::XML::Builder.new {
|
16
|
+
Sale {
|
17
|
+
StartDate from.iso8601
|
18
|
+
EndDate to.iso8601
|
19
|
+
SalePrice sale_price, currency: 'USD'
|
20
|
+
}
|
21
|
+
}.doc.root.to_xml
|
22
|
+
actual = Serializer.tree 'Sale', nil do | xml |
|
23
|
+
xml.StartDate from.iso8601
|
24
|
+
xml.EndDate to.iso8601
|
25
|
+
xml.SalePrice sale_price, currency: 'USD'
|
26
|
+
end
|
27
|
+
actual.should == expected
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should properly serialize with a parent' do
|
31
|
+
sku = '7890123456'
|
32
|
+
expected = Nokogiri::XML::Builder.new {
|
33
|
+
Price {
|
34
|
+
SKU sku
|
35
|
+
StandardPrice regular_price, currency: 'USD'
|
36
|
+
Sale {
|
37
|
+
StartDate from.iso8601
|
38
|
+
EndDate to.iso8601
|
39
|
+
SalePrice sale_price, currency: 'USD'
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}.to_xml
|
43
|
+
actual = Nokogiri::XML::Builder.new {
|
44
|
+
Price {
|
45
|
+
SKU sku
|
46
|
+
StandardPrice regular_price, currency: 'USD'
|
47
|
+
actual = Serializer.tree 'Sale', self do | xml |
|
48
|
+
xml.StartDate from.iso8601
|
49
|
+
xml.EndDate to.iso8601
|
50
|
+
xml.SalePrice sale_price, currency: 'USD'
|
51
|
+
end
|
52
|
+
}
|
53
|
+
}.to_xml
|
54
|
+
actual.should == expected
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context '.leaf' do
|
60
|
+
|
61
|
+
it 'should properly serialize without a parent' do
|
62
|
+
expected = Nokogiri::XML::Builder.new {
|
63
|
+
SalePrice sale_price, currency: 'USD'
|
64
|
+
}.doc.root.to_xml
|
65
|
+
actual = Serializer.leaf 'SalePrice', nil, sale_price, currency: 'USD'
|
66
|
+
actual.should == expected
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should properly serialize with a parent' do
|
70
|
+
expected = Nokogiri::XML::Builder.new {
|
71
|
+
Sale {
|
72
|
+
StartDate from.iso8601
|
73
|
+
EndDate to.iso8601
|
74
|
+
SalePrice sale_price, currency: 'USD'
|
75
|
+
}
|
76
|
+
}.to_xml
|
77
|
+
actual = Nokogiri::XML::Builder.new {
|
78
|
+
Sale {
|
79
|
+
StartDate from.iso8601
|
80
|
+
EndDate to.iso8601
|
81
|
+
Serializer.leaf 'SalePrice', self, sale_price, currency: 'USD'
|
82
|
+
}
|
83
|
+
}.to_xml
|
84
|
+
actual.should == expected
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context '#xml_for' do
|
90
|
+
|
91
|
+
let(:data) do
|
92
|
+
{
|
93
|
+
foo: 'Bar',
|
94
|
+
foo_bar: {
|
95
|
+
baz_quk: 'FooBarBazQuk'
|
96
|
+
},
|
97
|
+
baz: [
|
98
|
+
'Foo',
|
99
|
+
'Bar',
|
100
|
+
'Baz',
|
101
|
+
'Quk'
|
102
|
+
],
|
103
|
+
price: Mws::Money(regular_price, :usd)
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should work with no exceptions' do
|
108
|
+
expected = Nokogiri::XML::Builder.new {
|
109
|
+
Data {
|
110
|
+
Foo 'Bar'
|
111
|
+
FooBar {
|
112
|
+
BazQuk 'FooBarBazQuk'
|
113
|
+
}
|
114
|
+
Baz 'Foo'
|
115
|
+
Baz 'Bar'
|
116
|
+
Baz 'Baz'
|
117
|
+
Baz 'Quk'
|
118
|
+
Price regular_price, currency: 'USD'
|
119
|
+
}
|
120
|
+
}.to_xml
|
121
|
+
actual = Nokogiri::XML::Builder.new
|
122
|
+
Serializer.new.xml_for('Data', data, actual)
|
123
|
+
actual.to_xml.should == expected
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should work with exceptions' do
|
127
|
+
expected = Nokogiri::XML::Builder.new {
|
128
|
+
Data {
|
129
|
+
FOO 'Bar'
|
130
|
+
FooBar {
|
131
|
+
BazQuk 'FooBarBazQuk'
|
132
|
+
}
|
133
|
+
BaZ 'Foo'
|
134
|
+
BaZ 'Bar'
|
135
|
+
BaZ 'Baz'
|
136
|
+
BaZ 'Quk'
|
137
|
+
Price regular_price, currency: 'USD'
|
138
|
+
}
|
139
|
+
}.to_xml
|
140
|
+
actual = Nokogiri::XML::Builder.new
|
141
|
+
Serializer.new(foo: 'FOO', baz: 'BaZ').xml_for('Data', data, actual)
|
142
|
+
actual.to_xml.should == expected
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
context '#hash_for' do
|
148
|
+
|
149
|
+
let(:xml) do
|
150
|
+
Nokogiri::XML::Builder.new {
|
151
|
+
Data {
|
152
|
+
FOO 'Bar'
|
153
|
+
FooBar {
|
154
|
+
BazQuk 'FooBarBazQuk'
|
155
|
+
}
|
156
|
+
BaZ 'Foo'
|
157
|
+
BaZ 'Bar'
|
158
|
+
BaZ 'Baz'
|
159
|
+
BaZ 'Quk'
|
160
|
+
Price regular_price, currency: 'USD'
|
161
|
+
}
|
162
|
+
}.doc.root
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should work with exceptions' do
|
166
|
+
expected = {
|
167
|
+
foo: 'Bar',
|
168
|
+
foo_bar: {
|
169
|
+
baz_quk: 'FooBarBazQuk'
|
170
|
+
},
|
171
|
+
baz: [
|
172
|
+
'Foo',
|
173
|
+
'Bar',
|
174
|
+
'Baz',
|
175
|
+
'Quk'
|
176
|
+
],
|
177
|
+
price: regular_price.to_s
|
178
|
+
}
|
179
|
+
actual = Serializer.new(foo: 'FOO', baz: 'BaZ').hash_for(xml, nil)
|
180
|
+
actual.should == expected
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|