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.
Files changed (57) hide show
  1. data/.gitignore +19 -0
  2. data/.sublime-project +19 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +111 -0
  6. data/Rakefile +1 -0
  7. data/lib/mws.rb +34 -0
  8. data/lib/mws/apis.rb +6 -0
  9. data/lib/mws/apis/feeds.rb +20 -0
  10. data/lib/mws/apis/feeds/api.rb +103 -0
  11. data/lib/mws/apis/feeds/distance.rb +23 -0
  12. data/lib/mws/apis/feeds/feed.rb +114 -0
  13. data/lib/mws/apis/feeds/image_listing.rb +44 -0
  14. data/lib/mws/apis/feeds/inventory.rb +77 -0
  15. data/lib/mws/apis/feeds/measurement.rb +32 -0
  16. data/lib/mws/apis/feeds/money.rb +31 -0
  17. data/lib/mws/apis/feeds/price_listing.rb +48 -0
  18. data/lib/mws/apis/feeds/product.rb +173 -0
  19. data/lib/mws/apis/feeds/sale_price.rb +31 -0
  20. data/lib/mws/apis/feeds/shipping.rb +160 -0
  21. data/lib/mws/apis/feeds/submission_info.rb +45 -0
  22. data/lib/mws/apis/feeds/submission_result.rb +87 -0
  23. data/lib/mws/apis/feeds/transaction.rb +37 -0
  24. data/lib/mws/apis/feeds/weight.rb +19 -0
  25. data/lib/mws/apis/orders.rb +23 -0
  26. data/lib/mws/connection.rb +84 -0
  27. data/lib/mws/enum.rb +81 -0
  28. data/lib/mws/errors.rb +32 -0
  29. data/lib/mws/query.rb +45 -0
  30. data/lib/mws/serializer.rb +81 -0
  31. data/lib/mws/signer.rb +20 -0
  32. data/lib/mws/utils.rb +50 -0
  33. data/mws.gemspec +25 -0
  34. data/scripts/catalog-workflow +136 -0
  35. data/spec/mws/apis/feeds/api_spec.rb +229 -0
  36. data/spec/mws/apis/feeds/distance_spec.rb +43 -0
  37. data/spec/mws/apis/feeds/feed_spec.rb +92 -0
  38. data/spec/mws/apis/feeds/image_listing_spec.rb +109 -0
  39. data/spec/mws/apis/feeds/inventory_spec.rb +135 -0
  40. data/spec/mws/apis/feeds/measurement_spec.rb +84 -0
  41. data/spec/mws/apis/feeds/money_spec.rb +43 -0
  42. data/spec/mws/apis/feeds/price_listing_spec.rb +90 -0
  43. data/spec/mws/apis/feeds/product_spec.rb +264 -0
  44. data/spec/mws/apis/feeds/shipping_spec.rb +78 -0
  45. data/spec/mws/apis/feeds/submission_info_spec.rb +111 -0
  46. data/spec/mws/apis/feeds/submission_result_spec.rb +157 -0
  47. data/spec/mws/apis/feeds/transaction_spec.rb +64 -0
  48. data/spec/mws/apis/feeds/weight_spec.rb +43 -0
  49. data/spec/mws/apis/orders_spec.rb +9 -0
  50. data/spec/mws/connection_spec.rb +331 -0
  51. data/spec/mws/enum_spec.rb +166 -0
  52. data/spec/mws/query_spec.rb +104 -0
  53. data/spec/mws/serializer_spec.rb +187 -0
  54. data/spec/mws/signer_spec.rb +67 -0
  55. data/spec/mws/utils_spec.rb +147 -0
  56. data/spec/spec_helper.rb +10 -0
  57. 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