diff --git a/.gitignore b/.gitignore index 4a14492..971eb1f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ pkg/* .DS_Store spec/credentials.yml spec/fixtures/ephemeral_response/* -.rvmrc \ No newline at end of file +.rvmrc +.idea +.idea/* diff --git a/Gemfile b/Gemfile index cc4ce34..c9137a8 100644 --- a/Gemfile +++ b/Gemfile @@ -12,4 +12,4 @@ gem 'rash' gem 'rspec' gem 'ephemeral_response' gem 'awesome_print' -gem 'pry' \ No newline at end of file +gem 'pry' diff --git a/README.markdown b/README.markdown index c7e17ee..99fe17e 100644 --- a/README.markdown +++ b/README.markdown @@ -14,6 +14,10 @@ Quick Start To quickly test your connection to the service without credentials, you can ping the server, which returns server time in UTC: +Start console(You have to be on the ruby-mws path) + + rake console + MWS::Base.server_time ### Initialization @@ -114,4 +118,22 @@ This object can be used to access all API services. Below are examples on how to * GetReport - Used to request a report by report ID. All reports are currently returned as a flat file string. - `@mws.reports.get_report :report_id => '11223344'` \ No newline at end of file + `@mws.reports.get_report :report_id => '11223344'` + +Testing +------- + +Add the file `spec/credentials.yml` that looks like + +``` +aws_access_key_id: '[access key]' +secret_access_key: '[secret access key]' +seller_id: '[seller id]' +marketplace_id: '[marketplace id]' +``` + +Then run `bundle exec rspec spec/` + +To use binding.pry in a spec, be sure to require it with `require 'pry'`. + +There's still work and research to be done on how to integrate our changes in with the author's. Because we're using different credentials than the author and there are hardcoded order ids in the specs we'll never have all the specs passing for all of us. diff --git a/Rakefile b/Rakefile index 2995527..816ed6a 100644 --- a/Rakefile +++ b/Rakefile @@ -1 +1,6 @@ require "bundler/gem_tasks" + +desc "Open an irb session preloaded with this library" +task :console do + sh "irb -rubygems -I lib -r ruby-mws.rb" +end \ No newline at end of file diff --git a/bin/ruby-mws b/bin/ruby-mws old mode 100644 new mode 100755 diff --git a/lib/ruby-mws.rb b/lib/ruby-mws.rb index 1694f99..d71adef 100644 --- a/lib/ruby-mws.rb +++ b/lib/ruby-mws.rb @@ -37,11 +37,11 @@ def symbolize_keys class String - def camelize(first_letter_in_uppercase = true) + def ruby_mws_camelize(first_letter_in_uppercase = true) if first_letter_in_uppercase self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } else - self.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + self.to_s[0].chr.downcase + ruby_mws_camelize(lower_case_and_underscored_word)[1..-1] end end end @@ -56,7 +56,9 @@ def camelize(first_letter_in_uppercase = true) require 'ruby-mws/api/base' require 'ruby-mws/api/inventory' require 'ruby-mws/api/order' +require 'ruby-mws/api/product' +require 'ruby-mws/api/feed' require 'ruby-mws/api/report' require 'ruby-mws/api/query' require 'ruby-mws/api/response' -require 'ruby-mws/api/binary_response' \ No newline at end of file +require 'ruby-mws/api/binary_response' diff --git a/lib/ruby-mws/api/base.rb b/lib/ruby-mws/api/base.rb index a23bcae..6b371f8 100644 --- a/lib/ruby-mws/api/base.rb +++ b/lib/ruby-mws/api/base.rb @@ -37,11 +37,13 @@ def send_request(name, params, options={}) params = [default_params(name), params, options, @connection.to_hash].inject :merge params[:lists] ||= {} - params[:lists][:marketplace_id] = "MarketplaceId.Id" query = Query.new params resp = self.class.send(params[:verb], query.request_uri) + @connection.call_communication_callbacks(:params => query.safe_params, + :response => resp) + @response = Response.parse resp, name, params if @response.respond_to?(:next_token) and @next[:token] = @response.next_token # modifying, not comparing @@ -52,7 +54,7 @@ def send_request(name, params, options={}) def default_params(name) { - :action => name.to_s.camelize, + :action => name.to_s.ruby_mws_camelize, :signature_method => 'HmacSHA256', :signature_version => '2', :timestamp => Time.now.iso8601, @@ -76,4 +78,4 @@ def inspect end end -end \ No newline at end of file +end diff --git a/lib/ruby-mws/api/feed.rb b/lib/ruby-mws/api/feed.rb new file mode 100644 index 0000000..13b9bfc --- /dev/null +++ b/lib/ruby-mws/api/feed.rb @@ -0,0 +1,289 @@ +require 'digest/md5' +require 'nokogiri' + +module MWS + module API + class Feed < Base + ORDER_ACK = '_POST_ORDER_ACKNOWLEDGEMENT_DATA_' + SHIP_ACK = '_POST_ORDER_FULFILLMENT_DATA_' + PRODUCT_LIST = '_POST_PRODUCT_DATA_' + PRODUCT_LIST_IMAGE = '_POST_PRODUCT_IMAGE_DATA_' + PRODUCT_LIST_PRICE = '_POST_PRODUCT_PRICING_DATA_' + PRODUCT_LIST_INVENTORY = '_POST_INVENTORY_AVAILABILITY_DATA_' + PRODUCT_FLAT_FILE_INVLOADER = '_POST_FLAT_FILE_INVLOADER_DATA_' + + # POSTs a request to the submit feed action of the feeds api + # + # @param type [String] either MWS::API::Feed::ORDER_ACK or SHIP_ACK + # @param content_params [Hash{Symbol => String,Hash,Integer}] + # @return [MWS::API::Response] The response from Amazon + # @todo Think about whether we should make this more general + # for submission back to the author's fork + def submit_feed(type=nil, content_params={}) + name = :submit_feed + body = case type + when ORDER_ACK + content_for_ack_with(content_params) + when SHIP_ACK + content_for_ship_with(content_params) + when PRODUCT_LIST + content_for_product_list(content_params) + when PRODUCT_LIST_IMAGE + content_for_product_list_image(content_params) + when PRODUCT_LIST_PRICE + content_for_product_list_price(content_params) + when PRODUCT_LIST_INVENTORY + content_for_product_list_inventory(content_params) + when PRODUCT_FLAT_FILE_INVLOADER + content_for_product_flat_file_invloader(content_params) + end + query_params = {:feed_type => type} + submit_to_mws(name, body, query_params) + end + + def feed_submission_result(feed_submission_id) + name = :get_feed_submission_result + body = "" + query_params = {:feed_submission_id => feed_submission_id} + submit_to_mws(name, body, query_params) + end + + + private + + def submit_to_mws(name, body, query_params) + options = { + :verb => :post, + :uri => '/', + :version => '2009-01-01' + } + params = [default_params(name.to_s), query_params, options, @connection.to_hash].inject :merge + query = Query.new params + resp = self.class.post(query.request_uri, + :body => body, + :headers => { + 'Content-MD5' => Base64.encode64(Digest::MD5.digest(body)), + 'Content-Type' => 'text/xml; charset=iso-8859-1' + }) + @connection.call_communication_callbacks(:params => query.safe_params, + :body => body, + :response => resp) + Response.parse resp, name, params + end + + def amazon_envelope_with_header + Nokogiri::XML::Builder.new do |xml| + xml.AmazonEnvelope("xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xsi:noNamespaceSchemaLocation" => "amzn-envelope.xsd") { # add attrs here + xml.Header { + xml.DocumentVersion "1.01" + xml.MerchantIdentifier @connection.seller_id + } + yield xml + } + end + end + + def content_for_product_list_price(opts={}) + amazon_envelope_with_header do |xml| + xml.MessageType "Price" + xml.PurgeAndReplace opts[:purge_and_replace] + opts[:entries].each do |entry| + xml.Message { + xml.MessageID entry[:message_id] + xml.OperationType entry[:operation_type] + xml.Price { + xml.SKU entry[:isbn] + xml.StandardPrice(:currency => entry[:currency]){ xml.text(entry[:standard_price]) } + } + } + end + end.to_xml + end + + def content_for_product_list_inventory(opts={}) + amazon_envelope_with_header do |xml| + xml.MessageType "Inventory" + xml.PurgeAndReplace opts[:purge_and_replace] + opts[:entries].each do |entry| + xml.Message { + xml.MessageID entry[:message_id] + xml.OperationType entry[:operation_type] + xml.Inventory { + xml.SKU entry[:isbn] + xml.Quantity entry[:quantity] + xml.FulfillmentLatency entry[:fulfillment_latency] || 5 + } + } + end + end.to_xml + end + + def content_for_product_list_image(opts={}) + amazon_envelope_with_header do |xml| + xml.MessageType "ProductImage" + xml.PurgeAndReplace opts[:purge_and_replace] + opts[:entries].each do |entry| + xml.Message { + xml.MessageID entry[:message_id] + xml.OperationType entry[:operation_type] + xml.ProductImage { + xml.SKU entry[:isbn] + xml.ImageType entry[:image_type] + xml.ImageLocation entry[:image_location] + } + } + end + end.to_xml + end + + def content_for_product_flat_file_invloader(opts={}) + CSV.generate({:col_sep=>"\t"}) do |csv| + csv << ['TemplateType=InventoryLoader', 'Version=2014.0415'] + csv << ['SKU', 'Will Ship Internationally'] + csv << ['item_sku', 'will_ship_internationally'] + opts[:entries].each do |entry| + csv << [entry[:isbn], 'y'] + end + end + end + + # Returns a string containing the order acknowledgement xml + # + # @param opts [Hash{Symbol => String}] contains + # @option opts [String] :amazon_order_item_code ID of the specific item in the order + # @option opts [String] :amazon_order_id ID of the order on amazon's side + # @option opts [String] :merchant_order_id Internal order id + # @option opts [String] :merchant_order_item_id Internal order line item id + def content_for_product_list(opts={}) + amazon_envelope_with_header do |xml| + xml.MessageType "Product" + xml.PurgeAndReplace opts[:purge_and_replace] + opts[:entries].each do |entry_hash| + xml.Message { + xml.MessageID entry_hash[:message_id] + xml.OperationType entry_hash[:operation_type] + xml.Product { + xml.SKU entry_hash[:isbn] + xml.StandardProductID { + xml.Type "ISBN" + xml.Value entry_hash[:isbn] + } + get_addition_data(xml, entry_hash) if entry_hash[:operation_type] != "Delete" + } + } + end + end.to_xml + end + + def get_addition_data(xml, entry_hash) + xml.Condition { + xml.ConditionType entry_hash[:item_condition_type] + } + xml.ItemPackageQuantity entry_hash[:item_package_quantity] + xml.NumberOfItems entry_hash[:number_of_items] + xml.DescriptionData { + xml.Title entry_hash[:title] + xml.Brand entry_hash[:brand] + xml.Description entry_hash[:description] + xml.PackageDimensions { + xml.Length(:unitOfMeasure => entry_hash[:unit_of_measure]) { xml.text(entry_hash[:package_length]) } + xml.Width(:unitOfMeasure => entry_hash[:unit_of_measure]) { xml.text(entry_hash[:package_width]) } + xml.Height(:unitOfMeasure => entry_hash[:unit_of_measure]) { xml.text(entry_hash[:package_height]) } + } + xml.MSRP(:currency => entry_hash[:currency]){ xml.text(entry_hash[:standard_price]) } + xml.Manufacturer entry_hash[:manufacturer] + add_search_terms(xml, entry_hash) unless entry_hash[:search_terms].nil? + } + xml.ProductData { + xml.Books { + xml.ProductType { + xml.BooksMisc { + xml.Author entry_hash[:authors] + xml.Binding entry_hash[:binding] + xml.PublicationDate entry_hash[:publication_date] + } + } + } + } + end + + + def add_search_terms(xml, entry_hash) + entry_hash[:search_terms][:taggings].each do |search_term| + xml.SearchTerms {xml.text(search_term[:tag_name])} + end + end + + # @option opts [String] :status_code (optional) Ack status code. Defaults to 'Success' + # @option opts [String] :merchant_order_item_id (optional) Internal order line item id + # @option opts [ArrayString}>] :items (optional) list of items in the order + def content_for_ack_with(opts={}) + amazon_envelope_with_header do |xml| + xml.MessageType "OrderAcknowledgement" + xml.Message { + xml.MessageID "1" + xml.OrderAcknowledgement { + xml.AmazonOrderID opts[:amazon_order_id] + xml.MerchantOrderID opts[:merchant_order_id] + xml.StatusCode opts[:status_code] || "Success" + (opts[:items] || [opts]).each do |item_hash| + xml.Item { + xml.AmazonOrderItemCode item_hash[:amazon_order_item_code] + xml.MerchantOrderItemID item_hash[:merchant_order_item_id] + } + end + } + } + end.to_xml + end + + # Returns a string containing the shipping acknowledgement xml + # + # @param [Hash{Symbol => String,Array,DateTime}] opts contains: + # @option opts [Array] :orders order specifics including: + # @option opts [String] :merchant_order_id The internal order id + # @option opts [String] :amazon_order_id The id assigned to the order + # by amazon. + # @option opts [String] :carrier_code Represents a shipper code + # possible codes are USPS, UPS, FedEx, DHL, Fastway, GLS, GO!, + # NipponExpress, YamatoTransport, Hermes Logistik Gruppe, + # Royal Mail, Parcelforce, City Link, TNT, Target, SagawaExpress + # @option opts [String] :shipping_method string describing method (i.e. 2nd Day) + # @option opts [Array] :items item specifics including: + # :amazon_order_item_code + # :quantity keys + # @option opts [String] :tracking_number (optional) shipper tracking number + # @option opts [String] :fulfillment_date (optional) DateTime the orders were fulfilled + # defaults to the current time but an individual order's fulfillment date is + # sent in preference over either of the defaults + # @option opts [String] :merchant_order_item_id Internal order line item id + def content_for_ship_with(opts={}) + orders_fulfillment_date = opts[:fulfillment_date] || DateTime.now + + amazon_envelope_with_header do |xml| + xml.MessageType "OrderFulfillment" + opts[:orders].each do |order_hash| + xml.Message { + xml.MessageID order_hash[:message_id] + xml.OrderFulfillment { + xml.AmazonOrderID order_hash[:amazon_order_id] + xml.FulfillmentDate order_hash[:fulfillment_date] || orders_fulfillment_date + xml.FulfillmentData { + xml.CarrierCode order_hash[:carrier_code] + xml.ShippingMethod order_hash[:shipping_method] + xml.ShipperTrackingNumber order_hash[:tracking_number] if order_hash[:tracking_number] + } + order_hash[:items].each do |item_hash| + xml.Item { + xml.AmazonOrderItemCode item_hash[:amazon_order_item_code] + xml.Quantity item_hash[:quantity] + } + end + } + } + end + end.to_xml + end + end + end +end diff --git a/lib/ruby-mws/api/order.rb b/lib/ruby-mws/api/order.rb index 1b11a92..d8702c6 100644 --- a/lib/ruby-mws/api/order.rb +++ b/lib/ruby-mws/api/order.rb @@ -4,10 +4,12 @@ module API class Order < Base def_request [:list_orders, :list_orders_by_next_token], :verb => :get, - :uri => '/Orders/2011-01-01', - :version => '2011-01-01', + :uri => '/Orders/2013-09-01', + :version => '2013-09-01', :lists => { - :order_status => "OrderStatus.Status" + :order_status => "OrderStatus.Status", + :marketplace_id => "MarketplaceId.Id" + }, :mods => [ lambda {|r| r.orders = r.orders.order if r.orders} @@ -15,18 +17,22 @@ class Order < Base def_request [:list_order_items, :list_order_items_by_next_token], :verb => :get, - :uri => '/Orders/2011-01-01', - :version => '2011-01-01', + :uri => '/Orders/2013-09-01', + :version => '2013-09-01', + :lists => { + :marketplace_id => "MarketplaceId.Id" + }, :mods => [ lambda {|r| r.order_items = [r.order_items.order_item].flatten} ] def_request :get_order, :verb => :get, - :uri => '/Orders/2011-01-01', - :version => '2011-01-01', + :uri => '/Orders/2013-09-01', + :version => '2013-09-01', :lists => { - :amazon_order_id => "AmazonOrderId.Id" + :amazon_order_id => "AmazonOrderId.Id", + :marketplace_id => "MarketplaceId.Id" }, :mods => [ lambda {|r| r.orders = [r.orders.order].flatten} @@ -34,4 +40,4 @@ class Order < Base end end -end \ No newline at end of file +end diff --git a/lib/ruby-mws/api/product.rb b/lib/ruby-mws/api/product.rb new file mode 100644 index 0000000..327042b --- /dev/null +++ b/lib/ruby-mws/api/product.rb @@ -0,0 +1,14 @@ +module MWS + module API + + class Product < Base + def_request [:get_matching_product, :get_matching_product_by_next_token], + :verb => :get, + :uri => '/Products/2011-10-01', + :version => '2011-10-01', + :lists => { + :asin => "ASINList.ASIN" + } + end + end +end diff --git a/lib/ruby-mws/api/query.rb b/lib/ruby-mws/api/query.rb index 0dae27b..4b48b41 100644 --- a/lib/ruby-mws/api/query.rb +++ b/lib/ruby-mws/api/query.rb @@ -2,6 +2,7 @@ module MWS module API class Query + UNSAFE_PARAMS = [:mods, :aws_access_key_id, :seller_id, :secret_access_key].freeze def initialize(params) @params = params @@ -17,7 +18,7 @@ def canonical end def signature - digest = OpenSSL::Digest::Digest.new('sha256') + digest = OpenSSL::Digest.new('sha256') key = @params[:secret_access_key] Base64.encode64(OpenSSL::HMAC.digest(digest, key, canonical)).chomp end @@ -26,6 +27,12 @@ def request_uri "https://" << @params[:host] << @params[:uri] << '?' << build_sorted_query(signature) end + # Strips all the "unsafe" keys from the params instance var + # @return [Hash] cleaned params + def safe_params + @params.reject{|key,_| UNSAFE_PARAMS.include? key.to_sym} + end + private def build_sorted_query(signature=nil) params = @params.dup.delete_if {|k,v| exclude_from_query.include? k} @@ -34,7 +41,7 @@ def build_sorted_query(signature=nil) # hack to capitalize AWS in param names # TODO: Allow for multiple marketplace ids - params = Hash[params.map{|k,v| [k.camelize.sub(/Aws/,'AWS'), v]}] + params = Hash[params.map{|k,v| [k.ruby_mws_camelize.sub(/Aws/,'AWS'), v]}] params = params.sort.map! { |p| "#{p[0]}=#{process_param(p[1])}" } params.join('&') @@ -70,4 +77,4 @@ def exclude_from_query end end -end \ No newline at end of file +end diff --git a/lib/ruby-mws/api/response.rb b/lib/ruby-mws/api/response.rb index bfb2363..3f8c66d 100644 --- a/lib/ruby-mws/api/response.rb +++ b/lib/ruby-mws/api/response.rb @@ -2,19 +2,23 @@ module MWS module API class Response < Hashie::Rash - + def self.parse(body, name, params) return body unless body.is_a?(Hash) rash = self.new(body) - handle_error_response(rash["error_response"]["error"]) unless rash["error_response"].nil? - raise BadResponseError, "received non-matching response type #{rash.keys}" if rash["#{name}_response"].nil? - rash = rash["#{name}_response"] - - if rash = rash["#{name}_result"] - # only runs mods if correct result is present - params[:mods].each {|mod| mod.call(rash) } if params[:mods] + if !rash.keys.nil? && rash.keys.first == "amazon_envelope" rash + else + err_msg = rash.try(:error_response).try(:error) + raise BadResponseError, "received non-matching response type #{rash.keys} code: #{err_msg.try(:code)} message: #{err_msg.try(:message)}" if rash["#{name}_response"].nil? + rash = rash["#{name}_response"] + + if rash = rash["#{name}_result"] + # only runs mods if correct result is present + params[:mods].each {|mod| mod.call(rash) } if params[:mods] + rash + end end end @@ -27,4 +31,4 @@ def self.handle_error_response(error) end end -end \ No newline at end of file +end diff --git a/lib/ruby-mws/base.rb b/lib/ruby-mws/base.rb index f8c72e5..2d09279 100644 --- a/lib/ruby-mws/base.rb +++ b/lib/ruby-mws/base.rb @@ -12,6 +12,14 @@ def orders @orders ||= MWS::API::Order.new(@connection) end + def products + @products ||= MWS::API::Product.new(@connection) + end + + def feeds + @feeds ||= MWS::API::Feed.new(@connection) + end + def inventory @inventory ||= MWS::API::Inventory.new(@connection) end @@ -27,4 +35,4 @@ def self.server_time end end -end \ No newline at end of file +end diff --git a/lib/ruby-mws/connection.rb b/lib/ruby-mws/connection.rb index c4421df..b74c253 100644 --- a/lib/ruby-mws/connection.rb +++ b/lib/ruby-mws/connection.rb @@ -13,6 +13,9 @@ def initialize(options={}) @host ||= DEFAULT_HOST attrs.each { |a| raise MissingConnectionOptions, ":#{a} is required" if instance_variable_get("@#{a}").nil?} + + @response_callback = options[:response_callback] + @request_callback = options[:request_callback] end def public_attrs @@ -47,6 +50,19 @@ def self.server_time(host=DEFAULT_HOST) response = HTTParty.get("https://#{host}") Time.parse(response['PingResponse']['Timestamp']['timestamp']) end + + # Calls the request_callback and response_callback (in that order) + # if they're defined + # + # @param comm [Hash] the communications between amazon and us + # @option comm [Hash] :params the request parameters + # @option comm [String] :body the request body + # @option comm [HTTParty::Response] :response The response returned + # by HTTParty + def call_communication_callbacks(comm) + @request_callback.call(comm[:params], comm[:body] || '') if @request_callback + @response_callback.call(comm[:response]) if @response_callback + end end -end \ No newline at end of file +end diff --git a/lib/ruby-mws/version.rb b/lib/ruby-mws/version.rb index 68be4b9..6f59aa1 100644 --- a/lib/ruby-mws/version.rb +++ b/lib/ruby-mws/version.rb @@ -1,3 +1,3 @@ module MWS - VERSION = "0.1" + VERSION = "0.1.9" end diff --git a/spec/fixtures/list_orders_canonical.txt b/spec/fixtures/list_orders_canonical.txt index e3d5f0c..1e5b059 100644 --- a/spec/fixtures/list_orders_canonical.txt +++ b/spec/fixtures/list_orders_canonical.txt @@ -1,4 +1,4 @@ GET mws.amazonservices.com -/Orders/2011-01-01 -AWSAccessKeyId=access&Action=ListOrders&LastUpdatedAfter=2012-01-13T15%3A48%3A55-05%3A00&MarketplaceId.Id.1=ATVPDKIKX0DER&SellerId=A27WNMSA8OPBXY&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-01-14T15%3A48%3A55-05%3A00&Version=2011-01-01 \ No newline at end of file +/Orders/2013-09-01 +AWSAccessKeyId=access&Action=ListOrders&LastUpdatedAfter=2012-01-13T15%3A48%3A55-05%3A00&MarketplaceId.Id.1=ATVPDKIKX0DER&SellerId=A27WNMSA8OPBXY&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-01-14T15%3A48%3A55-05%3A00&Version=2013-09-01 \ No newline at end of file diff --git a/spec/ruby-mws/api/base_spec.rb b/spec/ruby-mws/api/base_spec.rb index af41767..c5899d1 100644 --- a/spec/ruby-mws/api/base_spec.rb +++ b/spec/ruby-mws/api/base_spec.rb @@ -5,8 +5,8 @@ class FakeApi < MWS::API::Base def self.test_params { :verb => :get, - :uri => '/FakeApi/2011-01-01', - :version => '2011-01-01' + :uri => '/FakeApi/2013-09-01', + :version => '2013-09-01' } end diff --git a/spec/ruby-mws/api/feed_spec.rb b/spec/ruby-mws/api/feed_spec.rb new file mode 100644 index 0000000..7d48c7b --- /dev/null +++ b/spec/ruby-mws/api/feed_spec.rb @@ -0,0 +1,562 @@ +require 'spec_helper' + +describe MWS::API::Feed do + + before :all do + EphemeralResponse.activate + end + + context "requests" do + let(:mws) {@mws = MWS.new(auth_params)} + let(:first_order_id) {'105-8268075-6520231'} + let(:second_order_id) {'105-8268075-6520232'} + let(:order_ack_order_id){'105-1063273-7151427'} + let(:orders_fulfillment_date) { DateTime.now } + let(:first_order_fulfillment) { Date.parse('2016-01-01') } + let(:carrier_code){'UPS'} + let(:shipping_method){'2nd Day'} + let(:tracking_number){'123321123321'} + let(:first_item_code){'27030690916666'} + let(:second_item_code){'62918663121794'} + let(:third_item_code){'27030690916662'} + let(:fourth_item_code){'62918663121792'} + let(:shipment_hash) { + { + :fulfillment_date => orders_fulfillment_date, + :orders => [ { + :amazon_order_id => first_order_id, + :fulfillment_date => first_order_fulfillment, + :shipping_method => shipping_method, + :carrier_code => carrier_code, + :tracking_number => tracking_number, + :items => [ + { + :amazon_order_item_code => first_item_code, + :quantity => 1 + }, + { + :amazon_order_item_code => second_item_code, + :quantity => 1 + } + ] + },{ + :amazon_order_id => second_order_id, + :shipping_method => shipping_method, + :carrier_code => carrier_code, + :items => [ + { + :amazon_order_item_code => third_item_code, + :quantity => 1, + :merchant_order_item_id => 11 + }, + { + :amazon_order_item_code => fourth_item_code, + :quantity => 1, + :merchant_order_item_id => 12 + } + ] + } ] + } + } + let(:merchant_order_id){"10"} + let(:merchant_order_item_code){"123"} + let(:amazon_order_item_code){"10-31-123123"} + let(:order_hash){{ + :amazon_order_id => order_ack_order_id, + :merchant_order_id => merchant_order_id, + :amazon_order_item_code => amazon_order_item_code, + :merchant_order_item_id => merchant_order_item_code + }} + let(:product_hash_list) { + { + :purge_and_replace => false, + :entries => [ product_hash_1, product_hash_2 ] + } + } + let(:five_taggings) {{:taggings=>[{:tag_name=>"layne mcdonald"}, {:tag_name=>"how the man became the moon"}, {:tag_name=>"team work"}, {:tag_name=>"kindness to others"}, {:tag_name=>"friendship"}]}} + let(:without_taggings) {{:taggings=>[]}} + + let(:product_hash_1) { + { + :message_id => 1, + :operation_type => 'Update', + :isbn => first_item_code, + :item_condition_type => 'New', + :item_package_quantity => 1, + :number_of_items => 1, + :title => 'The Hobbit 1', + :brand => 'Blurb', + :description => 'The Hobbit 2, or There and Back Again is a fantasy novel.', + :unit_of_measure => 'IN', + :package_length => 14, + :package_width => 12, + :package_height => 1, + :currency => 'USD', + :standard_price => '290', + :manufacturer => 'Blurb', + :search_terms => five_taggings, + :authors => 'J. R. R. Tolkien', + :binding => 'Hardcover', + :publication_date => '2014-01-31T11:03:11', + :pages => 100 + } + } + + let(:product_hash_2) { + { + :message_id => 2, + :operation_type => 'Update', + :isbn => second_item_code, + :item_condition_type => 'New', + :item_package_quantity => 1, + :number_of_items => 1, + :title => 'The Hobbit 2', + :brand => 'Blurb', + :description => 'The Hobbit 2, or There and Back Again is a fantasy novel.', + :unit_of_measure => 'IN', + :package_length => 11, + :package_width => 6, + :package_height => 2, + :currency => 'USD', + :standard_price => '390', + :manufacturer => 'Blurb', + :search_terms => without_taggings, + :authors => 'J. R. R. Tolkien', + :binding => 'Hardcover', + :publication_date => '2016-01-31T11:03:11', + :pages => 111 + } + } + + let(:product_image_hash_1) { + { + :message_id => 1, + :operation_type => 'Update', + :isbn => '9781320717869', + :image_type => 'Main', + :image_location => 'https://www-techinasiacom.netdna-ssl.com/wp-content/uploads/2012/05/funny-cat.jpg' + } + } + + let(:product_image_hash_2) { + { + :message_id => 2, + :operation_type => 'Update', + :isbn => '9781320717870', + :image_type => 'Main', + :image_location => 'http://www.vetprofessionals.com/catprofessional/images/home-cat.jpg' + } + } + + let(:product_price_hash_first) { + { + :message_id => 1, + :operation_type => 'Update', + :isbn => '9781320717869', + :currency => 'USD', + :standard_price => '290' + } + } + + let(:product_price_hash_second) { + { + :message_id => 2, + :operation_type => 'Update', + :isbn => '9781320717870', + :currency => 'USD', + :standard_price => '400' + } + } + + let(:product_inventory_hash_first) { + { + :message_id => 1, + :operation_type => 'Update', + :isbn => '9781320717869', + :currency => 'USD', + :quantity => '100', + :fulfillment_latency => '5' + } + } + + let(:product_inventory_hash_second) { + { + :message_id => 2, + :operation_type => 'Update', + :isbn => '9781320717870', + :quantity => '200', + :fulfillment_latency => '5' + } + } + + let(:product_list_remove_hash) { + { + :message_id => 1, + :operation_type => 'Delete', + :isbn => '9781320717869', + } + } + let(:product_list_remove_hash_list) { + { + :purge_and_replace => false, + :entries => [ product_list_remove_hash ] + } + } + + let(:product_price_hash_list) { + { + :purge_and_replace => false, + :entries => [ product_price_hash_first, product_price_hash_second ] + } + } + + let(:product_image_hash_list) { + { + :purge_and_replace => false, + :entries => [ product_image_hash_1, product_image_hash_2 ] + } + } + + let(:product_inventory_hash_list) { + { + :purge_and_replace => false, + :entries => [ product_inventory_hash_first, product_inventory_hash_second ] + } + } + + + describe "#feed_submission_result" do + + it "should have feed_submission_id in params" do + feed_submission_id = "50130016609" + MWS::API::Feed.should_receive(:post) do |uri, hash| + uri.should include("FeedSubmissionId") + end + response = mws.feeds.feed_submission_result(feed_submission_id) + end + + it "should have response" do + feed_submission_id = "50130016609" + MWS::API::Feed.should_receive(:post) do |uri, hash| + uri.should include("FeedSubmissionId") + end + response = mws.feeds.feed_submission_result(feed_submission_id) + response.should_not be_nil + end + + it "should have error response if feed_submission_id is nil" do + feed_submission_id = nil + response = mws.feeds.feed_submission_result(feed_submission_id) + body_doc = Nokogiri.parse(response) + body_doc.css('ErrorResponse').should_not be_nil + body_doc.css('Error').should_not be_nil + body_doc.css('Error Type').text.should == 'Sender' + body_doc.css('Error Code').text.should == 'InvalidRequest' + body_doc.css('Error Message').text.should == 'parameter FeedSubmissionId failed a validation check: value cannot be empty.' + body_doc.css('Error Detail').should_not be_nil + end + + it "should be able to post and get feed submission response" do + feed_submission_id = "50130016609" + response = mws.feeds.feed_submission_result(feed_submission_id) + response[:AmazonEnvelope].should_not be_nil + response[:AmazonEnvelope][:Message][:MessageID].should == "1" + response[:AmazonEnvelope][:Message][:ProcessingReport][:DocumentTransactionID].should == feed_submission_id + response[:AmazonEnvelope][:Message][:ProcessingReport][:StatusCode].should == "Complete" + response[:AmazonEnvelope][:Message][:ProcessingReport][:ProcessingSummary].should_not be_nil + response[:AmazonEnvelope][:Message][:ProcessingReport][:ProcessingSummary].should_not be_nil + response[:AmazonEnvelope][:Message][:ProcessingReport][:ProcessingSummary][:MessagesWithError].should == "1" + response[:AmazonEnvelope][:Message][:ProcessingReport][:Result].first[:ResultCode].should == "Error" + response[:AmazonEnvelope][:Message][:ProcessingReport][:Result].first[:ResultMessageCode].should_not be_nil + response[:AmazonEnvelope][:Message][:ProcessingReport][:Result].first[:ResultDescription].should_not be_nil + end + + end + + + + describe "submit_feed" do + it "should be able to ack an order" do + response = mws.feeds.submit_feed(MWS::API::Feed::ORDER_ACK, order_hash) + response.feed_submission_info.should_not be_nil + + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::ORDER_ACK + end + + it "should create the correct body for an order" do + MWS::API::Feed.should_receive(:post) do |uri, hash| + hash.should include(:body) + body = hash[:body] + body_doc = Nokogiri.parse(body) + body_doc.css('AmazonEnvelope Header MerchantIdentifier').text.should == "doma" + body_doc.css('AmazonEnvelope MessageType').text.should == "OrderAcknowledgement" + body_doc.css('AmazonEnvelope Message OrderAcknowledgement').should_not be_empty + body_doc.css('AmazonEnvelope Message OrderAcknowledgement AmazonOrderID').text.should == order_ack_order_id + body_doc.css('AmazonEnvelope Message OrderAcknowledgement MerchantOrderID').text.should == merchant_order_id + body_doc.css('AmazonEnvelope Message OrderAcknowledgement StatusCode')[0].text.should == "Success" + body_doc.css('AmazonEnvelope Message OrderAcknowledgement Item').should_not be_empty + body_doc.css('AmazonEnvelope Message OrderAcknowledgement Item AmazonOrderItemCode')[0].text.should == amazon_order_item_code + body_doc.css('AmazonEnvelope Message OrderAcknowledgement Item MerchantOrderItemID')[0].text.should == merchant_order_item_code + body_doc.css('AmazonEnvelope Message OrderAcknowledgement Item Quantity').each do |quantity| + quantity.text.should == "1" + end + + end + + response = mws.feeds.submit_feed(MWS::API::Feed::ORDER_ACK, order_hash) + end + + it "should be able to ack a shipment" do + response = mws.feeds.submit_feed(MWS::API::Feed::SHIP_ACK, shipment_hash) + response.feed_submission_info.should_not be_nil + + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::SHIP_ACK + end + + it "should create the correct body for shipment" do + MWS::API::Feed.should_receive(:post) do |uri, hash| + hash.should include(:body) + body = hash[:body] + + body_doc = Nokogiri.parse(body) + ids = body_doc.css('AmazonEnvelope Message OrderFulfillment AmazonOrderID') + + ids.should_not be_empty + ids[0].text.should == first_order_id + ids[1].text.should == second_order_id + + body_doc.css('AmazonEnvelope MessageType').length.should == 1 # multiple types was causing problems + body_doc.css('AmazonEnvelope Message OrderFulfillment').should_not be_empty + body_doc.css('AmazonEnvelope Message OrderFulfillment AmazonOrderID').should_not be_empty + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentDate').first.text.should == first_order_fulfillment.to_s + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentDate').last.text.should == orders_fulfillment_date.to_s + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentData').should_not be_empty + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentData CarrierCode').first.text.should == carrier_code + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentData ShippingMethod').first.text.should == shipping_method + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentData ShipperTrackingNumber').count.should == 1 + body_doc.css('AmazonEnvelope Message OrderFulfillment FulfillmentData ShipperTrackingNumber').first.text.should == tracking_number + + body_doc.css('AmazonEnvelope Message Item').should_not be_empty + body_doc.css('AmazonEnvelope Message Item AmazonOrderItemCode')[0].text.should == first_item_code + body_doc.css('AmazonEnvelope Message Item AmazonOrderItemCode')[1].text.should == second_item_code + body_doc.css('AmazonEnvelope Message Item AmazonOrderItemCode')[2].text.should == third_item_code + body_doc.css('AmazonEnvelope Message Item AmazonOrderItemCode')[3].text.should == fourth_item_code + end + response = mws.feeds.submit_feed(MWS::API::Feed::SHIP_ACK, shipment_hash) + end + + context "#product_image_data" do + it 'should be able to set the image for a product' do + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST_IMAGE, product_price_hash_list) + response.feed_submission_info.should_not be_nil + + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::PRODUCT_LIST_IMAGE + end + + it "should create the correct body for product images" do + MWS::API::Feed.should_receive(:post) do |uri, product_image_hash| + product_image_hash.should include(:body) + body = product_image_hash[:body] + body_doc = Nokogiri.parse(body) + + body_doc.css('AmazonEnvelope MessageType').text.should == "ProductImage" + body_doc.css('AmazonEnvelope PurgeAndReplace').text.should == "false" + body_doc.css('AmazonEnvelope Message MessageID')[0].text.should == "1" + body_doc.css('AmazonEnvelope Message MessageID')[1].text.should == "2" + body_doc.css('AmazonEnvelope Message ProductImage SKU')[0].text.should == "9781320717869" + body_doc.css('AmazonEnvelope Message ProductImage SKU')[1].text.should == "9781320717870" + body_doc.css('AmazonEnvelope Message ProductImage').should_not be_empty + body_doc.css('AmazonEnvelope Message ProductImage ImageType')[0].text.should == "Main" + body_doc.css('AmazonEnvelope Message ProductImage ImageLocation')[0].text.should == "https://www-techinasiacom.netdna-ssl.com/wp-content/uploads/2012/05/funny-cat.jpg" + body_doc.css('AmazonEnvelope Message ProductImage ImageLocation')[1].text.should == "http://www.vetprofessionals.com/catprofessional/images/home-cat.jpg" + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST_IMAGE, product_image_hash_list) + end + end + + context "#product_price_data" do + it 'should be able to set the price' do + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST_PRICE, product_price_hash_list) + response.feed_submission_info.should_not be_nil + + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::PRODUCT_LIST_PRICE + end + + it "should create the correct body for price" do + MWS::API::Feed.should_receive(:post) do |uri, hash_list| + hash_list.should include(:body) + body = hash_list[:body] + body_doc = Nokogiri.parse(body) + + body_doc.css('AmazonEnvelope Message Price SKU').should_not be_empty + body_doc.css('AmazonEnvelope Message MessageID')[0].text.should == "1" + body_doc.css('AmazonEnvelope Message MessageID')[1].text.should == "2" + body_doc.css('AmazonEnvelope Message Price SKU')[0].text.should == "9781320717869" + body_doc.css('AmazonEnvelope Message Price SKU')[1].text.should == "9781320717870" + body_doc.css('AmazonEnvelope MessageType').length.should == 1 + body_doc.css('AmazonEnvelope PurgeAndReplace').text.should == "false" + body_doc.css('AmazonEnvelope Message Price').should_not be_empty + body_doc.css('AmazonEnvelope Message Price StandardPrice')[0].text.should == "290" + body_doc.css('AmazonEnvelope Message Price StandardPrice')[1].text.should == "400" + body_doc.css('AmazonEnvelope Message Price StandardPrice').first.attributes["currency"].value.should == "USD" + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST_PRICE, product_price_hash_list) + end + end + + context "#product_inventory_data" do + it 'should be able to set the inventory' do + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST_INVENTORY, product_inventory_hash_list) + response.feed_submission_info.should_not be_nil + + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::PRODUCT_LIST_INVENTORY + end + + it "should create the correct body for inventory" do + MWS::API::Feed.should_receive(:post) do |uri, hash_list| + hash_list.should include(:body) + body = hash_list[:body] + body_doc = Nokogiri.parse(body) + + body_doc.css('AmazonEnvelope Message MessageID')[0].text.should == "1" + body_doc.css('AmazonEnvelope Message MessageID')[1].text.should == "2" + body_doc.css('AmazonEnvelope Message Inventory SKU')[0].text.should == "9781320717869" + body_doc.css('AmazonEnvelope Message Inventory SKU')[1].text.should == "9781320717870" + body_doc.css('AmazonEnvelope MessageType').length.should == 1 + body_doc.css('AmazonEnvelope PurgeAndReplace').text.should == "false" + body_doc.css('AmazonEnvelope Message Inventory Quantity')[0].text.should == "100" + body_doc.css('AmazonEnvelope Message Inventory Quantity')[1].text.should == "200" + body_doc.css('AmazonEnvelope Message Inventory FulfillmentLatency')[0].text.should == "5" + body_doc.css('AmazonEnvelope Message Inventory FulfillmentLatency')[1].text.should == "5" + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST_INVENTORY, product_inventory_hash_list) + end + end + + context "#product_list_remove" do + it 'should be able to set the inventory' do + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST, product_list_remove_hash_list) + + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::PRODUCT_LIST + end + + it "should create the correct body for product remove" do + MWS::API::Feed.should_receive(:post) do |uri, hash_list| + hash_list.should include(:body) + body = hash_list[:body] + body_doc = Nokogiri.parse(body) + + body_doc.css('AmazonEnvelope Message MessageID').text.should == "1" + body_doc.css('AmazonEnvelope Message Product SKU').text.should == "9781320717869" + body_doc.css('AmazonEnvelope MessageType').length.should == 1 + body_doc.css('AmazonEnvelope Message OperationType').text.should == "Delete" + body_doc.css('AmazonEnvelope PurgeAndReplace').text.should == "false" + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST, product_list_remove_hash_list) + end + + it "should not have additional data" do + MWS::API::Feed.should_receive(:post) do |uri, hash_list| + hash_list.should include(:body) + body = hash_list[:body] + body_doc = Nokogiri.parse(body) + + body_doc.css('AmazonEnvelope Message Product Condition').should be_empty + body_doc.css('AmazonEnvelope Message Product ProductData').should be_empty + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST, product_list_remove_hash_list) + end + end + + context "#product_flat_file_invloader" do + it 'should be able to set the inventory' do + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_FLAT_FILE_INVLOADER, product_price_hash_list) + response.feed_submission_info.should_not be_nil + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::PRODUCT_FLAT_FILE_INVLOADER + end + + it "should create the correct body for inventory" do + MWS::API::Feed.should_receive(:post) do |uri, hash_list| + hash_list.should include(:body) + body = hash_list[:body] + body.should == "TemplateType=InventoryLoader\tVersion=2014.0415\nSKU\tWill Ship Internationally\nitem_sku\twill_ship_internationally\n9781320717869\ty\n9781320717870\ty\n" + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_FLAT_FILE_INVLOADER, product_price_hash_list) + end + end + + it 'should be able to ack the product' do + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST, product_hash_list) + response.feed_submission_info.should_not be_nil + info = response.feed_submission_info + info.feed_processing_status.should == "_SUBMITTED_" + info.feed_type.should == MWS::API::Feed::PRODUCT_LIST + end + + it "should create the correct body for product" do + MWS::API::Feed.should_receive(:post) do |uri, hash_list| + hash_list.should include(:body) + body = hash_list[:body] + body_doc = Nokogiri.parse(body) + ids = body_doc.css('AmazonEnvelope Message Product SKU') + ids.should_not be_empty + ids[0].text.should == first_item_code + ids[1].text.should == second_item_code + + body_doc.css('AmazonEnvelope MessageType').length.should == 1 # multiple types was causing problems + body_doc.css('AmazonEnvelope PurgeAndReplace').text.should == "false" + body_doc.css('AmazonEnvelope Message Product').should_not be_empty + body_doc.css('AmazonEnvelope Message Product SKU').should_not be_empty + body_doc.css('AmazonEnvelope Message Product StandardProductID Value')[0].text.should == first_item_code + body_doc.css('AmazonEnvelope Message Product Condition ConditionType')[0].text.should == "New" + body_doc.css('AmazonEnvelope Message Product DescriptionData Title')[0].text.should == "The Hobbit 1" + body_doc.css('AmazonEnvelope Message Product DescriptionData Title')[1].text.should == "The Hobbit 2" + body_doc.css('AmazonEnvelope Message Product DescriptionData Manufacturer')[0].text.should == "Blurb" + body_doc.css('AmazonEnvelope Message Product DescriptionData MSRP')[0].text.should == "290" + body_doc.css('AmazonEnvelope Message Product DescriptionData MSRP')[1].text.should == "390" + body_doc.css('AmazonEnvelope Message Product DescriptionData MSRP').first.attributes["currency"].value.should == "USD" + body_doc.css('AmazonEnvelope Message Product DescriptionData SearchTerms').length.should == 5 + body_doc.css('AmazonEnvelope Message Product DescriptionData SearchTerms').first.text.should == "layne mcdonald" + body_doc.css('AmazonEnvelope Message Product DescriptionData SearchTerms').last.text.should == "friendship" + body_doc.css('AmazonEnvelope Message Product DescriptionData PackageDimensions Length')[0].text.should == "14" + body_doc.css('AmazonEnvelope Message Product DescriptionData PackageDimensions Width')[0].text.should == "12" + body_doc.css('AmazonEnvelope Message Product DescriptionData PackageDimensions Height')[0].text.should == "1" + body_doc.css('AmazonEnvelope Message Product DescriptionData PackageDimensions Length').first.attributes["unitOfMeasure"].value.should == "IN" + body_doc.css('AmazonEnvelope Message Product ProductData Books ProductType Author')[0].text.should == "J. R. R. Tolkien" + body_doc.css('AmazonEnvelope Message Product ProductData Books ProductType Binding')[0].text.should == "Hardcover" + end + response = mws.feeds.submit_feed(MWS::API::Feed::PRODUCT_LIST, product_hash_list) + end + end + + describe "callback block" do + let(:response_callback) {double("response_callback", :call => nil)} + let(:request_callback) {double("request_callback", :call => nil)} + let(:mws) {MWS.new(auth_params.merge(:response_callback => response_callback, + :request_callback => request_callback))} + + it "should be called on list order items" do + expect(response_callback).to receive(:call) + expect(request_callback).to receive(:call) + response = mws.feeds.submit_feed(MWS::API::Feed::ORDER_ACK, { + :amazon_order_id => '105-1063273-7151427' + }) + + end + end + end +end diff --git a/spec/ruby-mws/api/order_spec.rb b/spec/ruby-mws/api/order_spec.rb index d5aed1a..23dc8c3 100644 --- a/spec/ruby-mws/api/order_spec.rb +++ b/spec/ruby-mws/api/order_spec.rb @@ -4,13 +4,14 @@ before :all do EphemeralResponse.activate - @mws = MWS.new(auth_params) end context "requests" do + let (:mws) {MWS.new(auth_params)} + describe "list_orders" do it "should receive a list of orders" do - orders = @mws.orders.list_orders :last_updated_after => "2012-01-15T13:07:26-05:00" , + orders = mws.orders.list_orders :last_updated_after => "2012-01-15T13:07:26-05:00" , :timestamp => timestamp orders.orders.should be_an_instance_of Array orders.orders.first.should have_key :amazon_order_id @@ -19,7 +20,7 @@ describe "list_orders_by_next_token" do it "should receive a list_orders_by_next_token_result" do - orders = @mws.orders.list_orders_by_next_token :timestamp => timestamp, + orders = mws.orders.list_orders_by_next_token :timestamp => timestamp, :next_token => "zwR7fTkqiKp/YYuZQbKv1QafEPREmauvizt1MIhPYZZl3LSdPSOgN1byEfyVqilNBpcRD1uxgRxTg2dsYWmzKd8ytOVgN7d/KyNtf5fepe2OEd7gVZif6X81/KsTyqd1e64rGQd1TyCS68vI7Bqws+weLxD7b1CVZciW6QBe7o2FizcdzSXGSiKsUkM4/6QGllhc4XvGqg5e0zIeaOVNezxWEXvdeDL7eReo//Hc3LMRF18hF5ZYNntoCB8irbDGcVkxA+q0fZYwE7+t4NjazyEZY027dXAVTSGshRBy6ZTthhfBGj6Dwur8WCwcU8Vc25news0zC1w6gK1h3EdXw7a3u+Q12Uw9ZROnI57RGr4CrtRODNGKSRdGvNrxcHpI2aLZKrJa2MgKRa+KsojCckrDiHqb8mBEJ88g6izJN42dQcLTGQRwBej+BBOOHYP4" orders.orders.should be_an_instance_of Array orders.orders.first.should have_key :amazon_order_id @@ -28,7 +29,7 @@ describe "get_order" do it "should return one order for one order id" do - order = @mws.orders.get_order :amazon_order_id => "102-4850183-7065809", + order = mws.orders.get_order :amazon_order_id => "102-4850183-7065809", :timestamp => timestamp order.should have_key :orders order.orders.should be_an_instance_of Array @@ -36,7 +37,7 @@ end it "should return multiple orders for multiple order ids" do - orders = @mws.orders.get_order :amazon_order_id => ["102-4850183-7065809", "002-3400187-5292203"], + orders = mws.orders.get_order :amazon_order_id => ["102-4850183-7065809", "002-3400187-5292203"], :timestamp => timestamp orders.orders.should be_an_instance_of Array orders.orders.size.should == 2 @@ -46,7 +47,7 @@ describe "list_order_items" do it "should return a list of items for an order" do - order = @mws.orders.list_order_items :amazon_order_id => "102-4850183-7065809", + order = mws.orders.list_order_items :amazon_order_id => "102-4850183-7065809", :timestamp => timestamp order.should have_key :amazon_order_id order.should have_key :order_items @@ -55,8 +56,37 @@ end describe "list_order_items_by_next_token" do + end + + describe "callback block" do + let(:response_callback) {double("response_callback", :call => nil)} + let(:request_callback) {double("request_callback", :call => nil)} + let(:mws) {MWS.new(auth_params.merge(:response_callback => response_callback, + :request_callback => request_callback))} + let(:order_id) { + # just grab one order id + # if this doesn't work most things will fail + orders = mws.orders.list_orders :last_updated_after => "2012-01-15T13:07:26-05:00" + orders.orders.first.amazon_order_id + } + + it "should be called on list order items" do + expect(response_callback).to receive(:call) + expect(request_callback).to receive(:call) + mws.orders.list_order_items :amazon_order_id => order_id + end + it "should be called on list orders" do + expect(response_callback).to receive(:call) + expect(request_callback).to receive(:call) + mws.orders.list_orders :last_updated_after => "2012-01-15T13:07:26-05:00" + end + + it "should be called on list order items" do + expect(response_callback).to receive(:call) + expect(request_callback).to receive(:call) + mws.orders.list_order_items :amazon_order_id => order_id + end end end - -end \ No newline at end of file +end diff --git a/spec/ruby-mws/api/query_spec.rb b/spec/ruby-mws/api/query_spec.rb index 49c1b79..58847c2 100644 --- a/spec/ruby-mws/api/query_spec.rb +++ b/spec/ruby-mws/api/query_spec.rb @@ -10,14 +10,25 @@ it "should assemble the canonical query" do @query.canonical.should == %Q{GET mws.amazonservices.com -/Orders/2011-01-01 -AWSAccessKeyId=#{default_params[:aws_access_key_id]}&Action=ListOrders&LastUpdatedAfter=2012-01-13T15%3A48%3A55-05%3A00&MarketplaceId.Id.1=#{default_params[:marketplace_id]}&SellerId=#{default_params[:seller_id]}&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-01-14T15%3A48%3A55-05%3A00&Version=2011-01-01} +/Orders/2013-09-01 +AWSAccessKeyId=#{default_params[:aws_access_key_id]}&Action=ListOrders&LastUpdatedAfter=2012-01-13T15%3A48%3A55-05%3A00&MarketplaceId.Id.1=#{default_params[:marketplace_id]}&SellerId=#{default_params[:seller_id]}&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-01-14T15%3A48%3A55-05%3A00&Version=2013-09-01} end end context ".request_uri" do it "should assemble the request uri" do - @query.request_uri.should == "https://mws.amazonservices.com/Orders/2011-01-01?AWSAccessKeyId=#{default_params[:aws_access_key_id]}&Action=ListOrders&LastUpdatedAfter=2012-01-13T15%3A48%3A55-05%3A00&MarketplaceId.Id.1=#{default_params[:marketplace_id]}&SellerId=#{default_params[:seller_id]}&Signature=SIGNATURE&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-01-14T15%3A48%3A55-05%3A00&Version=2011-01-01" + @query.request_uri.should == "https://mws.amazonservices.com/Orders/2013-09-01?AWSAccessKeyId=#{default_params[:aws_access_key_id]}&Action=ListOrders&LastUpdatedAfter=2012-01-13T15%3A48%3A55-05%3A00&MarketplaceId.Id.1=#{default_params[:marketplace_id]}&SellerId=#{default_params[:seller_id]}&Signature=SIGNATURE&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-01-14T15%3A48%3A55-05%3A00&Version=2013-09-01" + end + end + + context ".safe_params" do + it "should remove all the unsafe params" do + @query.safe_params.keys.should_not include(:mods, :aws_access_key_id, :seller_id, :secret_access_key) + end + + it "should also remove unsafe params if the keys are strings" do + query = MWS::API::Query.new(default_params.merge("mods" => "something")) + query.safe_params.keys.should_not include("mods") end end @@ -26,8 +37,8 @@ def default_params params = { :last_updated_after => "2012-01-13T15:48:55-05:00", :verb => :get, - :uri => "/Orders/2011-01-01", - :version => "2011-01-01", + :uri => "/Orders/2013-09-01", + :version => "2013-09-01", :host => "mws.amazonservices.com", :action => "ListOrders", :signature_method => "HmacSHA256", diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7738559..10b9eb2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,4 +36,4 @@ def timestamp end class TestWorksError < StandardError -end \ No newline at end of file +end