amazon_product 3.0.0.pre.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 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