vacuum 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
+
[](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
|