vacuum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +61 -0
- data/lib/vacuum.rb +23 -0
- data/lib/vacuum/builder.rb +51 -0
- data/lib/vacuum/cart.rb +90 -0
- data/lib/vacuum/cart_operations.rb +12 -0
- data/lib/vacuum/error.rb +13 -0
- data/lib/vacuum/locale.rb +39 -0
- data/lib/vacuum/lookup_operations.rb +62 -0
- data/lib/vacuum/request.rb +131 -0
- data/lib/vacuum/response.rb +85 -0
- data/lib/vacuum/search_operations.rb +43 -0
- data/lib/vacuum/version.rb +3 -0
- data/spec/fixtures/http_response +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/vacuum/builder_spec.rb +38 -0
- data/spec/vacuum/cart_operations_spec.rb +0 -0
- data/spec/vacuum/cart_spec.rb +0 -0
- data/spec/vacuum/lookup_operations_spec.rb +53 -0
- data/spec/vacuum/request_spec.rb +183 -0
- data/spec/vacuum/response_spec.rb +96 -0
- data/spec/vacuum/search_operations_spec.rb +59 -0
- metadata +87 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2010 Hakan Ensari
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Vacuum
|
2
|
+
|
3
|
+
Vacuum is a [Nokogiri][1]-backed Ruby wrapper to the [Amazon Product
|
4
|
+
Advertising API] [2].
|
5
|
+
|
6
|
+
[![travis](https://secure.travis-ci.org/hakanensari/vacuum.png)](http://travis-ci.org/hakanensari/vacuum)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add to your Gemfile.
|
11
|
+
|
12
|
+
gem 'vacuum'
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
Set up a request.
|
17
|
+
|
18
|
+
require "vacuum"
|
19
|
+
|
20
|
+
req = Vacuum["us"]
|
21
|
+
|
22
|
+
req.configure do |c|
|
23
|
+
c.key = AMAZON_KEY
|
24
|
+
c.secret = AMAZON_SECRET
|
25
|
+
c.tag = AMAZON_ASSOCIATE_TAG
|
26
|
+
end
|
27
|
+
|
28
|
+
Search for something.
|
29
|
+
|
30
|
+
req << { :operation => 'ItemSearch',
|
31
|
+
:search_index => 'All',
|
32
|
+
:keywords => 'George Orwell' }
|
33
|
+
res = request.get
|
34
|
+
|
35
|
+
Or use a shorthand.
|
36
|
+
|
37
|
+
res = req.search('George Orwell')
|
38
|
+
|
39
|
+
Customise your request.
|
40
|
+
|
41
|
+
res = req.search('Books', :response_group => 'ItemAttributes',
|
42
|
+
:power => 'George Orwell'
|
43
|
+
|
44
|
+
For a reference of available methods and syntax, [read here] [3].
|
45
|
+
|
46
|
+
Consume the entire response.
|
47
|
+
|
48
|
+
res.to_hash
|
49
|
+
|
50
|
+
Quickly drop down to a particular node.
|
51
|
+
|
52
|
+
res.each('Item') do |item|
|
53
|
+
puts item['ASIN']
|
54
|
+
end
|
55
|
+
|
56
|
+
[Please see the project page] [4] for further detail.
|
57
|
+
|
58
|
+
[1]: http://nokogiri.org/
|
59
|
+
[2]: https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html
|
60
|
+
[3]: https://github.com/hakanensari/vacuum/blob/master/lib/vacuum/operations.rb
|
61
|
+
[4]: http://code.papercavalier.com/vacuum/
|
data/lib/vacuum.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
%w{cart_operations lookup_operations search_operations builder cart
|
6
|
+
error locale request response}.each do |f|
|
7
|
+
require "vacuum/#{f}"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Vacuum is a Ruby wrapper to the Amazon Product Advertising
|
11
|
+
# API.
|
12
|
+
module Vacuum
|
13
|
+
@requests = Hash.new
|
14
|
+
|
15
|
+
# @param [#to_sym] locale a locale key
|
16
|
+
# @return [Vacuum::Request] a request
|
17
|
+
#
|
18
|
+
# @note The locale key may be any of the following: +ca+, +cn+, +de+,
|
19
|
+
# +es+, +fr+, +it+, +jp+, +uk+, or +us+.
|
20
|
+
def self.[](locale)
|
21
|
+
@requests[locale] ||= Request.new(locale)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Builder
|
3
|
+
# Builds a hash from a Nokogiri XML document
|
4
|
+
#
|
5
|
+
# @note In earlier versions of the library, I was relying on the
|
6
|
+
# XML Mini Nokogiri module in Active Support. This method
|
7
|
+
# essentially accomplishes the same.
|
8
|
+
#
|
9
|
+
# @see Based on https://gist.github.com/335286
|
10
|
+
#
|
11
|
+
# @param [Nokogiri::XML::Document] xml an XML document
|
12
|
+
# @return [Hash] a hashified version of the XML document
|
13
|
+
def self.from_xml(xml)
|
14
|
+
case xml
|
15
|
+
when Nokogiri::XML::Document
|
16
|
+
from_xml(xml.root)
|
17
|
+
when Nokogiri::XML::Element
|
18
|
+
hsh = {}
|
19
|
+
|
20
|
+
xml.attributes.each_pair do |key, attr|
|
21
|
+
hsh[key] = attr.value
|
22
|
+
end
|
23
|
+
|
24
|
+
xml.children.each do |child|
|
25
|
+
result = from_xml(child)
|
26
|
+
|
27
|
+
if child.name == 'text'
|
28
|
+
if hsh.empty?
|
29
|
+
return result
|
30
|
+
else
|
31
|
+
hsh['__content__'] = result
|
32
|
+
end
|
33
|
+
elsif hsh[child.name]
|
34
|
+
case hsh[child.name]
|
35
|
+
when Array
|
36
|
+
hsh[child.name] << result
|
37
|
+
else
|
38
|
+
hsh[child.name] = [hsh[child.name]] << result
|
39
|
+
end
|
40
|
+
else
|
41
|
+
hsh[child.name] = result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
hsh
|
46
|
+
else
|
47
|
+
xml.content.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/vacuum/cart.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module Vacuum
|
2
|
+
class Cart
|
3
|
+
# @return [String] cart_id
|
4
|
+
attr :id
|
5
|
+
|
6
|
+
# @return [String] hmac
|
7
|
+
attr :hmac
|
8
|
+
|
9
|
+
attr :items
|
10
|
+
|
11
|
+
# @return [Vacuum::Response] last_response last response
|
12
|
+
# returned by the Amazon API
|
13
|
+
attr :last_response
|
14
|
+
|
15
|
+
# @return [String] purchase_url
|
16
|
+
attr :purchase_url
|
17
|
+
|
18
|
+
attr :sub_total
|
19
|
+
|
20
|
+
# Creates a new cart
|
21
|
+
#
|
22
|
+
# @param [Vacuum::Request] req an API request
|
23
|
+
# @param [Hash] params a hash of parameters
|
24
|
+
def initialize(req, params)
|
25
|
+
@req = req
|
26
|
+
get 'Create', params
|
27
|
+
end
|
28
|
+
|
29
|
+
# Clears the cart
|
30
|
+
#
|
31
|
+
# @param [Hash] params a hash of parameters
|
32
|
+
def clear(params = {})
|
33
|
+
get 'Clear', params
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def get(operation, params)
|
39
|
+
@req.reset!
|
40
|
+
|
41
|
+
if id
|
42
|
+
@req << { 'CartId' => id,
|
43
|
+
'HMAC' => hmac }
|
44
|
+
end
|
45
|
+
|
46
|
+
@req << { 'Operation' => "Cart#{operation}" }.merge(params)
|
47
|
+
|
48
|
+
@last_response = @req.get
|
49
|
+
@items = @last_response.find('CartItems')
|
50
|
+
@id = @last_response.find('CartId').first
|
51
|
+
@hmac = @last_response.find('HMAC').first
|
52
|
+
@purchase_url = @last_response.find('PurchaseURL').first
|
53
|
+
@sub_total = @last_response.find('SubTotal').first
|
54
|
+
end
|
55
|
+
|
56
|
+
# Add items to cart
|
57
|
+
#
|
58
|
+
# @param [String] cart_id
|
59
|
+
# @param [String] hmac
|
60
|
+
# @param [Hash] params
|
61
|
+
# @return [Vacuum::Cart] a response
|
62
|
+
# def add_to_cart(cart_id, hmac, params)
|
63
|
+
# cartify 'Add', { 'CartId' => cart_id,
|
64
|
+
# 'HMAC' => hmac }.merge(params)
|
65
|
+
# end
|
66
|
+
|
67
|
+
|
68
|
+
# Gets an existing cart
|
69
|
+
#
|
70
|
+
# @param [String] cart_id
|
71
|
+
# @param [String] hmac
|
72
|
+
# @param [Hash] params
|
73
|
+
# @return [Vacuum::Cart] a response
|
74
|
+
# def get_cart(cart_id, hmac, params)
|
75
|
+
# cartify 'Get', { 'CartId' => cart_id,
|
76
|
+
# 'HMAC' => hmac }.merge(params)
|
77
|
+
# end
|
78
|
+
|
79
|
+
# Modifies an existing cart
|
80
|
+
#
|
81
|
+
# @param [String] cart_id
|
82
|
+
# @param [String] hmac
|
83
|
+
# @param [Hash] params
|
84
|
+
# @return [Vacuum::Cart] a response
|
85
|
+
# def modify_cart(cart_id, hmac, params)
|
86
|
+
# cartify 'Modify', { 'CartId' => cart_id,
|
87
|
+
# 'HMAC' => hmac }.merge(params)
|
88
|
+
# end
|
89
|
+
end
|
90
|
+
end
|
data/lib/vacuum/error.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Vacuum
|
2
|
+
# Raised when a bad locale is specified
|
3
|
+
class BadLocale < ArgumentError; end
|
4
|
+
|
5
|
+
# Raised when the Amazon key is not specified
|
6
|
+
class MissingKey < ArgumentError; end
|
7
|
+
|
8
|
+
# Raised when the Amazon secret is not specified
|
9
|
+
class MissingSecret < ArgumentError; end
|
10
|
+
|
11
|
+
# Raised when the Amazon associate tag is not specified
|
12
|
+
class MissingTag < ArgumentError; end
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Vacuum
|
2
|
+
# An Amazon locale
|
3
|
+
class Locale
|
4
|
+
# Amazon hosts
|
5
|
+
HOSTS = { :ca => 'ecs.amazonaws.ca',
|
6
|
+
:cn => 'webservices.amazon.cn',
|
7
|
+
:de => 'ecs.amazonaws.de',
|
8
|
+
:es => 'webservices.amazon.es',
|
9
|
+
:fr => 'ecs.amazonaws.fr',
|
10
|
+
:it => 'webservices.amazon.it',
|
11
|
+
:jp => 'ecs.amazonaws.jp',
|
12
|
+
:us => 'ecs.amazonaws.com',
|
13
|
+
:uk => 'ecs.amazonaws.co.uk' }
|
14
|
+
|
15
|
+
# Country codes for Amazon locales
|
16
|
+
LOCALES = HOSTS.keys
|
17
|
+
|
18
|
+
# @return [String] the Amazon Web Services access key
|
19
|
+
attr_accessor :key
|
20
|
+
|
21
|
+
# @return [String] the Amazon Web Services secret
|
22
|
+
attr_accessor :secret
|
23
|
+
|
24
|
+
# @return [String] the Amazon associate tag
|
25
|
+
attr_accessor :tag
|
26
|
+
|
27
|
+
# @param [Symbol] locale the locale key
|
28
|
+
# @raise [Vacuum::BadLocale] locale is bad
|
29
|
+
def initialize(locale)
|
30
|
+
raise BadLocale unless LOCALES.include?(locale)
|
31
|
+
@locale = locale
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] the Amazon host
|
35
|
+
def host
|
36
|
+
HOSTS[@locale]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Vacuum
|
2
|
+
# Lookup operations
|
3
|
+
module LookupOperations
|
4
|
+
# Given up to ten item ids, returns some or all of the item
|
5
|
+
# attributes, depending on the response group specified in the
|
6
|
+
# request.
|
7
|
+
#
|
8
|
+
# @param [Array] item_ids splat of item IDs and an optional hash of
|
9
|
+
# parameters
|
10
|
+
# @return [Vacuum::Response] a response
|
11
|
+
#
|
12
|
+
# Id Type defaults to ASIN.
|
13
|
+
#
|
14
|
+
# @example The following returns some basic information for the
|
15
|
+
# ASIN 0679753354.
|
16
|
+
#
|
17
|
+
# req.find('0679753354')
|
18
|
+
#
|
19
|
+
# @example The following request returns cover art for the same
|
20
|
+
# ASIN.
|
21
|
+
#
|
22
|
+
# req.find('0679753354', :response_group => 'Images')
|
23
|
+
#
|
24
|
+
def find(*item_ids)
|
25
|
+
reset!
|
26
|
+
params = item_ids.last.is_a?(Hash) ? item_ids.pop : {}
|
27
|
+
self.<<({ 'Operation' => 'ItemLookup',
|
28
|
+
'ItemId' => item_ids }.merge(params))
|
29
|
+
|
30
|
+
get
|
31
|
+
end
|
32
|
+
|
33
|
+
# Given a browse node ID, returns the specified browse node’s name,
|
34
|
+
# children, and ancestors.
|
35
|
+
#
|
36
|
+
# @param [String] browse_node_id browse node ID
|
37
|
+
# @params [Hash] params hash of parameters
|
38
|
+
# @return [Vacuum::Response] a response
|
39
|
+
def find_browse_node(browse_node_id, params = {})
|
40
|
+
reset!
|
41
|
+
self.<<({ 'Operation' => 'BrowseNodeLookup',
|
42
|
+
'BrowseNodeId' => browse_node_id }.merge(params))
|
43
|
+
|
44
|
+
get
|
45
|
+
end
|
46
|
+
|
47
|
+
# Given up to ten item ids, returns up to ten products per page
|
48
|
+
# that are similar to those items
|
49
|
+
#
|
50
|
+
# @param [Array] item_ids splat of item IDs and an optional hash of
|
51
|
+
# parameters
|
52
|
+
# @return [Vacuum::Response] a response
|
53
|
+
def find_similar(*item_ids)
|
54
|
+
reset!
|
55
|
+
params = item_ids.last.is_a?(Hash) ? item_ids.pop : {}
|
56
|
+
self.<<({ 'Operation' => 'SimilarityLookup',
|
57
|
+
'ItemId' => item_ids }.merge(params))
|
58
|
+
|
59
|
+
get
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Vacuum
|
2
|
+
# A wrapper around the request to the Amazon Product Advertising API
|
3
|
+
class Request
|
4
|
+
include CartOperations
|
5
|
+
include LookupOperations
|
6
|
+
include SearchOperations
|
7
|
+
|
8
|
+
# The latest Amazon API version
|
9
|
+
#
|
10
|
+
# @see http://aws.amazon.com/archives/Product%20Advertising%20API
|
11
|
+
#
|
12
|
+
# @note If you have a whitelisted access key, override this in your
|
13
|
+
# parameters with an earlier version.
|
14
|
+
CURRENT_API_VERSION = '2011-08-01'
|
15
|
+
|
16
|
+
# Creates a new request for specified locale
|
17
|
+
#
|
18
|
+
# @param [#to_sym] locale two-letter abbreviation for locale
|
19
|
+
def initialize(locale)
|
20
|
+
@locale = Locale.new(locale.to_sym)
|
21
|
+
@params = Hash.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Merges a hash of request parameters into the query
|
25
|
+
#
|
26
|
+
# @param [Hash] hsh pairs of parameter keys and values
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# request << { :key => 'value' }
|
30
|
+
#
|
31
|
+
def <<(hsh)
|
32
|
+
hsh.each do |k, v|
|
33
|
+
# Cast value to string.
|
34
|
+
v = v.is_a?(Array) ? v.join(',') : v.to_s
|
35
|
+
|
36
|
+
# Remove whitespace after commas.
|
37
|
+
v.gsub!(/,\s+/, ',')
|
38
|
+
|
39
|
+
# Camelize key.
|
40
|
+
k = k.to_s.
|
41
|
+
split('_').
|
42
|
+
map { |w| w[0, 1] = w[0, 1].upcase; w }.
|
43
|
+
join
|
44
|
+
|
45
|
+
@params[k] = v
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Configures the Amazon locale
|
50
|
+
#
|
51
|
+
# @yield passes locale to block for configuration
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# request.configure do |c|
|
55
|
+
# c.key = YOUR_KEY
|
56
|
+
# c.secret = YOUR_SECRET
|
57
|
+
# c.tag = YOUR_ASSOCIATE_TAG
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
def configure(&block)
|
61
|
+
block.call(@locale)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Performs a request
|
65
|
+
#
|
66
|
+
# @return [Vacuum::Response] a response
|
67
|
+
def get
|
68
|
+
resp = Net::HTTP.get_response(url)
|
69
|
+
|
70
|
+
Response.new(resp.body, resp.code)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Hash] params The request parameters
|
74
|
+
# @raise [Vacuum::MissingKey] Amazon key is missing
|
75
|
+
# @raise [Vacuum::MissingTag] Amazon associate tag is
|
76
|
+
# missing
|
77
|
+
def params
|
78
|
+
raise MissingKey unless @locale.key
|
79
|
+
raise MissingTag unless @locale.tag
|
80
|
+
|
81
|
+
{ 'AWSAccessKeyId' => @locale.key,
|
82
|
+
'AssociateTag' => @locale.tag,
|
83
|
+
'Service' => 'AWSECommerceService',
|
84
|
+
'Timestamp' => timestamp,
|
85
|
+
'Version' => CURRENT_API_VERSION }.merge(@params)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Resets the request parameters
|
89
|
+
def reset!
|
90
|
+
@params = {}
|
91
|
+
end
|
92
|
+
|
93
|
+
# @raise [Vacuum::MissingSecret] Amazon secret is missing
|
94
|
+
# @return [URI::HTTP] the Amazon URL
|
95
|
+
def url
|
96
|
+
raise MissingSecret unless @locale.secret
|
97
|
+
|
98
|
+
URI::HTTP.build(:host => @locale.host,
|
99
|
+
:path => '/onca/xml',
|
100
|
+
:query => sign(query))
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def escape(value)
|
106
|
+
value.gsub(/([^a-zA-Z0-9_.~-]+)/) do
|
107
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def query
|
112
|
+
params.sort.map { |k, v| "#{k}=" + escape(v) }.join('&')
|
113
|
+
end
|
114
|
+
|
115
|
+
def sign(unsigned_query)
|
116
|
+
digest = OpenSSL::Digest::Digest.new('sha256')
|
117
|
+
url_string = ['GET',
|
118
|
+
@locale.host,
|
119
|
+
'/onca/xml',
|
120
|
+
unsigned_query].join("\n")
|
121
|
+
hmac = OpenSSL::HMAC.digest(digest, @locale.secret, url_string)
|
122
|
+
signature = escape([hmac].pack('m').chomp)
|
123
|
+
|
124
|
+
"#{unsigned_query}&Signature=#{signature}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def timestamp
|
128
|
+
Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Vacuum
|
2
|
+
# A wrapper around the API response
|
3
|
+
class Response
|
4
|
+
|
5
|
+
# @return [String] body the response body
|
6
|
+
attr_accessor :body
|
7
|
+
|
8
|
+
# @return [Integer] code the HTTP status code of the response
|
9
|
+
attr_accessor :code
|
10
|
+
|
11
|
+
# Creates a new response
|
12
|
+
#
|
13
|
+
# @param [String] body the response body
|
14
|
+
# @param [#to_i] code the HTTP status code of the response
|
15
|
+
def initialize(body, code)
|
16
|
+
@body = body
|
17
|
+
@code = code.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
# Queries for a specified attribute and yields to a given block
|
21
|
+
# each matching document
|
22
|
+
#
|
23
|
+
# @param [String] query attribute to be queried
|
24
|
+
# @yield passes matching nodes to given block
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# resp.each('Item') { |item| p item }
|
28
|
+
#
|
29
|
+
def each(query, &block)
|
30
|
+
find(query).each { |match| block.call(match) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array] errors in the response
|
34
|
+
def errors
|
35
|
+
find('Error')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Queries for a specified attribute and returns matching nodes
|
39
|
+
#
|
40
|
+
# @param [String] query attribute to be queried
|
41
|
+
# @return [Array] matching nodes
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# items = resp.find('Item')
|
45
|
+
#
|
46
|
+
def find(query)
|
47
|
+
xml.xpath("//xmlns:#{query}").map { |e| Builder.from_xml(e) }
|
48
|
+
end
|
49
|
+
alias [] find
|
50
|
+
|
51
|
+
# @return [true, false] checks if the response has errors
|
52
|
+
def has_errors?
|
53
|
+
errors.count > 0
|
54
|
+
end
|
55
|
+
|
56
|
+
# Queries for a specifed attribute, yields to a given block
|
57
|
+
# matching nodes, and collects final values.
|
58
|
+
#
|
59
|
+
# @param [String] query attribute to be queried
|
60
|
+
# @yield passes matching nodes to given block
|
61
|
+
# @return [Array] processed results
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# asins = resp.map('Item') { |item| item['ASIN'] }
|
65
|
+
#
|
66
|
+
def map(path, &block)
|
67
|
+
find(path).map { |match| block.call(match) }
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Hash] a hashified version of the response body
|
71
|
+
def to_hash
|
72
|
+
Builder.from_xml(xml)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [true, false] checks if the HTTP response is OK
|
76
|
+
def valid?
|
77
|
+
code == 200
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Nokogiri::XML] the XML document
|
81
|
+
def xml
|
82
|
+
@xml ||= Nokogiri::XML(@body)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Vacuum
|
2
|
+
# Search operations
|
3
|
+
module SearchOperations
|
4
|
+
# Returns up to ten items that satisfy the search criteria,
|
5
|
+
# including one or more search indices.
|
6
|
+
#
|
7
|
+
# @param [String, nil] search_index search index or keyword query
|
8
|
+
# @param [String, Hash] params keyword query or hash of parameters
|
9
|
+
# @return [Vacuum::Response] a reponse
|
10
|
+
#
|
11
|
+
# @example The following searches the entire Amazon catalog for the
|
12
|
+
# keyword 'book'.
|
13
|
+
#
|
14
|
+
# req.search('book')
|
15
|
+
#
|
16
|
+
# @example The following searches the books search index for the
|
17
|
+
# keyword 'lacan'.
|
18
|
+
#
|
19
|
+
# req.search('Books', 'lacan')
|
20
|
+
#
|
21
|
+
# @example The following runs a power search on the books search
|
22
|
+
# index for non-fiction titles authored by Lacan and sorts results
|
23
|
+
# by Amazon's relevance ranking.
|
24
|
+
#
|
25
|
+
# req.search('Books', :power => 'author:lacan and not fiction',
|
26
|
+
# :sort => 'relevancerank')
|
27
|
+
#
|
28
|
+
def search(search_index, params = nil)
|
29
|
+
reset!
|
30
|
+
if params.nil?
|
31
|
+
params = { 'Keywords' => search_index }
|
32
|
+
search_index = 'All'
|
33
|
+
end
|
34
|
+
if params.is_a? String
|
35
|
+
params = { 'Keywords' => params }
|
36
|
+
end
|
37
|
+
self.<<({ 'Operation' => 'ItemSearch',
|
38
|
+
'SearchIndex' => search_index }.merge(params))
|
39
|
+
|
40
|
+
get
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" ?><ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2011-08-01"><OperationRequest><RequestId>86b89a15-b717-4d95-99aa-fe531b4ca762</RequestId><Arguments><Argument Name="Operation" Value="ItemLookup"></Argument><Argument Name="Service" Value="AWSECommerceService"></Argument><Argument Name="AssociateTag" Value="theorydot08-20"></Argument><Argument Name="Version" Value="2011-08-01"></Argument><Argument Name="Signature" Value="vOT9O1NW8PYLvrUX6KI3jrZ4Fg7LdtEYTrlsWzhbm1k="></Argument><Argument Name="ItemId" Value="0816614024,0143105825"></Argument><Argument Name="IdType" Value="ASIN"></Argument><Argument Name="AWSAccessKeyId" Value="0ZVSQ33MDFPQS8H2PM02"></Argument><Argument Name="Timestamp" Value="2011-07-29T17:52:34Z"></Argument></Arguments><RequestProcessingTime>0.0111990000000000</RequestProcessingTime></OperationRequest><Items><Request><IsValid>True</IsValid><ItemLookupRequest><IdType>ASIN</IdType><ItemId>0816614024</ItemId><ItemId>0143105825</ItemId><ResponseGroup>Small</ResponseGroup><VariationPage>All</VariationPage></ItemLookupRequest></Request><Item><ASIN>0816614024</ASIN><DetailPageURL>http://www.amazon.com/Thousand-Plateaus-Schizophrenia-Gilles-Deleuze/dp/0816614024%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0816614024</DetailPageURL><ItemLinks><ItemLink><Description>Technical Details</Description><URL>http://www.amazon.com/Thousand-Plateaus-Schizophrenia-Gilles-Deleuze/dp/tech-data/0816614024%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink><ItemLink><Description>Add To Baby Registry</Description><URL>http://www.amazon.com/gp/registry/baby/add-item.html%3Fasin.0%3D0816614024%26SubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink><ItemLink><Description>Add To Wedding Registry</Description><URL>http://www.amazon.com/gp/registry/wedding/add-item.html%3Fasin.0%3D0816614024%26SubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink><ItemLink><Description>Add To Wishlist</Description><URL>http://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3D0816614024%26SubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink><ItemLink><Description>Tell A Friend</Description><URL>http://www.amazon.com/gp/pdp/taf/0816614024%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink><ItemLink><Description>All Customer Reviews</Description><URL>http://www.amazon.com/review/product/0816614024%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink><ItemLink><Description>All Offers</Description><URL>http://www.amazon.com/gp/offer-listing/0816614024%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0816614024</URL></ItemLink></ItemLinks><ItemAttributes><Author>Gilles Deleuze</Author><Creator Role="Contributor">Felix Guattari</Creator><Manufacturer>Univ Of Minnesota Press</Manufacturer><ProductGroup>Book</ProductGroup><Title>Thousand Plateaus: Capitalism and Schizophrenia</Title></ItemAttributes></Item><Item><ASIN>0143105825</ASIN><DetailPageURL>http://www.amazon.com/Anti-Oedipus-Capitalism-Schizophrenia-Penguin-Classics/dp/0143105825%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0143105825</DetailPageURL><ItemLinks><ItemLink><Description>Technical Details</Description><URL>http://www.amazon.com/Anti-Oedipus-Capitalism-Schizophrenia-Penguin-Classics/dp/tech-data/0143105825%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink><ItemLink><Description>Add To Baby Registry</Description><URL>http://www.amazon.com/gp/registry/baby/add-item.html%3Fasin.0%3D0143105825%26SubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink><ItemLink><Description>Add To Wedding Registry</Description><URL>http://www.amazon.com/gp/registry/wedding/add-item.html%3Fasin.0%3D0143105825%26SubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink><ItemLink><Description>Add To Wishlist</Description><URL>http://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3D0143105825%26SubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink><ItemLink><Description>Tell A Friend</Description><URL>http://www.amazon.com/gp/pdp/taf/0143105825%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink><ItemLink><Description>All Customer Reviews</Description><URL>http://www.amazon.com/review/product/0143105825%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink><ItemLink><Description>All Offers</Description><URL>http://www.amazon.com/gp/offer-listing/0143105825%3FSubscriptionId%3D0ZVSQ33MDFPQS8H2PM02%26tag%3Dtheorydot08-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0143105825</URL></ItemLink></ItemLinks><ItemAttributes><Author>Gilles Deleuze</Author><Author>Felix Guattari</Author><Creator Role="Translator">Robert Hurley</Creator><Creator Role="Translator">Mark Seem</Creator><Creator Role="Introduction">Mark Seem</Creator><Creator Role="Translator">Helen Lane</Creator><Creator Role="Preface">Michel Foucault</Creator><Manufacturer>Penguin Classics</Manufacturer><ProductGroup>Book</ProductGroup><Title>Anti-Oedipus: Capitalism and Schizophrenia (Penguin Classics)</Title></ItemAttributes></Item></Items></ItemLookupResponse>
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'pry'
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
10
|
+
require File.expand_path('../../lib/vacuum', __FILE__)
|
11
|
+
|
12
|
+
RSpec.configure do |c|
|
13
|
+
c.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Vacuum
|
4
|
+
describe Builder do
|
5
|
+
let(:xml) do
|
6
|
+
xml = <<-XML.gsub!(/>\s+</, '><').strip!
|
7
|
+
<?xml version=\"1.0\" ?>
|
8
|
+
<ItemAttributes>
|
9
|
+
<Title>Anti-Oedipus</Title>
|
10
|
+
<Author>Gilles Deleuze</Author>
|
11
|
+
<Author>Felix Guattari</Author>
|
12
|
+
<Creator Role="Translator">Robert Hurley</Creator>
|
13
|
+
</ItemAttributes>
|
14
|
+
XML
|
15
|
+
Nokogiri::XML(xml)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.from_xml' do
|
19
|
+
it 'returns a hash' do
|
20
|
+
Builder.from_xml(xml).should be_an_instance_of Hash
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handles only childs' do
|
24
|
+
Builder.from_xml(xml)['Title'].should eql 'Anti-Oedipus'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'handles arrays' do
|
28
|
+
Builder.from_xml(xml)['Author'].should be_a Array
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'handles attributes' do
|
32
|
+
node = Builder.from_xml(xml)['Creator']
|
33
|
+
node['Role'].should eql 'Translator'
|
34
|
+
node['__content__'].should eql 'Robert Hurley'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
File without changes
|
File without changes
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Vacuum
|
4
|
+
describe LookupOperations do
|
5
|
+
let(:req) { Request.new('us') }
|
6
|
+
|
7
|
+
before do
|
8
|
+
req.configure do |c|
|
9
|
+
c.key = 'foo'
|
10
|
+
c.tag = 'bar'
|
11
|
+
end
|
12
|
+
req.stub!(:get)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#find" do
|
16
|
+
before do
|
17
|
+
req.find('1', '2', :foo => 'bar')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'merges item ids' do
|
21
|
+
req.params['ItemId'].should eql '1,2'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'merges additional parameters' do
|
25
|
+
req.params['Foo'].should eql 'bar'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#find_browse_node" do
|
30
|
+
before do
|
31
|
+
req.find_browse_node('123', :foo => 'bar')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'merges item ids' do
|
35
|
+
req.params['BrowseNodeId'].should eql '123'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'merges additional parameters' do
|
39
|
+
req.params['Foo'].should eql 'bar'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#find_similar" do
|
44
|
+
before do
|
45
|
+
req.find_similar('1', '2')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'merges item ids' do
|
49
|
+
req.params['ItemId'].should eql '1,2'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Vacuum
|
4
|
+
describe Request do
|
5
|
+
let(:req) { Request.new('us') }
|
6
|
+
|
7
|
+
describe '#<<' do
|
8
|
+
before do
|
9
|
+
req.configure do |c|
|
10
|
+
c.key = 'foo'
|
11
|
+
c.tag = 'bar'
|
12
|
+
end
|
13
|
+
|
14
|
+
req.reset!
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'merges parameters into the query' do
|
18
|
+
req << { 'Key' => 'value' }
|
19
|
+
|
20
|
+
req.params['Key'].should eql 'value'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'camelizes keys' do
|
24
|
+
req << { :some_key => 'value' }
|
25
|
+
|
26
|
+
req.params.should have_key 'SomeKey'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'leaves camelized keys as is' do
|
30
|
+
req << { 'SomeKey' => 'value' }
|
31
|
+
|
32
|
+
req.params.should have_key 'SomeKey'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'casts numeric values to string' do
|
36
|
+
req << { 'Key' => 1 }
|
37
|
+
|
38
|
+
req.params['Key'].should eql '1'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'converts array values to string' do
|
42
|
+
req << { 'Key' => ['foo', 'bar'] }
|
43
|
+
|
44
|
+
req.params['Key'].should eql 'foo,bar'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'removes whitespace after commas in values' do
|
48
|
+
req << { 'Key' => 'foo, bar' }
|
49
|
+
|
50
|
+
req.params['Key'].should eql 'foo,bar'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#configure' do
|
55
|
+
it 'yields the locale' do
|
56
|
+
req.configure(&:class).should eql Locale
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#get' do
|
61
|
+
before do
|
62
|
+
req.configure do |c|
|
63
|
+
c.key = 'foo'
|
64
|
+
c.secret = 'bar'
|
65
|
+
c.tag = 'baz'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'returns a response' do
|
70
|
+
req.get.should be_a Response
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'raises an error if secret is missing' do
|
74
|
+
req.configure { |c| c.secret = nil }
|
75
|
+
|
76
|
+
expect { req.get }.to raise_error MissingSecret
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#params' do
|
81
|
+
before do
|
82
|
+
req.configure do |c|
|
83
|
+
c.key = 'foo'
|
84
|
+
c.tag = 'bar'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'raises an error if key is missing' do
|
89
|
+
req.configure { |c| c.key = nil }
|
90
|
+
|
91
|
+
expect { req.params }.to raise_error MissingKey
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'raises an error if tag is missing' do
|
95
|
+
req.configure { |c| c.tag = nil }
|
96
|
+
|
97
|
+
expect { req.params }.to raise_error MissingTag
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'includes common request parameters' do
|
101
|
+
req.params['Service'].should eql 'AWSECommerceService'
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'includes credentials' do
|
105
|
+
req.params.should have_key 'AWSAccessKeyId'
|
106
|
+
req.params.should have_key 'AssociateTag'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'includes a timestamp' do
|
110
|
+
req.params['Timestamp'].should =~ /^\d+-\d+-\d+T\d+:\d+:\d+Z$/
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when no API version is specified' do
|
114
|
+
it 'includes the current API version' do
|
115
|
+
req.params['Version'].should eql Request::CURRENT_API_VERSION
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when an API version is specified' do
|
120
|
+
it 'includes the specified API version' do
|
121
|
+
req << { 'Version' => '1' }
|
122
|
+
req.params['Version'].should eql '1'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '#reset!' do
|
128
|
+
before do
|
129
|
+
req.configure do |c|
|
130
|
+
c.key = 'foo'
|
131
|
+
c.tag = 'bar'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'resets the request parameters' do
|
136
|
+
req << { 'Key' => 'value' }
|
137
|
+
req.params.should have_key 'Key'
|
138
|
+
|
139
|
+
req.reset!
|
140
|
+
req.params.should_not have_key 'Key'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#url' do
|
145
|
+
before do
|
146
|
+
req.configure do |c|
|
147
|
+
c.key = 'foo'
|
148
|
+
c.secret = 'bar'
|
149
|
+
c.tag = 'baz'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'builds a URL' do
|
154
|
+
req.url.should be_a URI::HTTP
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'canonicalizes the request parameters' do
|
158
|
+
req.url.query.should match /\w+=\w+&/
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'sorts the request parameters' do
|
162
|
+
req << { 'A' => 1 }
|
163
|
+
req.url.query.should match /^A=1&/
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'URL-encodes values' do
|
167
|
+
req << { :key => 'foo,bar' }
|
168
|
+
req.url.query.should match /foo%2Cbar/
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'signs the query' do
|
172
|
+
req.url.query.should match /&Signature=/
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'raises an error if no secret is specified' do
|
176
|
+
expect do
|
177
|
+
req.configure { |c| c.secret = nil }
|
178
|
+
req.url
|
179
|
+
end.to raise_error MissingSecret
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Vacuum
|
4
|
+
describe Response do
|
5
|
+
let(:resp) do
|
6
|
+
body = File.read(File.expand_path('../../fixtures/http_response', __FILE__))
|
7
|
+
code = '200'
|
8
|
+
Response.new(body, code)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#each' do
|
12
|
+
it 'yields matches to given block' do
|
13
|
+
yielded = false
|
14
|
+
resp.each('Item') do |item|
|
15
|
+
yielded = true
|
16
|
+
end
|
17
|
+
|
18
|
+
yielded.should be_true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#errors' do
|
23
|
+
it 'returns an array of errors' do
|
24
|
+
resp.body = <<-EOF.gsub!(/>\s+</, '><').strip!
|
25
|
+
<?xml version=\"1.0\" ?>
|
26
|
+
<resp xmlns="http://example.com">
|
27
|
+
<Errors>
|
28
|
+
<Error>foo</Error>
|
29
|
+
</Errors>
|
30
|
+
</resp>
|
31
|
+
EOF
|
32
|
+
|
33
|
+
resp.errors.should =~ ['foo']
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#has_errors?' do
|
38
|
+
context 'when a resp does not contain any errors' do
|
39
|
+
it 'returns false' do
|
40
|
+
resp.stub!(:errors).and_return([])
|
41
|
+
|
42
|
+
resp.should_not have_errors
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when a resp contains errors' do
|
47
|
+
it 'returns true' do
|
48
|
+
resp.stub!(:errors).and_return([1])
|
49
|
+
|
50
|
+
resp.should have_errors
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#find' do
|
56
|
+
it 'returns an array of matching nodes' do
|
57
|
+
resp.find('ASIN').should_not be_empty
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#map" do
|
62
|
+
it "yields each match to a block and maps returned values" do
|
63
|
+
titles = resp.map('Item') { |i| i['ItemAttributes']['Title'] }
|
64
|
+
|
65
|
+
titles.count.should eql 2
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#to_hash' do
|
70
|
+
it 'casts resp to a hash' do
|
71
|
+
resp.to_hash.should be_a Hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#valid?' do
|
76
|
+
context 'when HTTP status is OK' do
|
77
|
+
it 'returns true' do
|
78
|
+
resp.should be_valid
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when HTTP status is not OK' do
|
83
|
+
it 'returns false' do
|
84
|
+
resp.code = 403
|
85
|
+
resp.should_not be_valid
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#xml' do
|
91
|
+
it 'returns a Nokogiri document' do
|
92
|
+
resp.xml.should be_an_instance_of Nokogiri::XML::Document
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Vacuum
|
4
|
+
describe SearchOperations do
|
5
|
+
let(:req) { Request.new('us') }
|
6
|
+
|
7
|
+
before do
|
8
|
+
req.configure do |c|
|
9
|
+
c.key = 'foo'
|
10
|
+
c.tag = 'bar'
|
11
|
+
end
|
12
|
+
req.stub!(:get)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#search" do
|
16
|
+
context "when given a keyword" do
|
17
|
+
before do
|
18
|
+
req.search('foo')
|
19
|
+
end
|
20
|
+
|
21
|
+
it "does a keyword search" do
|
22
|
+
req.params['Keywords'].should eql 'foo'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "searches all products" do
|
26
|
+
req.params["SearchIndex"].should eql 'All'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when given a search index and a keyword" do
|
31
|
+
before do
|
32
|
+
req.search('foo', 'bar')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "does a keyword search" do
|
36
|
+
req.params['Keywords'].should eql 'bar'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sets the search index" do
|
40
|
+
req.params["SearchIndex"].should eql 'foo'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when given a search index and parameters" do
|
45
|
+
before do
|
46
|
+
req.search('foo', :bar => 'baz')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "sets the parameters" do
|
50
|
+
req.params['Bar'].should eql 'baz'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets the search index" do
|
54
|
+
req.params["SearchIndex"].should eql 'foo'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vacuum
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Hakan Ensari
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: &70206980398600 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.4'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70206980398600
|
25
|
+
description: Vacuum is a Ruby wrapper to the Amazon Product Advertising API.
|
26
|
+
email:
|
27
|
+
- code@papercavalier.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- lib/vacuum/builder.rb
|
33
|
+
- lib/vacuum/cart.rb
|
34
|
+
- lib/vacuum/cart_operations.rb
|
35
|
+
- lib/vacuum/error.rb
|
36
|
+
- lib/vacuum/locale.rb
|
37
|
+
- lib/vacuum/lookup_operations.rb
|
38
|
+
- lib/vacuum/request.rb
|
39
|
+
- lib/vacuum/response.rb
|
40
|
+
- lib/vacuum/search_operations.rb
|
41
|
+
- lib/vacuum/version.rb
|
42
|
+
- lib/vacuum.rb
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- spec/fixtures/http_response
|
46
|
+
- spec/spec_helper.rb
|
47
|
+
- spec/vacuum/builder_spec.rb
|
48
|
+
- spec/vacuum/cart_operations_spec.rb
|
49
|
+
- spec/vacuum/cart_spec.rb
|
50
|
+
- spec/vacuum/lookup_operations_spec.rb
|
51
|
+
- spec/vacuum/request_spec.rb
|
52
|
+
- spec/vacuum/response_spec.rb
|
53
|
+
- spec/vacuum/search_operations_spec.rb
|
54
|
+
homepage: http://code.papercavalier.com/vacuum/
|
55
|
+
licenses: []
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.8.10
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: A Ruby wrapper to the Amazon Product Advertising API
|
78
|
+
test_files:
|
79
|
+
- spec/fixtures/http_response
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- spec/vacuum/builder_spec.rb
|
82
|
+
- spec/vacuum/cart_operations_spec.rb
|
83
|
+
- spec/vacuum/cart_spec.rb
|
84
|
+
- spec/vacuum/lookup_operations_spec.rb
|
85
|
+
- spec/vacuum/request_spec.rb
|
86
|
+
- spec/vacuum/response_spec.rb
|
87
|
+
- spec/vacuum/search_operations_spec.rb
|