amazon_product 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Amazon Product
2
+
3
+ Amazon Product is a Ruby wrapper to the [Amazon Product Advertising API] [1].
4
+
5
+ [![travis](https://secure.travis-ci.org/hakanensari/amazon_product.png)](http://travis-ci.org/hakanensari/amazon_product)
6
+
7
+ ## Usage
8
+
9
+ Require.
10
+
11
+ Set up a request.
12
+
13
+ request = AmazonProduct["us"]
14
+
15
+ request.configure do |c|
16
+ c.key = YOUR_AMAZON_KEY
17
+ c.secret = YOUR_AMAZON_SECRET
18
+ c.tag = YOUR_AMAZON_ASSOCIATE_TAG
19
+ end
20
+
21
+ Look up a product.
22
+
23
+ request << { :operation' => 'ItemLookup',
24
+ :item_id' => '0679753354' }
25
+ response = request.get
26
+
27
+ [Or use a shorthand] [2].
28
+
29
+ response = req.find('0679753354')
30
+
31
+ Consume the entire response.
32
+
33
+ response.to_hash
34
+
35
+ Quickly drop down to a particular node.
36
+
37
+ response['Item']
38
+
39
+ Please see [the project page] [3] for more detailed info.
40
+
41
+ [1]: https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html
42
+
43
+ [2]: https://github.com/hakanensari/amazon_product/blob/master/lib/amazon_product/operations.rb
44
+
45
+ [3]: http://code.papercavalier.com/amazon_product/
@@ -0,0 +1,13 @@
1
+ module AmazonProduct
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,47 @@
1
+ module AmazonProduct
2
+ module HashBuilder
3
+ # Builds a hash from a Nokogiri XML document.
4
+ #
5
+ # In earlier versions of Sucker, I was relying on the XML Mini
6
+ # Nokogiri module in Active Support. This method essentially
7
+ # accomplishes the same.
8
+ #
9
+ # Based on https://gist.github.com/335286
10
+ def self.from_xml(xml)
11
+ case xml
12
+ when Nokogiri::XML::Document
13
+ from_xml(xml.root)
14
+ when Nokogiri::XML::Element
15
+ result_hash = {}
16
+
17
+ xml.attributes.each_pair do |key, attribute|
18
+ result_hash[key] = attribute.value
19
+ end
20
+
21
+ xml.children.each do |child|
22
+ result = from_xml(child)
23
+
24
+ if child.name == 'text'
25
+ if result_hash.empty?
26
+ return result
27
+ else
28
+ result_hash['__content__'] = result
29
+ end
30
+ elsif result_hash[child.name]
31
+ if result_hash[child.name].is_a? Array
32
+ result_hash[child.name] << result
33
+ else
34
+ result_hash[child.name] = [result_hash[child.name]] << result
35
+ end
36
+ else
37
+ result_hash[child.name] = result
38
+ end
39
+ end
40
+
41
+ result_hash
42
+ else
43
+ xml.content.to_s
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ module AmazonProduct
2
+ # An Amazon locale.
3
+ class Locale
4
+ # Available Amazon hosts.
5
+ HOSTS = { :ca => 'ecs.amazonaws.ca',
6
+ :cn => 'webservices.amazon.cn',
7
+ :de => 'ecs.amazonaws.de',
8
+ :fr => 'ecs.amazonaws.fr',
9
+ :it => 'webservices.amazon.it',
10
+ :jp => 'ecs.amazonaws.jp',
11
+ :us => 'ecs.amazonaws.com',
12
+ :uk => 'ecs.amazonaws.co.uk' }
13
+
14
+ # The Amazon Web Services access key.
15
+ attr_accessor :key
16
+
17
+ # The Amazon Web Services secret.
18
+ attr_accessor :secret
19
+
20
+ # The Amazon associate tag.
21
+ attr_accessor :tag
22
+
23
+ def initialize(locale)
24
+ raise BadLocale unless HOSTS.has_key?(locale)
25
+ @locale = locale
26
+ end
27
+
28
+ # The Amazon host.
29
+ def host
30
+ HOSTS[@locale]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,110 @@
1
+ module AmazonProduct
2
+ # Some shorthand notation for available operations.
3
+ module Operations
4
+ # Cart operations.
5
+ def add_to_cart(params)
6
+ cart 'Add', params
7
+ end
8
+
9
+ def clear_cart(params)
10
+ cart 'Clear', params
11
+ end
12
+
13
+ def create_cart(params)
14
+ cart 'Create', params
15
+ end
16
+
17
+ def get_cart(params)
18
+ cart 'Get', params
19
+ end
20
+
21
+ def modify_cart(params)
22
+ cart 'Modify', params
23
+ end
24
+
25
+ # Given up to ten item ids, returns some or all of the item attributes,
26
+ # depending on the response group specified in the request.
27
+ #
28
+ # Id Type defaults to ASIN.
29
+ #
30
+ # Assuming you have a request object, the following returns some basic
31
+ # information for the ASIN 0679753354:
32
+ #
33
+ # request.find('0679753354')
34
+ #
35
+ # The following request returns cover art for the same ASIN:
36
+ #
37
+ # req.find('0679753354', :response_group => 'Images')
38
+ #
39
+ def find(*item_ids)
40
+ reset
41
+ params = item_ids.last.is_a?(Hash) ? item_ids.pop : {}
42
+ self.<< ({ 'Operation' => 'ItemLookup',
43
+ 'ItemId' => item_ids }).merge(params)
44
+ get
45
+ end
46
+
47
+ # Given a browse node ID, returns the specified browse node’s name,
48
+ # children, and ancestors.
49
+ def find_browse_node(browse_node_id, params = {})
50
+ reset
51
+ self.<< ({ 'Operation' => 'BrowseNodeLookup',
52
+ 'BrowseNodeId' => browse_node_id }).merge(params)
53
+ get
54
+ end
55
+
56
+ # Given up to ten item ids, returns up to ten products per page that are
57
+ # similar to those items.
58
+ def find_similar(*item_ids)
59
+ reset
60
+ params = item_ids.last.is_a?(Hash) ? item_ids.pop : {}
61
+ self.<< ({ 'Operation' => 'SimilarityLookup',
62
+ 'ItemId' => item_ids }).merge(params)
63
+ get
64
+ end
65
+
66
+ # Returns up to ten items that satisfy the search criteria, including one
67
+ # or more search indices.
68
+ #
69
+ # Assuming you have a request object, the following searches the entire
70
+ # Amazon catalog for the keyword 'book':
71
+ #
72
+ # request.search('book')
73
+ #
74
+ # The following searches the books search index for the keyword 'lacan':
75
+ #
76
+ # request.search('Books', 'lacan')
77
+ #
78
+ # The following runs a power search on the books search index for non-
79
+ # fiction titles authored by Lacan and sorts results by Amazon's relevance
80
+ # ranking:
81
+ #
82
+ # request.search('Books', :power => 'author:lacan and not fiction',
83
+ # :sort => 'relevancerank')
84
+ #
85
+ def search(search_index = nil, params = nil)
86
+ reset
87
+
88
+ if params.nil?
89
+ params = { 'Keywords' => search_index }
90
+ search_index = 'All'
91
+ end
92
+
93
+ if params.is_a? String
94
+ params = { 'Keywords' => params }
95
+ end
96
+
97
+ self.<< ({ 'Operation' => 'ItemSearch',
98
+ 'SearchIndex' => search_index }.merge(params))
99
+ get
100
+ end
101
+
102
+ private
103
+
104
+ def cart(operation, params)
105
+ reset
106
+ self.<< ({ 'Operation' => "Cart#{operation}" }.merge(params))
107
+ get
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,110 @@
1
+ module AmazonProduct
2
+ # A wrapper around the API request.
3
+ class Request
4
+ extend Forwardable
5
+ include Operations
6
+
7
+ # The latest Amazon API version. See:
8
+ # http://aws.amazon.com/archives/Product%20Advertising%20API
9
+ CURRENT_API_VERSION = '2011-08-01'
10
+
11
+ # The Amazon locale.
12
+ attr :locale
13
+
14
+ def_delegators :locale, :host, :key, :secret, :tag
15
+
16
+ # Creates a new request for specified locale.
17
+ def initialize(locale)
18
+ @locale = Locale.new(locale.to_sym)
19
+ @params = Hash.new
20
+ end
21
+
22
+ # Merges a hash of request parameters into the query.
23
+ #
24
+ # request << { :key => 'value }
25
+ #
26
+ def <<(hash)
27
+ hash.each do |k, v|
28
+ # Cast value to string.
29
+ v = v.is_a?(Array) ? v.join(',') : v.to_s
30
+
31
+ # Camelize key.
32
+ k = k.to_s.split('_').map { |w| w[0, 1] = w[0, 1].upcase; w }.join
33
+
34
+ @params[k] = v
35
+ end
36
+ end
37
+
38
+ # Configures the Amazon locale.
39
+ #
40
+ # request.configure do |c|
41
+ # c.key = YOUR_KEY
42
+ # c.secret = YOUR_SECRET
43
+ # c.tag = YOUR_ASSOCIATE_TAG
44
+ # end
45
+ #
46
+ def configure
47
+ yield locale
48
+ end
49
+
50
+ # The request parameters.
51
+ def params
52
+ raise MissingKey unless key
53
+ raise MissingTag unless tag
54
+
55
+ { 'AWSAccessKeyId' => key,
56
+ 'AssociateTag' => tag,
57
+ 'Service' => 'AWSECommerceService',
58
+ 'Timestamp' => timestamp,
59
+ 'Version' => CURRENT_API_VERSION }.merge(@params)
60
+ end
61
+
62
+ # A string representation of the request parameters.
63
+ def query
64
+ params.sort.map { |k, v| "#{k}=" + escape(v) }.join('&')
65
+ end
66
+
67
+ # Resets the request parameters.
68
+ def reset
69
+ @params = Hash.new
70
+ end
71
+
72
+ # Performs a request.
73
+ def get
74
+ resp = Net::HTTP.get_response(url)
75
+ Response.new(resp)
76
+ end
77
+
78
+ # Adds a signature to a query
79
+ def sign(unsigned_query)
80
+ raise MissingSecret unless secret
81
+
82
+ digest = OpenSSL::Digest::Digest.new('sha256')
83
+ url_string = ['GET', host, '/onca/xml', unsigned_query].join("\n")
84
+ hmac = OpenSSL::HMAC.digest(digest, secret, url_string)
85
+ signature = escape([hmac].pack('m').chomp)
86
+
87
+ "#{unsigned_query}&Signature=#{signature}"
88
+ end
89
+
90
+ # The current timestamp.
91
+ def timestamp
92
+ Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
93
+ end
94
+
95
+ # The Amazon URL.
96
+ def url
97
+ URI::HTTP.build(:host => host,
98
+ :path => '/onca/xml',
99
+ :query => sign(query))
100
+ end
101
+
102
+ private
103
+
104
+ def escape(value)
105
+ value.gsub(/([^a-zA-Z0-9_.~-]+)/) do
106
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,71 @@
1
+ module AmazonProduct
2
+ # A wrapper around the API response.
3
+ class Response
4
+
5
+ # The response body.
6
+ attr_accessor :body
7
+
8
+ # The HTTP status code of the response.
9
+ attr_accessor :code
10
+
11
+ def initialize(response)
12
+ self.body = response.body
13
+ self.code = response.code.to_i
14
+ end
15
+
16
+ # A shorthand that queries for a specified attribute and yields to a given
17
+ # block each matching document.
18
+ #
19
+ # response.each('Item') { |item| puts item }
20
+ #
21
+ def each(path, &block)
22
+ find(path).each { |match| block.call(match) }
23
+ end
24
+
25
+ # An array of errors in the response.
26
+ def errors
27
+ find('Error')
28
+ end
29
+
30
+ # Queries for a specified attribute and returns an array of matching
31
+ # documents.
32
+ #
33
+ # items = response.find('Item')
34
+ #
35
+ def find(attribute)
36
+ xml.xpath("//xmlns:#{attribute}").map do |element|
37
+ HashBuilder.from_xml(element)
38
+ end
39
+ end
40
+ alias [] find
41
+
42
+ # Returns true if the response contains errors.
43
+ def has_errors?
44
+ errors.count > 0
45
+ end
46
+
47
+ # A shorthand that queries for a specifed attribute, yields to a given
48
+ # block matching documents, and collects final values.
49
+ #
50
+ # items = response.map('Item') { |item| # do something }
51
+ #
52
+ def map(path, &block)
53
+ find(path).map { |match| block.call(match) }
54
+ end
55
+
56
+ # Parses the response into a simple hash.
57
+ def to_hash
58
+ HashBuilder.from_xml(xml)
59
+ end
60
+
61
+ # Checks if the HTTP response is OK.
62
+ def valid?
63
+ code == 200
64
+ end
65
+
66
+ # The XML document.
67
+ def xml
68
+ @xml ||= Nokogiri::XML(body)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,34 @@
1
+ require 'amazon_product'
2
+ require 'em-synchrony'
3
+ require 'em-synchrony/em-http'
4
+
5
+ # Patches Request and Response to make them fiber-aware.
6
+ module AmazonProduct
7
+ class Request
8
+ def adapter
9
+ @adapter ||= EM::HttpRequest
10
+ end
11
+
12
+ # Performs an evented request.
13
+ #
14
+ # Yields a response to given block.
15
+ def aget(&block)
16
+ http = EM::HttpRequest.new(url).aget
17
+ http.callback { block.call(Response.new(http)) }
18
+ http.errback { block.call(Response.new(http)) }
19
+ end
20
+
21
+ # Performs an evented request.
22
+ def get
23
+ http = EM::HttpRequest.new(url).get
24
+ Response.new(http)
25
+ end
26
+ end
27
+
28
+ class Response
29
+ def initialize(http)
30
+ self.body = http.response
31
+ self.code = http.response_header.status
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module AmazonProduct
2
+ VERSION = '3.0.0.pre.1'
3
+ end
@@ -0,0 +1,26 @@
1
+ require 'forwardable'
2
+ require 'net/http'
3
+ require 'nokogiri'
4
+ require 'openssl'
5
+
6
+ require 'amazon_product/error'
7
+ require 'amazon_product/hash_builder'
8
+ require 'amazon_product/locale'
9
+ require 'amazon_product/operations'
10
+ require 'amazon_product/request'
11
+ require 'amazon_product/response'
12
+
13
+ # Amazon Product is a Ruby wrapper to the Amazon Product Advertising API.
14
+ module AmazonProduct
15
+ @requests = Hash.new
16
+
17
+ # A request.
18
+ #
19
+ # Takes an Amazon locale as argument. This can be +ca+, +cn+, +de+, +fr+,
20
+ # +it+, +jp+, +uk+, or +us+.
21
+ #
22
+ # The library will cache one request per locale.
23
+ def self.[](locale)
24
+ @requests[locale] ||= Request.new(locale)
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ module AmazonProduct
4
+ describe HashBuilder 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
+ HashBuilder.from_xml(xml).should be_an_instance_of Hash
21
+ end
22
+
23
+ it 'handles only childs' do
24
+ HashBuilder.from_xml(xml)['Title'].should eql 'Anti-Oedipus'
25
+ end
26
+
27
+ it 'handles arrays' do
28
+ HashBuilder.from_xml(xml)['Author'].should be_a Array
29
+ end
30
+
31
+ it 'handles attributes' do
32
+ node = HashBuilder.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
@@ -0,0 +1,189 @@
1
+ require 'spec_helper'
2
+
3
+ module AmazonProduct
4
+ describe Request do
5
+
6
+ subject { Request.new('us') }
7
+
8
+ describe '#<<' do
9
+ before do
10
+ subject.configure do |c|
11
+ c.key = 'foo'
12
+ c.tag = 'bar'
13
+ end
14
+ end
15
+
16
+ it 'merges parameters into the query' do
17
+ subject << { 'Key' => 'value' }
18
+ subject.params['Key'].should eql 'value'
19
+ end
20
+
21
+ it 'camelizes keys' do
22
+ subject << { :some_key => 'value' }
23
+ subject.params.should have_key 'SomeKey'
24
+ end
25
+
26
+ it 'does not modify already-camelized keys' do
27
+ subject << { 'SomeKey' => 'value' }
28
+ subject.params.should have_key 'SomeKey'
29
+ end
30
+
31
+ it 'casts numeric values to string' do
32
+ subject << { 'Key' => 1 }
33
+ subject.params['Key'].should eql '1'
34
+ end
35
+
36
+ it 'converts array values to string' do
37
+ subject << { 'Key' => ['foo', 'bar'] }
38
+ subject.params['Key'].should eql 'foo,bar'
39
+ end
40
+ end
41
+
42
+ describe '#configure' do
43
+ it 'configures the locale' do
44
+ subject.configure do |c|
45
+ c.key = 'foo'
46
+ end
47
+
48
+ locale = subject.locale
49
+ locale.key.should eql 'foo'
50
+ end
51
+ end
52
+
53
+ describe '#get' do
54
+ before do
55
+ subject.configure do |c|
56
+ c.key = 'foo'
57
+ c.secret = 'bar'
58
+ c.tag = 'baz'
59
+ end
60
+ end
61
+
62
+ it 'returns a response' do
63
+ response = subject.get
64
+ response.should be_a Response
65
+ end
66
+ end
67
+
68
+ describe '#params' do
69
+ context 'when no credentials are specified' do
70
+ it 'raises an error' do
71
+ expect do
72
+ subject.params
73
+ end.to raise_error MissingKey
74
+
75
+ expect do
76
+ subject.locale.key = 'foo'
77
+ subject.params
78
+ end.to raise_error MissingTag
79
+ end
80
+ end
81
+
82
+ context 'when credentials are specified' do
83
+ before do
84
+ subject.configure do |c|
85
+ c.key = 'foo'
86
+ c.tag = 'bar'
87
+ end
88
+ end
89
+
90
+ it 'returns the request parameters' do
91
+ subject.params['Service'].should eql 'AWSECommerceService'
92
+ end
93
+
94
+ it 'includes credentials' do
95
+ subject.params.should have_key 'AWSAccessKeyId'
96
+ subject.params.should have_key 'AssociateTag'
97
+ end
98
+
99
+ it 'includes a timestamp' do
100
+ subject.params.should have_key 'Timestamp'
101
+ end
102
+
103
+ context 'when no API version is specified' do
104
+ it 'includes the current API version' do
105
+ subject.params['Version'].should eql Request::CURRENT_API_VERSION
106
+ end
107
+ end
108
+
109
+ context 'when an API version is specified' do
110
+ it 'includes that API version' do
111
+ subject << { 'Version' => 'foo' }
112
+ subject.params['Version'].should eql 'foo'
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ describe '#query' do
119
+ before do
120
+ subject.configure do |c|
121
+ c.key = 'foo'
122
+ c.tag = 'bar'
123
+ end
124
+ end
125
+
126
+ it 'canonicalizes the request parameters' do
127
+ subject.query.should match /\w+=\w+&/
128
+ end
129
+
130
+ it 'sorts the request parameters' do
131
+ subject << { 'A' => 1 }
132
+ subject.query.should match /^A=1&/
133
+ end
134
+
135
+ it 'URL-encodes values' do
136
+ subject << { :key => 'foo,bar' }
137
+ subject.query.should match /foo%2Cbar/
138
+ end
139
+ end
140
+
141
+ describe '#reset' do
142
+ before do
143
+ subject.configure do |c|
144
+ c.key = 'foo'
145
+ c.tag = 'bar'
146
+ end
147
+ end
148
+
149
+ it 'resets the request parameters' do
150
+ subject << { 'Key' => 'value' }
151
+ subject.params.should have_key 'Key'
152
+
153
+ subject.reset
154
+ subject.params.should_not have_key 'Key'
155
+ end
156
+ end
157
+
158
+ describe '#sign' do
159
+ it 'adds a signature to a query' do
160
+ subject.locale.secret = 'baz'
161
+ subject.sign('foo').should match /^foo&Signature=/
162
+ end
163
+
164
+ it 'raises an error if no secret is specified' do
165
+ expect { subject.sign('foo') }.to raise_error MissingSecret
166
+ end
167
+ end
168
+
169
+ describe '#timestamp' do
170
+ it 'generates a timestamp' do
171
+ subject.timestamp.should match /^\d+-\d+-\d+T\d+:\d+:\d+Z$/
172
+ end
173
+ end
174
+
175
+ describe '#url' do
176
+ before do
177
+ subject.configure do |c|
178
+ c.key = 'foo'
179
+ c.secret = 'bar'
180
+ c.tag = 'baz'
181
+ end
182
+ end
183
+
184
+ it 'builds a URL' do
185
+ subject.url.should be_a URI::HTTP
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ module AmazonProduct
4
+ describe Response do
5
+ let(:response) do
6
+ http_resp = Struct.new(:body, :code).new
7
+ http_resp.body = File.read(File.expand_path('../../fixtures/http_response', __FILE__))
8
+ http_resp.code = '200'
9
+ Response.new(http_resp)
10
+ end
11
+
12
+ describe '#each' do
13
+ context 'when a block is given' do
14
+ it 'yields matches to a block' do
15
+ yielded = false
16
+ response.each('Item') do |item|
17
+ yielded = true
18
+ end
19
+
20
+ yielded.should be_true
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#errors' do
26
+ it 'returns an array of errors' do
27
+ response.body = <<-XML.gsub!(/>\s+</, '><').strip!
28
+ <?xml version=\"1.0\" ?>
29
+ <Response xmlns="http://example.com">
30
+ <Errors>
31
+ <Error>foo</Error>
32
+ </Errors>
33
+ </Response>
34
+ XML
35
+ response.errors.should =~ ['foo']
36
+ end
37
+ end
38
+
39
+ describe '#has_errors?' do
40
+ context 'when a response does not contain any errors' do
41
+ it 'returns false' do
42
+ response.stub!(:errors).and_return([])
43
+ response.should_not have_errors
44
+ end
45
+ end
46
+
47
+ context 'when a response contains errors' do
48
+ it 'returns true' do
49
+ response.stub!(:errors).and_return([1])
50
+ response.should have_errors
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#find' do
56
+ it 'returns an array of matching nodes' do
57
+ response.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 = response.map('Item') { |item| item['ItemAttributes']['Title'] }
64
+ titles.count.should eql 2
65
+ end
66
+ end
67
+
68
+ describe '#to_hash' do
69
+ it 'casts response to a hash' do
70
+ response.to_hash.should be_a Hash
71
+ end
72
+ end
73
+
74
+ describe '#valid?' do
75
+ context 'when HTTP status is OK' do
76
+ it 'returns true' do
77
+ response.should be_valid
78
+ end
79
+ end
80
+
81
+ context 'when HTTP status is not OK' do
82
+ it 'returns false' do
83
+ response.code = 403
84
+ response.should_not be_valid
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '#xml' do
90
+ it 'returns a Nokogiri document' do
91
+ response.xml.should be_an_instance_of Nokogiri::XML::Document
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ module AmazonProduct
4
+ describe 'Synchrony adapter', :synchrony do
5
+ before(:all) do
6
+ require 'amazon_product/synchrony'
7
+ end
8
+
9
+ describe Request, :synchrony do
10
+ let(:request) do
11
+ req = AmazonProduct['us']
12
+ req.configure do |c|
13
+ c.key = 'foo'
14
+ c.secret = 'bar'
15
+ c.tag = 'baz'
16
+ end
17
+ req
18
+ end
19
+
20
+ it "uses an evented adapter" do
21
+ request.adapter.should eql ::EM::HttpRequest
22
+ end
23
+
24
+ describe "#aget" do
25
+ it "yields a response" do
26
+ response = nil
27
+ EM.synchrony do
28
+ request.aget { |resp| response = resp }
29
+ EM.stop
30
+ end
31
+
32
+ response.should be_a Response
33
+ end
34
+ end
35
+
36
+ describe "#get" do
37
+ it "returns a response" do
38
+ response = nil
39
+ EM.synchrony do
40
+ response = request.get
41
+ EM.stop
42
+ end
43
+
44
+ response.should be_a Response
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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>
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rspec'
4
+
5
+ require File.expand_path('../../lib/amazon_product', __FILE__)
6
+
7
+ RSpec.configure do |c|
8
+ c.treat_symbols_as_metadata_keys_with_true_values = true
9
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amazon_product
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0.pre.1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Hakan Ensari
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-10 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: &70241913617000 !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: *70241913617000
25
+ description: Amazon Product 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/amazon_product/error.rb
33
+ - lib/amazon_product/hash_builder.rb
34
+ - lib/amazon_product/locale.rb
35
+ - lib/amazon_product/operations.rb
36
+ - lib/amazon_product/request.rb
37
+ - lib/amazon_product/response.rb
38
+ - lib/amazon_product/synchrony.rb
39
+ - lib/amazon_product/version.rb
40
+ - lib/amazon_product.rb
41
+ - LICENSE
42
+ - README.md
43
+ - spec/amazon_product/hash_builder_spec.rb
44
+ - spec/amazon_product/request_spec.rb
45
+ - spec/amazon_product/response_spec.rb
46
+ - spec/amazon_product/synchrony_spec.rb
47
+ - spec/fixtures/http_response
48
+ - spec/spec_helper.rb
49
+ homepage: http://code.papercavalier.com/amazon_product/
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ segments:
62
+ - 0
63
+ hash: 1955989660319264497
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>'
68
+ - !ruby/object:Gem::Version
69
+ version: 1.3.1
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.6
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A Ruby wrapper to the Amazon Product Advertising API
76
+ test_files:
77
+ - spec/amazon_product/hash_builder_spec.rb
78
+ - spec/amazon_product/request_spec.rb
79
+ - spec/amazon_product/response_spec.rb
80
+ - spec/amazon_product/synchrony_spec.rb
81
+ - spec/fixtures/http_response
82
+ - spec/spec_helper.rb