diff --git a/.rubocop.yml b/.rubocop.yml index 37c92dd..48b1f82 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,6 +5,7 @@ AllCops: # directories or files. Exclude: - db/**/* + - vendor/**/* TargetRubyVersion: 2.4.1 # Cop names are not displayed in offense messages by default. Change behavior # by overriding DisplayCopNames, or by giving the `-D/--display-cop-names` @@ -16,4 +17,8 @@ AllCops: DisplayStyleGuide: true Style/NumericLiterals: - Enabled: false \ No newline at end of file + Enabled: false + +Style/BlockDelimiters: + Exclude: + - spec/**/* diff --git a/app/controllers/amazon_search_controller.rb b/app/controllers/amazon_search_controller.rb index 970e76b..785013b 100644 --- a/app/controllers/amazon_search_controller.rb +++ b/app/controllers/amazon_search_controller.rb @@ -9,7 +9,7 @@ class AmazonSearchController < ApplicationController def show authorize :amazon_search, :show? - @response = amazon_client.search_response + @response = amazon_search_response end def new @@ -18,9 +18,11 @@ def new private - def amazon_client - AmazonProductAPI::HTTPClient.new(query: params[:query], - page_num: params[:page_num] || 1) + def amazon_search_response + client = AmazonProductAPI::HTTPClient.new + query = client.item_search(query: params[:query], + page: params[:page_num] || 1) + query.response end def set_wishlist diff --git a/lib/amazon_product_api/http_client.rb b/lib/amazon_product_api/http_client.rb index cca66f9..427b228 100644 --- a/lib/amazon_product_api/http_client.rb +++ b/lib/amazon_product_api/http_client.rb @@ -1,56 +1,31 @@ # frozen_string_literal: true -require 'amazon_product_api/search_response' +require 'amazon_product_api/item_search_endpoint' +require 'amazon_product_api/item_lookup_endpoint' module AmazonProductAPI - # Responsible for building and executing the query to the Amazon Product API. + # Responsible for managing all Amazon Product API queries. # - # Any logic relating to endpoints, building the query string, authentication - # signatures, etc. should live in this class. + # All endpoints (returning query objects) should live in this class. class HTTPClient - require 'httparty' - require 'time' - require 'uri' - require 'openssl' - require 'base64' + attr_reader :env # injectable credentials - # The region you are interested in - ENDPOINT = 'webservices.amazon.com' - REQUEST_URI = '/onca/xml' - - attr_reader :env - attr_writer :query, :page_num - - def initialize(query:, page_num: 1, env: ENV) - @query = query - @page_num = page_num + def initialize(env: ENV) @env = env assign_env_vars end - # Generate the signed URL - def url - raise InvalidQueryError unless query && page_num - - "http://#{ENDPOINT}#{REQUEST_URI}" + # base - "?#{canonical_query_string}" + # query - "&Signature=#{uri_escape(signature)}" # signature - end - - # Performs the search query and returns the resulting SearchResponse - def search_response(http: HTTParty) - response = get(http: http) - SearchResponse.new parse_response(response) + def item_search(query:, page: 1) + ItemSearchEndpoint.new(query, page, aws_credentials) end - # Send the HTTP request - def get(http: HTTParty) - http.get(url) + def item_lookup(asin) + ItemLookupEndpoint.new(asin, aws_credentials) end private - attr_reader :query, :page_num, :aws_credentials + attr_reader :aws_credentials def assign_env_vars @aws_credentials = AWSCredentials.new(env['AWS_ACCESS_KEY'], @@ -61,54 +36,6 @@ def assign_env_vars "they're set." raise InvalidQueryError, msg unless @aws_credentials.present? end - - def parse_response(response) - Hash.from_xml(response.body) - end - - def uri_escape(phrase) - URI.encode_www_form_component(phrase.to_s) - end - - def params - params = { - 'Service' => 'AWSECommerceService', - 'Operation' => 'ItemSearch', - 'AWSAccessKeyId' => aws_credentials.access_key, - 'AssociateTag' => aws_credentials.associate_tag, - 'SearchIndex' => 'All', - 'Keywords' => query.to_s, - 'ResponseGroup' => 'ItemAttributes,Offers,Images', - 'ItemPage' => page_num.to_s - } - - # Set current timestamp if not set - params['Timestamp'] ||= Time.now.gmtime.iso8601 - params - end - - # Generate the canonical query - def canonical_query_string - params.sort - .map { |key, value| "#{uri_escape(key)}=#{uri_escape(value)}" } - .join('&') - end - - # Generate the string to be signed - def string_to_sign - "GET\n#{ENDPOINT}\n#{REQUEST_URI}\n#{canonical_query_string}" - end - - # Generate the signature required by the Product Advertising API - def signature - Base64.encode64(digest_with_key(string_to_sign)).strip - end - - def digest_with_key(string) - OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), - aws_credentials.secret_key, - string) - end end # Wrapper object to store/verify AWS credentials diff --git a/lib/amazon_product_api/item_lookup_endpoint.rb b/lib/amazon_product_api/item_lookup_endpoint.rb new file mode 100644 index 0000000..087d597 --- /dev/null +++ b/lib/amazon_product_api/item_lookup_endpoint.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'amazon_product_api/lookup_response' + +module AmazonProductAPI + # Responsible for looking up an item listing on Amazon + # + # http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemLookup.html + # + # Any logic relating to lookup, building the query string, authentication + # signatures, etc. should live in this class. + class ItemLookupEndpoint + require 'httparty' + require 'time' + require 'uri' + require 'openssl' + require 'base64' + + # The region you are interested in + ENDPOINT = 'webservices.amazon.com' + REQUEST_URI = '/onca/xml' + + attr_accessor :asin, :aws_credentials + + def initialize(asin, aws_credentials) + @asin = asin + @aws_credentials = aws_credentials + end + + # Generate the signed URL + def url + "http://#{ENDPOINT}#{REQUEST_URI}" + # base + "?#{canonical_query_string}" + # query + "&Signature=#{uri_escape(signature)}" # signature + end + + # Send the HTTP request + def get(http: HTTParty) + http.get(url) + end + + # Performs the search query and returns the resulting SearchResponse + def response(http: HTTParty, logger: Rails.logger) + response = parse_response get(http: http) + logger.debug(response) + LookupResponse.new(response).item + end + + private + + def parse_response(response) + Hash.from_xml(response.body) + end + + # Generate the signature required by the Product Advertising API + def signature + Base64.encode64(digest_with_key(string_to_sign)).strip + end + + # Generate the string to be signed + def string_to_sign + "GET\n#{ENDPOINT}\n#{REQUEST_URI}\n#{canonical_query_string}" + end + + # Generate the canonical query + def canonical_query_string + params.sort + .map { |key, value| "#{uri_escape(key)}=#{uri_escape(value)}" } + .join('&') + end + + def params + params = { + 'Service' => 'AWSECommerceService', + 'AWSAccessKeyId' => aws_credentials.access_key, + 'AssociateTag' => aws_credentials.associate_tag, + # endpoint-specific + 'Operation' => 'ItemLookup', + 'ResponseGroup' => 'ItemAttributes,Offers,Images', + 'ItemId' => asin.to_s + } + + # Set current timestamp if not set + params['Timestamp'] ||= Time.now.gmtime.iso8601 + params + end + + def digest_with_key(string) + OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), + aws_credentials.secret_key, + string) + end + + def uri_escape(phrase) + CGI.escape(phrase.to_s) + end + end +end diff --git a/lib/amazon_product_api/item_search_endpoint.rb b/lib/amazon_product_api/item_search_endpoint.rb new file mode 100644 index 0000000..7230cfa --- /dev/null +++ b/lib/amazon_product_api/item_search_endpoint.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'amazon_product_api/search_response' + +module AmazonProductAPI + # Responsible for building and executing an Amazon Product API search query. + # + # http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemSearch.html + # + # Any logic relating to searching, building the query string, authentication + # signatures, etc. should live in this class. + class ItemSearchEndpoint + require 'httparty' + require 'time' + require 'uri' + require 'openssl' + require 'base64' + + # The region you are interested in + ENDPOINT = 'webservices.amazon.com' + REQUEST_URI = '/onca/xml' + + attr_accessor :query, :page, :aws_credentials + + def initialize(query, page, aws_credentials) + @query = query + @page = page + @aws_credentials = aws_credentials + end + + # Generate the signed URL + def url + raise InvalidQueryError unless query && page + + "http://#{ENDPOINT}#{REQUEST_URI}" + # base + "?#{canonical_query_string}" + # query + "&Signature=#{uri_escape(signature)}" # signature + end + + # Send the HTTP request + def get(http: HTTParty) + http.get(url) + end + + # Performs the search query and returns the resulting SearchResponse + def response(http: HTTParty, logger: Rails.logger) + response = parse_response get(http: http) + logger.debug response + SearchResponse.new response + end + + private + + def parse_response(response) + Hash.from_xml(response.body) + end + + # Generate the signature required by the Product Advertising API + def signature + Base64.encode64(digest_with_key(string_to_sign)).strip + end + + # Generate the string to be signed + def string_to_sign + "GET\n#{ENDPOINT}\n#{REQUEST_URI}\n#{canonical_query_string}" + end + + # Generate the canonical query + def canonical_query_string + params.sort + .map { |key, value| "#{uri_escape(key)}=#{uri_escape(value)}" } + .join('&') + end + + def params + params = { + 'Service' => 'AWSECommerceService', + 'AWSAccessKeyId' => aws_credentials.access_key, + 'AssociateTag' => aws_credentials.associate_tag, + # endpoint-specific + 'Operation' => 'ItemSearch', + 'ResponseGroup' => 'ItemAttributes,Offers,Images', + 'SearchIndex' => 'All', + 'Keywords' => query.to_s, + 'ItemPage' => page.to_s + } + + # Set current timestamp if not set + params['Timestamp'] ||= Time.now.gmtime.iso8601 + params + end + + def digest_with_key(string) + OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), + aws_credentials.secret_key, + string) + end + + def uri_escape(phrase) + CGI.escape(phrase.to_s) + end + end +end diff --git a/lib/amazon_product_api/lookup_response.rb b/lib/amazon_product_api/lookup_response.rb new file mode 100644 index 0000000..160ddca --- /dev/null +++ b/lib/amazon_product_api/lookup_response.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'amazon_product_api/search_item' + +module AmazonProductAPI + # Parses the Amazon Product API item lookup response + # + # Any logic that involves digging through the response hash should live in + # this class. By isolating it from the rest of the codebase, we only have one + # file to touch if the API response changes. + class LookupResponse + def initialize(response_hash) + @hash = item_hash_for response_hash + end + + def item(item_class: SearchItem) + item_class.new(**item_attrs) + end + + private + + attr_reader :hash + + def item_attrs + { + asin: hash['ASIN'], + + price_cents: hash.dig('ItemAttributes', 'ListPrice', 'Amount').to_i, + + image_url: hash.dig('SmallImage', 'URL') || '', + image_width: hash.dig('SmallImage', 'Width') || '', + image_height: hash.dig('SmallImage', 'Height') || '', + + title: hash.dig('ItemAttributes', 'Title'), + detail_page_url: hash['DetailPageURL'] + } + end + + def item_hash_for(response_hash) + response_hash.dig('ItemLookupResponse', 'Items', 'Item') || [] + end + end +end diff --git a/lib/amazon_product_api/search_item.rb b/lib/amazon_product_api/search_item.rb index d090d39..e125a99 100644 --- a/lib/amazon_product_api/search_item.rb +++ b/lib/amazon_product_api/search_item.rb @@ -17,6 +17,17 @@ def initialize(**attrs) attrs[:image_height]) end + def update_hash + { + name: title, + price_cents: price_cents, + amazon_url: detail_page_url, + image_url: image_url, + image_height: image_height, + image_width: image_width + } + end + def image_url image.url end diff --git a/lib/amazon_product_api/search_response.rb b/lib/amazon_product_api/search_response.rb index 563d9d9..b2e2129 100644 --- a/lib/amazon_product_api/search_response.rb +++ b/lib/amazon_product_api/search_response.rb @@ -9,12 +9,12 @@ module AmazonProductAPI # this class. By isolating it from the rest of the codebase, we only have one # file to touch if the API response changes. class SearchResponse - def initialize(hash) - @hash = hash + def initialize(response_hash) + @response_hash = response_hash end def num_pages - (hash.dig('ItemSearchResponse', 'Items', 'TotalPages') || '1').to_i + (response_hash.dig('ItemSearchResponse', 'Items', 'TotalPages') || 1).to_i end def items(item_class: SearchItem) @@ -23,7 +23,7 @@ def items(item_class: SearchItem) private - attr_reader :hash + attr_reader :response_hash def item_attrs_from(hash) { @@ -42,7 +42,7 @@ def item_attrs_from(hash) end def item_hashes - hash.dig('ItemSearchResponse', 'Items', 'Item') || [] + response_hash.dig('ItemSearchResponse', 'Items', 'Item') || [] end end end diff --git a/spec/fixtures/files/amazon_corgi_lookup_response.xml b/spec/fixtures/files/amazon_corgi_lookup_response.xml new file mode 100644 index 0000000..e6840c0 --- /dev/null +++ b/spec/fixtures/files/amazon_corgi_lookup_response.xml @@ -0,0 +1 @@ +
request_id0.0579544460000000
TrueASINB00TFT77ZSItemAttributesOffersImagesAllB00TFT77ZShttps://www.amazon.com/Douglas-1713-Toys-Louie-Corgi/dp/B00TFT77ZS?SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B00TFT77ZSTechnical Detailshttps://www.amazon.com/Douglas-1713-Toys-Louie-Corgi/dp/tech-data/B00TFT77ZS?SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSAdd To Baby Registryhttps://www.amazon.com/gp/registry/baby/add-item.html?asin.0=B00TFT77ZS&SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSAdd To Wedding Registryhttps://www.amazon.com/gp/registry/wedding/add-item.html?asin.0=B00TFT77ZS&SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSAdd To Wishlisthttps://www.amazon.com/gp/registry/wishlist/add-item.html?asin.0=B00TFT77ZS&SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSTell A Friendhttps://www.amazon.com/gp/pdp/taf/B00TFT77ZS?SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSAll Customer Reviewshttps://www.amazon.com/review/product/B00TFT77ZS?SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSAll Offershttps://www.amazon.com/gp/offer-listing/B00TFT77ZS?SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZShttps://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL75_.jpg7075https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL160_.jpg149160https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL.jpg209225https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL30_.jpg2830https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL75_.jpg7075https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL75_.jpg7075https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL110_.jpg102110https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL._SL160_.jpg149160https://images-na.ssl-images-amazon.com/images/I/21GKf0-rrQL.jpg209225ToyDouglas1713E1713076754813527307675481352730793631101207Nicely Detailed High Quality Plush10.5" L, 8" HighSoft Squeezable Piece04729840.25911350USD$13.50Douglas11883617131713135811692557911713ToyTOYS_AND_GAMESDouglasDouglasDouglas Toys Louie Corgi7675481352737675481352737936311012071199USD$11.992800011https://www.amazon.com/gp/offer-listing/B00TFT77ZS?SubscriptionId=aws_access_key&tag=aws_associates_tag&linkCode=xm2&camp=2025&creative=386001&creativeASIN=B00TFT77ZSNew4x7Oo5uqvk0qCuUEHM4YYygKzOHNVAEfIqxMT6kpeU3rsYFtToGyvgze9eCetoXqho5XeicGcoayjtvDG81%2BZHpfQB25TagueCssy7gCF7wigr6ODFGtYtM%2BBvqkA35ABQK52TNl7v%2F2mpjrAprLFc%2FL1%2BIKzcqz1345USD$13.455USD$0.05Usually ships in 24 hoursnow0011
diff --git a/spec/lib/amazon_product_api/http_client_spec.rb b/spec/lib/amazon_product_api/http_client_spec.rb index 2701b01..0ce4c34 100644 --- a/spec/lib/amazon_product_api/http_client_spec.rb +++ b/spec/lib/amazon_product_api/http_client_spec.rb @@ -3,18 +3,19 @@ require 'amazon_product_api/http_client' describe AmazonProductAPI::HTTPClient do - let(:client) do + subject(:client) do env = { 'AWS_ACCESS_KEY' => 'aws_access_key', 'AWS_SECRET_KEY' => 'aws_secret_key', 'AWS_ASSOCIATES_TAG' => 'aws_associates_tag' } - AmazonProductAPI::HTTPClient.new(query: 'corgi', page_num: 5, env: env) + + AmazonProductAPI::HTTPClient.new(env: env) end context 'when credentials are not present' do it 'throws an error' do - expect { AmazonProductAPI::HTTPClient.new(query: 'anything', env: {}) } + expect { AmazonProductAPI::HTTPClient.new(env: {}) } .to raise_error(AmazonProductAPI::InvalidQueryError, 'Environment variables AWS_ACCESS_KEY, AWS_SECRET_KEY, and ' \ "AWS_ASSOCIATES_TAG are required values. Please make sure they're set.") @@ -22,69 +23,43 @@ end describe '#env' do - before do + before { allow(ENV).to receive(:[]).with('AWS_ACCESS_KEY') { '' } allow(ENV).to receive(:[]).with('AWS_SECRET_KEY') { '' } allow(ENV).to receive(:[]).with('AWS_ASSOCIATES_TAG') { '' } - end - subject { AmazonProductAPI::HTTPClient.new(query: 'anything').env } + } + subject { AmazonProductAPI::HTTPClient.new.env } it 'defaults to the ENV object' do expect(subject).to be ENV end end - describe '#url' do - subject(:url) { client.url } + # Verify the endpoints - it { should start_with 'http://webservices.amazon.com/onca/xml' } - it { should include 'AWSAccessKeyId=aws_access_key' } - it { should include 'AssociateTag=aws_associates_tag' } - it { should include 'ItemPage=5' } - it { should include 'Keywords=corgi' } - it { should include 'Operation=ItemSearch' } - it { should include 'ResponseGroup=ItemAttributes%2COffers%2CImages' } - it { should include 'SearchIndex=All' } - it { should include 'Service=AWSECommerceService' } - it { should include 'Timestamp=' } - it { should include 'Signature=' } - - # Edge cases + it { is_expected.to respond_to :item_search } + it { is_expected.to respond_to :item_lookup } + describe '#item_search' do context 'when no query term was provided' do it 'should raise an InvalidQueryError' do - client.query = nil - expect { client.url }.to raise_error AmazonProductAPI::InvalidQueryError + expect { client.item_search }.to raise_error ArgumentError end end context 'when no page number was provided' do it 'should default to page 1' do - client = AmazonProductAPI::HTTPClient.new(query: 'corgi') - expect(client.url).to include 'ItemPage=1' + generated_url = client.item_search(query: 'anything').url + expect(generated_url).to include 'ItemPage=1' end end context 'when the page number is set to nil' do it 'should raise an InvalidQueryError' do - client.page_num = nil - expect { client.url }.to raise_error AmazonProductAPI::InvalidQueryError + expect { + client.item_search(query: 'anything', page: nil).url + }.to raise_error AmazonProductAPI::InvalidQueryError end end end - - describe '#get' do - let(:http_double) { double('http') } - - it 'should make a `get` request to the specified http library' do - expect(http_double).to receive(:get).with(String) - client.get(http: http_double) - end - end - - describe '#search_response', :external do - subject { AmazonProductAPI::HTTPClient.new(query: 'corgi').search_response } - it { should be_a AmazonProductAPI::SearchResponse } - it { should respond_to :items } - end end diff --git a/spec/lib/amazon_product_api/item_lookup_endpoint_spec.rb b/spec/lib/amazon_product_api/item_lookup_endpoint_spec.rb new file mode 100644 index 0000000..c8c9288 --- /dev/null +++ b/spec/lib/amazon_product_api/item_lookup_endpoint_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'amazon_product_api/item_lookup_endpoint' + +describe AmazonProductAPI::ItemLookupEndpoint do + AWSTestCredentials = Struct.new(:access_key, :secret_key, :associate_tag) + + let(:aws_credentials) { + AWSTestCredentials.new('aws_access_key', + 'aws_secret_key', + 'aws_associates_tag') + } + let(:query) { + AmazonProductAPI::ItemLookupEndpoint.new('corgi_asin', aws_credentials) + } + + describe '#url' do + subject(:url) { query.url } + + it { should start_with 'http://webservices.amazon.com/onca/xml' } + it { should include 'AWSAccessKeyId=aws_access_key' } + it { should include 'AssociateTag=aws_associates_tag' } + it { should include 'Operation=ItemLookup' } + it { should include 'ItemId=corgi_asin' } + it { should include 'ResponseGroup=ItemAttributes%2COffers%2CImages' } + it { should include 'Service=AWSECommerceService' } + it { should include 'Timestamp=' } + it { should include 'Signature=' } + end + + describe '#get' do + let(:http_double) { double('http') } + + it 'should make a `get` request to the specified http library' do + expect(http_double).to receive(:get).with(String) + query.get(http: http_double) + end + end + + describe '#response', :external do + subject { query.response } + + it { should be_a AmazonProductAPI::SearchItem } + + it 'should have the item information' do + expect(subject.asin).to eq 'B00TFT77ZS' + expect(subject.title).to eq 'Douglas Toys Louie Corgi' + expect(subject.price_cents).to eq 1350 + end + end +end diff --git a/spec/lib/amazon_product_api/item_search_endpoint_spec.rb b/spec/lib/amazon_product_api/item_search_endpoint_spec.rb new file mode 100644 index 0000000..16ea7c9 --- /dev/null +++ b/spec/lib/amazon_product_api/item_search_endpoint_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'amazon_product_api/item_search_endpoint' + +describe AmazonProductAPI::ItemSearchEndpoint do + AWSTestCredentials = Struct.new(:access_key, :secret_key, :associate_tag) + + let(:aws_credentials) { + AWSTestCredentials.new('aws_access_key', + 'aws_secret_key', + 'aws_associates_tag') + } + let(:query) { + AmazonProductAPI::ItemSearchEndpoint.new('corgi', 5, aws_credentials) + } + + describe '#url' do + subject(:url) { query.url } + + it { should start_with 'http://webservices.amazon.com/onca/xml' } + it { should include 'AWSAccessKeyId=aws_access_key' } + it { should include 'AssociateTag=aws_associates_tag' } + it { should include 'ItemPage=5' } + it { should include 'Keywords=corgi' } + it { should include 'Operation=ItemSearch' } + it { should include 'ResponseGroup=ItemAttributes%2COffers%2CImages' } + it { should include 'SearchIndex=All' } + it { should include 'Service=AWSECommerceService' } + it { should include 'Timestamp=' } + it { should include 'Signature=' } + end + + describe '#get' do + let(:http_double) { double('http') } + + it 'should make a `get` request to the specified http library' do + expect(http_double).to receive(:get).with(String) + query.get(http: http_double) + end + end + + describe '#response', :external do + subject { + query = AmazonProductAPI::ItemSearchEndpoint.new('corgi', 1, + aws_credentials) + query.response + } + it { should respond_to :items } + end +end diff --git a/spec/lib/amazon_product_api/search_item_spec.rb b/spec/lib/amazon_product_api/search_item_spec.rb index fb572ca..eff3903 100644 --- a/spec/lib/amazon_product_api/search_item_spec.rb +++ b/spec/lib/amazon_product_api/search_item_spec.rb @@ -15,15 +15,15 @@ end end - describe "#valid_image?" do - it "returns true for valid data" do - item = AmazonProductAPI::SearchItem.new(image_url: "image url", + describe '#valid_image?' do + it 'returns true for valid data' do + item = AmazonProductAPI::SearchItem.new(image_url: 'image url', image_height: 600, image_width: 800) expect(item.valid_image?).to eq true end - it "returns false for invalid data" do + it 'returns false for invalid data' do item = AmazonProductAPI::SearchItem.new(image_url: nil, image_height: 600, image_width: 800) diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb index 2f41428..ce128c7 100644 --- a/spec/support/webmock.rb +++ b/spec/support/webmock.rb @@ -6,10 +6,18 @@ RSpec.configure do |config| config.before(:each, :external) do + # Item Search search_response = file_fixture('amazon_corgi_search_response.xml').read - stub_request(:get, 'webservices.amazon.com/onca/xml') - .with(query: hash_including('Keywords' => 'corgi')) + .with(query: hash_including('Operation' => 'ItemSearch', + 'Keywords' => 'corgi')) .to_return(body: search_response) + + # Item Lookup + lookup_response = file_fixture('amazon_corgi_lookup_response.xml').read + stub_request(:get, 'webservices.amazon.com/onca/xml') + .with(query: hash_including('Operation' => 'ItemLookup', + 'ItemId' => 'corgi_asin')) + .to_return(body: lookup_response) end end