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,81 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Mws
|
4
|
+
|
5
|
+
class Serializer
|
6
|
+
|
7
|
+
def self.tree(name, parent, &block)
|
8
|
+
if parent
|
9
|
+
parent.send(name, &block)
|
10
|
+
parent.doc.root.to_xml
|
11
|
+
else
|
12
|
+
Nokogiri::XML::Builder.new do | xml |
|
13
|
+
xml.send(name, &block)
|
14
|
+
end.doc.root.to_xml
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.leaf(name, parent, value, attributes)
|
19
|
+
if parent
|
20
|
+
parent.send(name, value, attributes)
|
21
|
+
parent.doc.root.to_xml
|
22
|
+
else
|
23
|
+
Nokogiri::XML::Builder.new do | xml |
|
24
|
+
xml.send(name, value, attributes)
|
25
|
+
end.doc.root.to_xml
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(exceptions={})
|
30
|
+
@xml_exceptions = exceptions
|
31
|
+
@hash_exceptions = {}
|
32
|
+
exceptions.each do | key, value |
|
33
|
+
@hash_exceptions[value.to_sym] = key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def xml_for(name, data, builder, context=nil)
|
38
|
+
element = @xml_exceptions[name.to_sym] || Utils.camelize(name)
|
39
|
+
path = path_for name, context
|
40
|
+
if data.respond_to? :keys
|
41
|
+
builder.send(element) do | b |
|
42
|
+
data.each do | key, value |
|
43
|
+
xml_for(key, value, builder, path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
elsif data.respond_to? :each
|
47
|
+
data.each { |value| xml_for(name, value, builder, path) }
|
48
|
+
elsif data.respond_to? :to_xml
|
49
|
+
data.to_xml element, builder
|
50
|
+
else
|
51
|
+
builder.send element, data
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def hash_for(node, context)
|
56
|
+
elements = node.elements()
|
57
|
+
return node.text unless elements.size > 0
|
58
|
+
res = {}
|
59
|
+
elements.each do | element |
|
60
|
+
name = @hash_exceptions[element.name.to_sym] || Utils.underscore(element.name).to_sym
|
61
|
+
path = path_for name, context
|
62
|
+
content = instance_exec element, path, &method(:hash_for)
|
63
|
+
if res.include? name
|
64
|
+
res[name] = [ res[name] ] unless res[name].instance_of? Array
|
65
|
+
res[name] << content
|
66
|
+
else
|
67
|
+
res[name] = content
|
68
|
+
end
|
69
|
+
end
|
70
|
+
res
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def path_for(name, context=nil)
|
76
|
+
[ context, name ].compact.join '.'
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
data/lib/mws/signer.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Mws::Signer
|
2
|
+
|
3
|
+
def initialize(options={})
|
4
|
+
@verb = (options[:method] || options[:verb] || 'POST').to_s.upcase
|
5
|
+
@host = (options[:host] || 'mws.amazonservices.com').to_s.downcase
|
6
|
+
@path = options[:path] || '/'
|
7
|
+
@secret = options[:secret]
|
8
|
+
end
|
9
|
+
|
10
|
+
def signature(query, secret=@secret)
|
11
|
+
digest = OpenSSL::Digest::Digest.new 'sha256'
|
12
|
+
message = [ @verb, @host, @path, query ].join "\n"
|
13
|
+
Base64::encode64(OpenSSL::HMAC.digest(digest, secret, message)).chomp
|
14
|
+
end
|
15
|
+
|
16
|
+
def sign(query, secret=@secret)
|
17
|
+
"#{query}&Signature=#{Mws::Utils.uri_escape signature(query, secret)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/mws/utils.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# This module contains a collection of generally useful methods that (currently) have no better place to live. They can
|
2
|
+
# either be referenced directly as module methods or be mixed in.
|
3
|
+
module Mws::Utils
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# This method will derive a camelized name from the provided underscored name.
|
7
|
+
#
|
8
|
+
# @param [#to_s] name The underscored name to be camelized.
|
9
|
+
# @param [Boolean] uc_first True if and only if the first letter of the resulting camelized name should be
|
10
|
+
# capitalized.
|
11
|
+
#
|
12
|
+
# @return [String] The camelized name corresponding to the provided underscored name.
|
13
|
+
def camelize(name, uc_first=true)
|
14
|
+
return nil if name.nil?
|
15
|
+
name = name.to_s.strip
|
16
|
+
return name if name.empty?
|
17
|
+
parts = name.split '_'
|
18
|
+
assemble = lambda { |head, tail| head + tail.capitalize }
|
19
|
+
parts[0] = uc_first ? parts[0].capitalize : parts[0].downcase
|
20
|
+
parts.inject(&assemble)
|
21
|
+
end
|
22
|
+
|
23
|
+
def underscore(name)
|
24
|
+
return nil if name.nil?
|
25
|
+
name = name.to_s.strip
|
26
|
+
return name if name.empty?
|
27
|
+
name.gsub(/::/, '/')
|
28
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
29
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
30
|
+
.tr("-", "_")
|
31
|
+
.downcase
|
32
|
+
end
|
33
|
+
|
34
|
+
def uri_escape(value)
|
35
|
+
value.gsub /([^a-zA-Z0-9_.~-]+)/ do
|
36
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def alias(to, from, *constants)
|
41
|
+
constants.each do | name |
|
42
|
+
constant = from.const_get(name)
|
43
|
+
to.singleton_class.send(:define_method, name) do | *args, &block |
|
44
|
+
constant.new *args, &block
|
45
|
+
end
|
46
|
+
to.const_set(name, constant)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/mws.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mws'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'mws-connect'
|
8
|
+
gem.version = Mws::VERSION
|
9
|
+
gem.authors = ['Sean M. Duncan', 'John E. Bailey']
|
10
|
+
gem.email = ['info@devmode.com']
|
11
|
+
gem.description = %q{The missing ruby client library for Amazon MWS}
|
12
|
+
gem.summary = %q{The missing ruby client library for Amazon MWS}
|
13
|
+
gem.homepage = 'http://github.com/devmode/mws'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|specs?|feat(ures?)?)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
gem.add_development_dependency 'rspec'
|
20
|
+
gem.add_development_dependency 'simplecov'
|
21
|
+
gem.add_development_dependency 'cucumber'
|
22
|
+
gem.add_development_dependency 'activesupport'
|
23
|
+
gem.add_dependency 'logging', '~> 1.8.0'
|
24
|
+
gem.add_dependency 'nokogiri', '~> 1.5.5'
|
25
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if ARGV.size < 3
|
4
|
+
puts "Usage: 'catalog-workflow merchant access secret'"
|
5
|
+
exit 1
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'mws'
|
9
|
+
require 'logging'
|
10
|
+
require 'active_support/core_ext'
|
11
|
+
|
12
|
+
Logging.logger.root.appenders = Logging.appenders.stdout
|
13
|
+
Logging.logger.root.level = :debug
|
14
|
+
|
15
|
+
mws = Mws.connect(
|
16
|
+
merchant: ARGV[0],
|
17
|
+
access: ARGV[1],
|
18
|
+
secret: ARGV[2]
|
19
|
+
)
|
20
|
+
|
21
|
+
class Workflow
|
22
|
+
|
23
|
+
def initialize(queue)
|
24
|
+
@queue = queue
|
25
|
+
@handlers = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def register(*deps, &handler)
|
29
|
+
@handlers << [ handler, deps ]
|
30
|
+
end
|
31
|
+
|
32
|
+
def complete(feed)
|
33
|
+
@handlers.each do | handler |
|
34
|
+
handler.last.delete feed
|
35
|
+
end
|
36
|
+
proceed
|
37
|
+
end
|
38
|
+
|
39
|
+
def proceed
|
40
|
+
@handlers.each_with_index do | handler, index |
|
41
|
+
if handler.last.empty?
|
42
|
+
@handlers[index] = nil
|
43
|
+
[ handler.first.call ].compact.flatten.each do | feed |
|
44
|
+
@queue << feed
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@handlers.compact!
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
in_process_q = []
|
54
|
+
terminated_q = []
|
55
|
+
|
56
|
+
workflow = Workflow.new(in_process_q)
|
57
|
+
|
58
|
+
workflow.register do
|
59
|
+
product_feed = mws.feeds.products.add(
|
60
|
+
Mws::Product('2634897') do
|
61
|
+
tax_code 'A_GEN_TAX'
|
62
|
+
name "Rocketfish\u2122 6' In-Wall HDMI Cable"
|
63
|
+
brand "Rocketfish\u2122"
|
64
|
+
description "This 6' HDMI cable supports signals up to 1080p and most screen refresh rates to ensure stunning image clarity with reduced motion blur in fast-action scenes."
|
65
|
+
bullet_point 'Compatible with HDMI components'
|
66
|
+
bullet_point'Connects an HDMI source to an HDTV or projector with an HDMI input'
|
67
|
+
bullet_point 'Up to 15 Gbps bandwidth'
|
68
|
+
bullet_point'In-wall rated'
|
69
|
+
msrp 49.99, :usd
|
70
|
+
category :ce
|
71
|
+
details {
|
72
|
+
cable_or_adapter {
|
73
|
+
cable_length as_distance 6, :feet
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
)
|
78
|
+
workflow.register product_feed.id do
|
79
|
+
price_feed = mws.feeds.prices.add(
|
80
|
+
Mws::PriceListing('2634897', 49.99).on_sale(29.99, Time.now, 3.months.from_now)
|
81
|
+
)
|
82
|
+
image_feed = mws.feeds.images.add(
|
83
|
+
Mws::ImageListing('2634897', 'http://images.bestbuy.com/BestBuy_US/images/products/2634/2634897_sa.jpg', 'Main'),
|
84
|
+
Mws::ImageListing('2634897', 'http://images.bestbuy.com/BestBuy_US/images/products/2634/2634897cv1a.jpg', 'PT1')
|
85
|
+
)
|
86
|
+
shipping_feed = mws.feeds.shipping.add(
|
87
|
+
Mws::Shipping('2634897') {
|
88
|
+
restricted :alaska_hawaii, :standard, :po_box
|
89
|
+
adjust 4.99, :usd, :continental_us, :standard
|
90
|
+
replace 11.99, :usd, :continental_us, :expedited, :street
|
91
|
+
}
|
92
|
+
)
|
93
|
+
workflow.register price_feed.id, image_feed.id, shipping_feed.id do
|
94
|
+
inventory_feed = mws.feeds.inventory.add(
|
95
|
+
Mws::Inventory('2634897', quantity: 10, fulfillment_type: :mfn)
|
96
|
+
)
|
97
|
+
workflow.register inventory_feed.id do
|
98
|
+
puts 'The workflow is complete!'
|
99
|
+
end
|
100
|
+
inventory_feed.id
|
101
|
+
end
|
102
|
+
[ price_feed.id, image_feed.id, shipping_feed.id ]
|
103
|
+
end
|
104
|
+
product_feed.id
|
105
|
+
end
|
106
|
+
|
107
|
+
workflow.proceed
|
108
|
+
|
109
|
+
50.times do
|
110
|
+
puts "In Process: #{in_process_q}"
|
111
|
+
mws.feeds.list(ids: in_process_q).each do | info |
|
112
|
+
puts "SubmissionId: #{info.id} Status: #{info.status}"
|
113
|
+
terminated_q << in_process_q.delete(info.id) if [:cancelled, :done].include? info.status
|
114
|
+
end unless in_process_q.empty?
|
115
|
+
|
116
|
+
puts "Terminated: #{terminated_q}"
|
117
|
+
unless terminated_q.empty?
|
118
|
+
id = terminated_q.shift
|
119
|
+
result = mws.feeds.get id
|
120
|
+
puts result.inspect
|
121
|
+
if result.messages_processed == result.count_for(:success)
|
122
|
+
workflow.complete result.transaction_id
|
123
|
+
end
|
124
|
+
end
|
125
|
+
puts "| Waiting |"
|
126
|
+
print '------------------------------------------------------------'
|
127
|
+
60.times do |it|
|
128
|
+
sleep 1
|
129
|
+
print "\r"
|
130
|
+
prg = it + 1
|
131
|
+
rem = 60 - prg
|
132
|
+
prg.times { print '=' }
|
133
|
+
rem.times { print '-' }
|
134
|
+
end
|
135
|
+
print "\n"
|
136
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mws::Apis::Feeds
|
4
|
+
|
5
|
+
class Api
|
6
|
+
attr_reader :defaults
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Api do
|
10
|
+
|
11
|
+
let(:connection) do
|
12
|
+
Mws::Connection.new(
|
13
|
+
merchant: 'GSWCJ4UBA31UTJ',
|
14
|
+
access: 'AYQAKIAJSCWMLYXAQ6K3',
|
15
|
+
secret: 'Ubzq/NskSrW4m5ncq53kddzBej7O7IE5Yx9drGrX'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:api) { Api.new(connection) }
|
20
|
+
|
21
|
+
context '.new' do
|
22
|
+
|
23
|
+
it 'should require connection' do
|
24
|
+
expect { Api.new(nil) }.to raise_error Mws::Errors::ValidationError, 'A connection is required.'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should default version to 2009-01-01' do
|
28
|
+
api.defaults[:version].should == '2009-01-01'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should initialize a products feed' do
|
32
|
+
TargetedApi.as_null_object
|
33
|
+
TargetedApi.should_receive(:new).with(anything, connection.merchant, :product)
|
34
|
+
api = Api.new(connection)
|
35
|
+
api.products.should_not be nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should initialize an images feed' do
|
39
|
+
TargetedApi.as_null_object
|
40
|
+
TargetedApi.should_receive(:new).with(anything, connection.merchant, :image)
|
41
|
+
api = Api.new(connection)
|
42
|
+
api.images.should_not be nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should initialize a prices feed' do
|
46
|
+
TargetedApi.as_null_object
|
47
|
+
TargetedApi.should_receive(:new).with(anything, connection.merchant, :price)
|
48
|
+
api = Api.new(connection)
|
49
|
+
api.prices.should_not be nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should initialize a shipping feed' do
|
53
|
+
TargetedApi.as_null_object
|
54
|
+
TargetedApi.should_receive(:new).with(anything, connection.merchant, :override)
|
55
|
+
api = Api.new(connection)
|
56
|
+
api.shipping.should_not be nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should initialize an inventory feed' do
|
60
|
+
TargetedApi.as_null_object
|
61
|
+
TargetedApi.should_receive(:new).with(anything, connection.merchant, :inventory)
|
62
|
+
api = Api.new(connection)
|
63
|
+
api.inventory.should_not be nil
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
context '#get' do
|
69
|
+
|
70
|
+
it 'should properly delegate to connection' do
|
71
|
+
connection.should_receive(:get).with('/', { feed_submission_id: 1 }, {
|
72
|
+
version: '2009-01-01',
|
73
|
+
action: 'GetFeedSubmissionResult',
|
74
|
+
xpath: 'AmazonEnvelope/Message'
|
75
|
+
}).and_return('a_node')
|
76
|
+
SubmissionResult.should_receive(:from_xml).with('a_node')
|
77
|
+
api.get(1)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
context '#submit' do
|
83
|
+
|
84
|
+
it 'should properly delegate to connection' do
|
85
|
+
response = double(:response)
|
86
|
+
response.should_receive(:xpath).with('FeedSubmissionInfo').and_return(['a_result'])
|
87
|
+
connection.should_receive(:post).with('/', { feed_type: '_POST_INVENTORY_AVAILABILITY_DATA_' }, 'a_body', {
|
88
|
+
version: '2009-01-01',
|
89
|
+
action: 'SubmitFeed'
|
90
|
+
}).and_return(response)
|
91
|
+
SubmissionInfo.should_receive(:from_xml).with('a_result')
|
92
|
+
api.submit 'a_body', feed_type: :inventory
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
context '#list' do
|
98
|
+
|
99
|
+
it 'should handle a single submission id' do
|
100
|
+
response = double(:response)
|
101
|
+
response.should_receive(:xpath).with('FeedSubmissionInfo').and_return(['result_one'])
|
102
|
+
connection.should_receive(:get).with('/', { feed_submission_id: [ 1 ] }, {
|
103
|
+
version: '2009-01-01',
|
104
|
+
action: 'GetFeedSubmissionList'
|
105
|
+
}).and_return(response)
|
106
|
+
SubmissionInfo.should_receive(:from_xml) { | node | node }.once
|
107
|
+
api.list(id: 1).should == [ 'result_one' ]
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should handle a multiple submission ids' do
|
111
|
+
response = double(:response)
|
112
|
+
response.should_receive(:xpath).with('FeedSubmissionInfo').and_return([ 'result_one', 'result_two', 'result_three' ])
|
113
|
+
connection.should_receive(:get).with('/', { feed_submission_id: [ 1, 2, 3 ] }, {
|
114
|
+
version: '2009-01-01',
|
115
|
+
action: 'GetFeedSubmissionList'
|
116
|
+
}).and_return(response)
|
117
|
+
SubmissionInfo.should_receive(:from_xml) { | node | node }.exactly(3).times
|
118
|
+
api.list(ids: [ 1, 2, 3 ]).should == [ 'result_one', 'result_two', 'result_three' ]
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
context '#count' do
|
124
|
+
|
125
|
+
it 'should properly delegate to connection' do
|
126
|
+
count = double(:count)
|
127
|
+
count.should_receive(:text).and_return('5')
|
128
|
+
response = double(:response)
|
129
|
+
response.should_receive(:xpath).with('Count').and_return([ count ])
|
130
|
+
connection.should_receive(:get).with('/', {}, {
|
131
|
+
version: '2009-01-01',
|
132
|
+
action: 'GetFeedSubmissionCount'
|
133
|
+
}).and_return(response)
|
134
|
+
api.count.should == 5
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
describe TargetedApi do
|
142
|
+
|
143
|
+
let(:connection) do
|
144
|
+
Mws::Connection.new(
|
145
|
+
merchant: 'GSWCJ4UBA31UTJ',
|
146
|
+
access: 'AYQAKIAJSCWMLYXAQ6K3',
|
147
|
+
secret: 'Ubzq/NskSrW4m5ncq53kddzBej7O7IE5Yx9drGrX'
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
let(:api) { Api.new(connection) }
|
152
|
+
|
153
|
+
context '#add' do
|
154
|
+
|
155
|
+
it 'should properly delegate to #submit' do
|
156
|
+
api.products.should_receive(:submit).with([ 'resource_one', 'resource_two' ], :update, true).and_return('a_result')
|
157
|
+
api.products.add('resource_one', 'resource_two').should == 'a_result'
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
context '#update' do
|
163
|
+
|
164
|
+
it 'should properly delegate to #submit' do
|
165
|
+
api.products.should_receive(:submit).with([ 'resource_one', 'resource_two' ], :update).and_return('a_result')
|
166
|
+
api.products.update('resource_one', 'resource_two').should == 'a_result'
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
context '#patch' do
|
172
|
+
|
173
|
+
it 'should properly delegate to #submit for products' do
|
174
|
+
api.products.should_receive(:submit).with([ 'resource_one', 'resource_two' ], :partial_update).and_return('a_result')
|
175
|
+
api.products.patch('resource_one', 'resource_two').should == 'a_result'
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should not be supported for feeds other than products' do
|
179
|
+
expect { api.images.patch('resource_one', 'resource_two') }.to raise_error 'Operation Type not supported.'
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
context '#delete' do
|
185
|
+
|
186
|
+
it 'should properly delegate to #submit' do
|
187
|
+
api.products.should_receive(:submit).with([ 'resource_one', 'resource_two' ], :delete).and_return('a_result')
|
188
|
+
api.products.delete('resource_one', 'resource_two').should == 'a_result'
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
context '#submit' do
|
194
|
+
|
195
|
+
it 'should properly construct the feed and delegate to feeds' do
|
196
|
+
resource = double :resource
|
197
|
+
resource.stub(:to_xml)
|
198
|
+
resource.stub(:sku).and_return('a_sku')
|
199
|
+
resource.stub(:operation_type).and_return(:update)
|
200
|
+
feed_xml = Nokogiri::XML::Builder.new do
|
201
|
+
AmazonEnvelope('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'amznenvelope.xsd') {
|
202
|
+
Header {
|
203
|
+
DocumentVersion '1.01'
|
204
|
+
MerchantIdentifier 'GSWCJ4UBA31UTJ'
|
205
|
+
}
|
206
|
+
MessageType 'Product'
|
207
|
+
PurgeAndReplace false
|
208
|
+
Message {
|
209
|
+
MessageID 1
|
210
|
+
OperationType 'Update'
|
211
|
+
}
|
212
|
+
}
|
213
|
+
end.doc.to_xml
|
214
|
+
submission_info = double(:submission_info).as_null_object
|
215
|
+
api.should_receive(:submit).with(feed_xml, feed_type: Feed::Type.PRODUCT, purge_and_replace: false).and_return(submission_info)
|
216
|
+
tx = api.products.submit [ resource ], :update
|
217
|
+
tx.items.size.should == 1
|
218
|
+
item = tx.items.first
|
219
|
+
item.id.should == 1
|
220
|
+
item.sku.should == 'a_sku'
|
221
|
+
item.operation.should == :update
|
222
|
+
item.qualifier.should be nil
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|