mct-amazon-ecs 0.5.7

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.
@@ -0,0 +1,29 @@
1
+ 0.5.5 2009-07-10
2
+ ----------------
3
+ * Added function of managing new request authentication
4
+
5
+ 0.5.4 2008-01-02
6
+ ----------------
7
+ * Add Response#error_code
8
+
9
+ 0.5.3 2007-09-12
10
+ ----------------
11
+ * send_request to use default options.
12
+
13
+ 0.5.2 2007-09-08
14
+ ----------------
15
+ * Fixed Amazon::Element.get_unescaped error when result returned for given element path is nil
16
+
17
+ 0.5.1 2007-02-08
18
+ ----------------
19
+ * Fixed Amazon Japan and France URL error
20
+ * Removed opts.delete(:search_index) from item_lookup, SearchIndex param is allowed
21
+ when looking for a book with IdType other than the ASIN.
22
+ * Check for defined? RAILS_DEFAULT_LOGGER to avoid exception for non-rails ruby app
23
+ * Added check for LOGGER constant if RAILS_DEFAULT_LOGGER is not defined
24
+ * Added Ecs.configure(&proc) method for easier configuration of default options
25
+ * Added Element#search_and_convert method
26
+
27
+ 0.5.0 2006-09-12
28
+ ----------------
29
+ Initial Release
data/README ADDED
@@ -0,0 +1,97 @@
1
+ == amazon-ecs
2
+
3
+ Generic Amazon E-commerce REST API using Hpricot with configurable
4
+ default options and method call options. Uses Response and
5
+ Element wrapper classes for easy access to REST XML output. It supports ECS 4.0.
6
+
7
+ It is generic, so you can easily extend <tt>Amazon::Ecs</tt> to support
8
+ other not implemented REST operations; and it is also generic because it just wraps around
9
+ Hpricot element object, instead of providing one-to-one object/attributes to XML elements map.
10
+
11
+ If in the future, there is a change in REST XML output structure,
12
+ no changes will be required on <tt>amazon-ecs</tt> library,
13
+ instead you just need to change the element path.
14
+
15
+ Version: 0.5.5
16
+
17
+ == INSTALLATION
18
+
19
+ $ gem install amazon-ecs
20
+
21
+ == EXAMPLE
22
+
23
+ require 'amazon/ecs'
24
+
25
+ # set the default options; options will be camelized and converted to REST request parameters.
26
+ Amazon::Ecs.options = {:aWS_access_key_id => [your developer token]}
27
+
28
+ # options provided on method call will merge with the default options
29
+ res = Amazon::Ecs.item_search('ruby', {:response_group => 'Medium', :sort => 'salesrank'})
30
+
31
+ # some common response object methods
32
+ res.is_valid_request? # return true if request is valid
33
+ res.has_error? # return true if there is an error
34
+ res.error # return error message if there is any
35
+ res.total_pages # return total pages
36
+ res.total_results # return total results
37
+ res.item_page # return current page no if :item_page option is provided
38
+
39
+ # traverse through each item (Amazon::Element)
40
+ res.items.each do |item|
41
+ # retrieve string value using XML path
42
+ item.get('asin')
43
+ item.get('itemattributes/title')
44
+
45
+ # or return Amazon::Element instance
46
+ atts = item.search_and_convert('itemattributes')
47
+ atts.get('title')
48
+
49
+ # return first author or a string array of authors
50
+ atts.get('author') # 'Author 1'
51
+ atts.get_array('author') # ['Author 1', 'Author 2', ...]
52
+
53
+ # return an hash of children text values with the element names as the keys
54
+ item.get_hash('smallimage') # {:url => ..., :width => ..., :height => ...}
55
+
56
+ # note that '/' returns Hpricot::Elements array object, nil if not found
57
+ reviews = item/'editorialreview'
58
+
59
+ # traverse through Hpricot elements
60
+ reviews.each do |review|
61
+ # Getting hash value out of Hpricot element
62
+ Amazon::Element.get_hash(review) # [:source => ..., :content ==> ...]
63
+
64
+ # Or to get unescaped HTML values
65
+ Amazon::Element.get_unescaped(review, 'source')
66
+ Amazon::Element.get_unescaped(review, 'content')
67
+
68
+ # Or this way
69
+ el = Amazon::Element.new(review)
70
+ el.get_unescaped('source')
71
+ el.get_unescaped('content')
72
+ end
73
+
74
+ # returns Amazon::Element instead of string
75
+ item.search_and_convert('itemattributes').
76
+ end
77
+
78
+ Refer to Amazon ECS documentation for more information on Amazon REST request parameters and XML output:
79
+ http://docs.amazonwebservices.com/AWSEcommerceService/2006-09-13/
80
+
81
+ To get a sample of Amazon REST response XML output, use AWSZone.com scratch pad:
82
+ http://www.awszone.com/scratchpads/aws/ecs.us/index.aws
83
+
84
+ == SOURCE CODES
85
+
86
+ * http://github.com/jugend/amazon-ecs/tree/master
87
+
88
+ == LINKS
89
+
90
+ * http://amazon-ecs.rubyforge.org
91
+ * http://www.pluitsolutions.com/amazon-ecs
92
+
93
+ == LICENSE
94
+
95
+ (The MIT License)
96
+
97
+ Copyright (c) 2006 Herryanto Siatono, Pluit Solutions
@@ -0,0 +1,5 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
3
+
4
+ require 'amazon/aws_product_sign'
5
+ require 'amazon/ecs'
@@ -0,0 +1,149 @@
1
+ require 'rubygems'
2
+ require 'cgi'
3
+ require 'time'
4
+ require 'hmac'
5
+ require 'hmac-sha2'
6
+ require 'base64'
7
+
8
+ # Code to sign a request to Amazon Product Advertising API (formerly known as
9
+ # the AWS ECommerce Service), as per the specs at:
10
+ # http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?RequestAuthenticationArticle.html
11
+ #
12
+ # This code based heavily on that at: http://chrisroos.co.uk/blog/2009-01-31-implementing-version-2-of-the-amazon-aws-http-request-signature-in-ruby
13
+ # Thanks Chris!
14
+ #
15
+ # Example:
16
+ #
17
+ # aws_signer = AwsProductSign(:access_key => "00000000000000000000", :secret_key => "1234567890")
18
+ # params = {
19
+ # "Service" => "AWSECommerceService",
20
+ # "Operation"=>"ItemLookup",
21
+ # "ItemId"=>"0679722769",
22
+ # "ResponseGroup"=>"ItemAttributes,Offers,Images,Reviews"
23
+ # }
24
+ # query_string = aws_signer.query_with_signature( params )
25
+ #
26
+ # params will have a Timestamp AWSAccessKeyId added to it, unless input
27
+ # already had it.
28
+ #
29
+ # Or you can get back a params hash instead of an encoded query string.
30
+ # Beware that the Signature parameter must be URL-encoded precisely, and
31
+ # not over-encoded: "the final signature you send in the request must be URL
32
+ # encoded as specified in RFC 3986
33
+
34
+ # Then you can go on to use those new params in rails url_for or the URI
35
+ # builder of your choice. Values are not URI-escaped yet. Or mutate the
36
+ # params passsed in with #add_signature! instead.
37
+ #
38
+ # Returning a new params hash, leaving your input untouched:
39
+ #
40
+ # query_string_component = aws_signer.add_signature( params )
41
+ #
42
+ # Or mutate your input:
43
+ # aws_signer.add_signature!(params)
44
+ #
45
+ #
46
+ # At the moment this class can't handle a query string where you need the same
47
+ # key twice. I don't think the AWS service ever uses that though?
48
+ #
49
+ # This class also assumes a GET request.
50
+ module Amazon
51
+ class AwsProductSign
52
+
53
+ def initialize(options = {})
54
+ @secret_key = options[:secret_key]
55
+ raise Exception.new("You must supply a :secret_key") unless @secret_key
56
+ @access_key = options[:access_key]
57
+ end
58
+
59
+ def query_with_signature(hash)
60
+ return hash_to_query( add_signature(hash) )
61
+ end
62
+
63
+ # Pass in a hash representing params for a query string.
64
+ # param keys should be strings, not symbols please.
65
+ # Will return a param with the "Signature" key/value added, without
66
+ # modifying original.
67
+ def add_signature(params)
68
+ # Make a copy to not modify original
69
+ add_signature!( Hash[params] )
70
+ end
71
+
72
+ # Like #add_signature, but will mutate the hash passed in,
73
+ # adding a "Signature" key/value to hash passed in, and return
74
+ # hash too.
75
+ def add_signature!(params)
76
+
77
+ # supply timestamp and access key if not already provided
78
+ params["Timestamp"] ||= Time.now.iso8601
79
+ params["AWSAccessKeyId"] ||= access_key
80
+ # Existing "Signature"? That's gotta go before we generate a new
81
+ # signature and add it.
82
+ params.delete("Signature")
83
+
84
+ query_string = canonical_querystring(params)
85
+
86
+ string_to_sign = string_to_sign(query_string)
87
+
88
+ hmac = HMAC::SHA256.new( secret_key )
89
+ hmac.update( string_to_sign )
90
+ # chomp is important! the base64 encoded version will have a newline at the end
91
+ signature = Base64.encode64(hmac.digest).chomp
92
+
93
+ params["Signature"] = signature
94
+
95
+ #order doesn't matter for the actual request, we return the hash
96
+ #and let client turn it into a url.
97
+ return params
98
+ end
99
+
100
+ # Insist on specific method of URL encoding, RFC3986.
101
+ def url_encode(string)
102
+ # It's kinda like CGI.escape, except CGI.escape is encoding a tilde when
103
+ # it ought not to be, so we turn it back. Also space NEEDS to be %20 not +.
104
+ return CGI.escape(string).gsub("%7E", "~").gsub("+", "%20")
105
+ end
106
+
107
+ # param keys should be strings, not symbols please. return a string joined
108
+ # by & in canonical order.
109
+ def canonical_querystring(params)
110
+ # I hope this built-in sort sorts by byte order, that's what's required.
111
+ values = params.keys.sort.collect {|key| [url_encode(key), url_encode(params[key].to_s)].join("=") }
112
+
113
+ return values.join("&")
114
+ end
115
+
116
+ def string_to_sign(query_string, options = {})
117
+ options[:verb] = "GET"
118
+ options[:request_uri] = "/onca/xml"
119
+ options[:host] = "webservices.amazon.com"
120
+
121
+
122
+ return options[:verb] + "\n" +
123
+ options[:host].downcase + "\n" +
124
+ options[:request_uri] + "\n" +
125
+ query_string
126
+ end
127
+
128
+ # Turns a hash into a query string, returns the query string.
129
+ # url-encodes everything to Amazon's specifications.
130
+ def hash_to_query(hash)
131
+ hash.collect do |key, value|
132
+
133
+ url_encode(key) + "=" + url_encode(value)
134
+
135
+ end.join("&")
136
+ end
137
+
138
+ def secret_key
139
+ return @secret_key
140
+ end
141
+ def access_key
142
+ return @access_key
143
+ end
144
+ def access_key=(a)
145
+ @access_key = a
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,326 @@
1
+ #--
2
+ # Copyright (c) 2006 Herryanto Siatono, Pluit Solutions
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+
25
+ require 'net/http'
26
+ require 'hpricot'
27
+ require 'cgi'
28
+ require 'openssl'
29
+ require 'uri'
30
+ require 'digest/sha2'
31
+ require 'base64'
32
+
33
+ module Amazon
34
+ class RequestError < StandardError; end
35
+
36
+ class Ecs
37
+ SERVICE_URLS = {:us => 'http://webservices.amazon.com/onca/xml',
38
+ :uk => 'http://webservices.amazon.co.uk/onca/xml',
39
+ :ca => 'http://webservices.amazon.ca/onca/xml',
40
+ :de => 'http://webservices.amazon.de/onca/xml',
41
+ :jp => 'http://webservices.amazon.co.jp/onca/xml',
42
+ :fr => 'http://webservices.amazon.fr/onca/xml'
43
+ }
44
+
45
+ @@options = {}
46
+ @@debug = false
47
+
48
+ # Default search options
49
+ def self.options
50
+ @@options
51
+ end
52
+
53
+ # Set default search options
54
+ def self.options=(opts)
55
+ @@options = opts
56
+ end
57
+
58
+ # Get debug flag.
59
+ def self.debug
60
+ @@debug
61
+ end
62
+
63
+ # Set debug flag to true or false.
64
+ def self.debug=(dbg)
65
+ @@debug = dbg
66
+ end
67
+
68
+ def self.configure(&proc)
69
+ raise ArgumentError, "Block is required." unless block_given?
70
+ yield @@options
71
+ end
72
+
73
+ # Search amazon items with search terms. Default search index option is 'Books'.
74
+ # For other search type other than keywords, please specify :type => [search type param name].
75
+ def self.item_search(terms, opts = {})
76
+ opts[:operation] = 'ItemSearch'
77
+ opts[:search_index] = opts[:search_index] || 'Books'
78
+
79
+ type = opts.delete(:type)
80
+ if type
81
+ opts[type.to_sym] = terms
82
+ else
83
+ opts[:keywords] = terms
84
+ end
85
+
86
+ self.send_request(opts)
87
+ end
88
+
89
+ # Search an item by ASIN no.
90
+ def self.item_lookup(item_id, opts = {})
91
+ opts[:operation] = 'ItemLookup'
92
+ opts[:item_id] = item_id
93
+
94
+ self.send_request(opts)
95
+ end
96
+
97
+ # Generic send request to ECS REST service. You have to specify the :operation parameter.
98
+ def self.send_request(opts)
99
+ opts = self.options.merge(opts) if self.options
100
+ request_url = prepare_url(opts)
101
+ log "Request URL: #{request_url}"
102
+
103
+ res = Net::HTTP.get_response(URI::parse(request_url))
104
+ unless res.kind_of? Net::HTTPSuccess
105
+ raise Amazon::RequestError, "HTTP Response: #{res.code} #{res.message}"
106
+ end
107
+ Response.new(res.body)
108
+ end
109
+
110
+ # Response object returned after a REST call to Amazon service.
111
+ class Response
112
+ # XML input is in string format
113
+ def initialize(xml)
114
+ @doc = Hpricot(xml)
115
+ end
116
+
117
+ # Return Hpricot object.
118
+ def doc
119
+ @doc
120
+ end
121
+
122
+ # Return true if request is valid.
123
+ def is_valid_request?
124
+ (@doc/"isvalid").inner_html == "True"
125
+ end
126
+
127
+ # Return true if response has an error.
128
+ def has_error?
129
+ !(error.nil? || error.empty?)
130
+ end
131
+
132
+ # Return error message.
133
+ def error
134
+ Element.get(@doc, "error/message")
135
+ end
136
+
137
+ # Return error code
138
+ def error_code
139
+ Element.get(@doc, "error/code")
140
+ end
141
+
142
+ # Return an array of Amazon::Element item objects.
143
+ def items
144
+ unless @items
145
+ @items = (@doc/"item").collect {|item| Element.new(item)}
146
+ end
147
+ @items
148
+ end
149
+
150
+ # Return the first item (Amazon::Element)
151
+ def first_item
152
+ items.first
153
+ end
154
+
155
+ # Return current page no if :item_page option is when initiating the request.
156
+ def item_page
157
+ unless @item_page
158
+ @item_page = (@doc/"itemsearchrequest/itempage").inner_html.to_i
159
+ end
160
+ @item_page
161
+ end
162
+
163
+ # Return total results.
164
+ def total_results
165
+ unless @total_results
166
+ @total_results = (@doc/"totalresults").inner_html.to_i
167
+ end
168
+ @total_results
169
+ end
170
+
171
+ # Return total pages.
172
+ def total_pages
173
+ unless @total_pages
174
+ @total_pages = (@doc/"totalpages").inner_html.to_i
175
+ end
176
+ @total_pages
177
+ end
178
+ end
179
+
180
+ protected
181
+ def self.log(s)
182
+ return unless self.debug
183
+ if defined? RAILS_DEFAULT_LOGGER
184
+ RAILS_DEFAULT_LOGGER.error(s)
185
+ elsif defined? LOGGER
186
+ LOGGER.error(s)
187
+ else
188
+ puts s
189
+ end
190
+ end
191
+
192
+ private
193
+ def self.prepare_url(opts)
194
+ country = opts.delete(:country) || 'us'
195
+ request_url = SERVICE_URLS[country.to_sym]
196
+ raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
197
+
198
+ access_key_id = opts.delete(:aWS_access_key_id)
199
+ secret_access_key = opts.delete(:secret_access_key)
200
+ raise Amazon::RequestError, "secret_access_key is nil" unless secret_access_key
201
+
202
+ opts = Hash[*opts.map { |k, v| [camelize(k.to_s), v.to_s] }.flatten]
203
+
204
+ aws_signer = AwsProductSign.new(:access_key => access_key_id, :secret_key => secret_access_key)
205
+ qs = aws_signer.query_with_signature(opts)
206
+
207
+ "#{request_url}?#{qs}"
208
+ end
209
+
210
+ IPAD = "\x36"
211
+ OPAD = "\x5c"
212
+ def self.hmac_sha256(key, message)
213
+ ikey = IPAD * 64
214
+ okey = OPAD * 64
215
+ key.size.times do |i|
216
+ ikey[i] = key[i] ^ ikey[i]
217
+ okey[i] = key[i] ^ okey[i]
218
+ end
219
+ value = Digest::SHA256.digest(ikey + message)
220
+ value = Digest::SHA256.digest(okey + value)
221
+ end
222
+
223
+ def self.camelize(s)
224
+ s.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
225
+ end
226
+ end
227
+
228
+ # Internal wrapper class to provide convenient method to access Hpricot element value.
229
+ class Element
230
+ # Pass Hpricot::Elements object
231
+ def initialize(element)
232
+ @element = element
233
+ end
234
+
235
+ # Returns Hpricot::Elments object
236
+ def elem
237
+ @element
238
+ end
239
+
240
+ # Find Hpricot::Elements matching the given path. Example: element/"author".
241
+ def /(path)
242
+ elements = @element/path
243
+ return nil if elements.size == 0
244
+ elements
245
+ end
246
+
247
+ # Find Hpricot::Elements matching the given path, and convert to Amazon::Element.
248
+ # Returns an array Amazon::Elements if more than Hpricot::Elements size is greater than 1.
249
+ def search_and_convert(path)
250
+ elements = self./(path)
251
+ return unless elements
252
+ elements = elements.map{|element| Element.new(element)}
253
+ return elements.first if elements.size == 1
254
+ elements
255
+ end
256
+
257
+ # Get the text value of the given path, leave empty to retrieve current element value.
258
+ def get(path='')
259
+ Element.get(@element, path)
260
+ end
261
+
262
+ # Get the unescaped HTML text of the given path.
263
+ def get_unescaped(path='')
264
+ Element.get_unescaped(@element, path)
265
+ end
266
+
267
+ # Get the array values of the given path.
268
+ def get_array(path='')
269
+ Element.get_array(@element, path)
270
+ end
271
+
272
+ # Get the children element text values in hash format with the element names as the hash keys.
273
+ def get_hash(path='')
274
+ Element.get_hash(@element, path)
275
+ end
276
+
277
+ # Similar to #get, except an element object must be passed-in.
278
+ def self.get(element, path='')
279
+ return unless element
280
+ result = element.at(path)
281
+ result = result.inner_html if result
282
+ result
283
+ end
284
+
285
+ # Similar to #get_unescaped, except an element object must be passed-in.
286
+ def self.get_unescaped(element, path='')
287
+ result = get(element, path)
288
+ CGI::unescapeHTML(result) if result
289
+ end
290
+
291
+ # Similar to #get_array, except an element object must be passed-in.
292
+ def self.get_array(element, path='')
293
+ return unless element
294
+
295
+ result = element/path
296
+ if (result.is_a? Hpricot::Elements) || (result.is_a? Array)
297
+ parsed_result = []
298
+ result.each {|item|
299
+ parsed_result << Element.get(item)
300
+ }
301
+ parsed_result
302
+ else
303
+ [Element.get(result)]
304
+ end
305
+ end
306
+
307
+ # Similar to #get_hash, except an element object must be passed-in.
308
+ def self.get_hash(element, path='')
309
+ return unless element
310
+
311
+ result = element.at(path)
312
+ if result
313
+ hash = {}
314
+ result = result.children
315
+ result.each do |item|
316
+ hash[item.name.to_sym] = item.inner_html
317
+ end
318
+ hash
319
+ end
320
+ end
321
+
322
+ def to_s
323
+ elem.to_s if elem
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,93 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ # Example used for testing is from
4
+ # http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/rest-signature.html
5
+ # Although that example itself has some bugs as of 12 may 09 doh.
6
+ # it's output isn't actually valid.
7
+ #
8
+ class AwsProductSignTest < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @example_params = {
12
+ "Service" => "AWSECommerceService",
13
+ "Operation"=>"ItemLookup",
14
+ "ItemId"=>"0679722769",
15
+ "ResponseGroup"=>"ItemAttributes,Offers,Images,Reviews",
16
+ "Version" => "2009-01-06",
17
+ "Timestamp" => "2009-05-13T10:43:28-04:00" # fixed timestamp so we can test output
18
+ }
19
+ @access_key = "00000000000000000000"
20
+ @secret_key = "1234567890"
21
+
22
+ @test_obj = AwsProductSign.new(:secret_key => @secret_key, :access_key => @access_key)
23
+ end
24
+
25
+
26
+ def test_url_encoding
27
+ # Make sure it doesn't encode what it shouldn't.
28
+ reserved_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"
29
+ assert_equal(reserved_chars,
30
+ @test_obj.url_encode(reserved_chars),
31
+ "Reserved chars are not encoded")
32
+
33
+ # Space better be %20 not plus.
34
+ assert_equal("%20", @test_obj.url_encode(" "), "Space is encoded as %20")
35
+
36
+ # Try a sample UTF-8 char, e acute.
37
+ assert_equal("%C3%A9",
38
+ @test_obj.url_encode("\xC3\xA9"),
39
+ "Encodes a UTF-8 char properly")
40
+
41
+ # Make sure it does escape a few other sample chars, although we won't
42
+ # try every possible char!
43
+ chars_to_escape = "%:,/+=" # that last one is a utf-8 e acute.
44
+
45
+ assert_equal( "%25%3A%2C%2F%2B%3D",
46
+ @test_obj.url_encode(chars_to_escape),
47
+ "Some other chars are encoded properly")
48
+ end
49
+
50
+ def test_canonical_query_order
51
+ ordered_keys =
52
+ @test_obj.canonical_querystring(@example_params).split("&").collect { |kv| kv.split("=")[0] }
53
+
54
+ # should be sorted byte-ordered
55
+ assert_equal(ordered_keys,
56
+ ["ItemId", "Operation", "ResponseGroup", "Service", "Timestamp", "Version"])
57
+ end
58
+
59
+ def test_add_signature
60
+ new_params = @test_obj.add_signature( @example_params )
61
+
62
+ assert_not_nil( new_params["Timestamp"], "Adds timestamp" )
63
+ assert_equal( @access_key, new_params["AWSAccessKeyId"], "Adds access key")
64
+ assert_equal("F3xmBlY91rML36hkQTZn/N2Bk3ABIVB8NI+e/JCYpDQ=" ,
65
+ new_params["Signature"],
66
+ "Adds correct signature")
67
+
68
+ assert( @example_params != new_params, "Does not mutate input")
69
+ end
70
+
71
+ def test_add_signature_mutate
72
+ params = Hash[@example_params]
73
+
74
+ @test_obj.add_signature!(params)
75
+
76
+ assert_not_nil( params["Signature"], "Mutates input")
77
+
78
+ end
79
+
80
+ def test_query_string
81
+ require 'cgi'
82
+ params = @test_obj.add_signature(@example_params)
83
+ query_string = @test_obj.query_with_signature(params)
84
+
85
+ re_parsed = CGI.parse(query_string)
86
+ # cgi puts everything in an array, flatten it please.
87
+ re_parsed.each {|k,v| re_parsed[k] = v.first}
88
+
89
+ assert_equal( params, re_parsed, "query string generated" )
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,110 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class Amazon::EcsTest < Test::Unit::TestCase
4
+
5
+ AWS_ACCESS_KEY_ID = '1ZD53WRGR730ZCVWBSG2'
6
+ raise "Please specify set your AWS_ACCESS_KEY_ID" if AWS_ACCESS_KEY_ID.empty?
7
+
8
+ Amazon::Ecs.configure do |options|
9
+ options[:response_group] = 'Large'
10
+ options[:aWS_access_key_id] = AWS_ACCESS_KEY_ID
11
+ end
12
+
13
+ ## Test item_search
14
+
15
+ def test_item_search
16
+ resp = Amazon::Ecs.item_search('ruby')
17
+ assert(resp.is_valid_request?)
18
+ assert(resp.total_results >= 3600)
19
+ assert(resp.total_pages >= 360)
20
+ end
21
+
22
+ def test_item_search_with_paging
23
+ resp = Amazon::Ecs.item_search('ruby', :item_page => 2)
24
+ assert resp.is_valid_request?
25
+ assert 2, resp.item_page
26
+ end
27
+
28
+ def test_item_search_with_invalid_request
29
+ resp = Amazon::Ecs.item_search(nil)
30
+ assert !resp.is_valid_request?
31
+ end
32
+
33
+ def test_item_search_with_no_result
34
+ resp = Amazon::Ecs.item_search('afdsafds')
35
+
36
+ assert resp.is_valid_request?
37
+ assert_equal "We did not find any matches for your request.",
38
+ resp.error
39
+ end
40
+
41
+ def test_item_search_uk
42
+ resp = Amazon::Ecs.item_search('ruby', :country => :uk)
43
+ assert resp.is_valid_request?
44
+ end
45
+
46
+ def test_item_search_by_author
47
+ resp = Amazon::Ecs.item_search('dave', :type => :author)
48
+ assert resp.is_valid_request?
49
+ end
50
+
51
+ def test_item_get
52
+ resp = Amazon::Ecs.item_search("0974514055")
53
+ item = resp.first_item
54
+
55
+ # test get
56
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
57
+ item.get("itemattributes/title")
58
+
59
+ # test get_array
60
+ assert_equal ['Dave Thomas', 'Chad Fowler', 'Andy Hunt'],
61
+ item.get_array("author")
62
+
63
+ # test get_hash
64
+ small_image = item.get_hash("smallimage")
65
+
66
+ assert_equal 3, small_image.keys.size
67
+ assert_match ".jpg", small_image[:url]
68
+ assert_equal "75", small_image[:height]
69
+ assert_equal "59", small_image[:width]
70
+
71
+ # test /
72
+ reviews = item/"editorialreview"
73
+ reviews.each do |review|
74
+ # returns unescaped HTML content, Hpricot escapes all text values
75
+ assert Amazon::Element.get_unescaped(review, 'source')
76
+ assert Amazon::Element.get_unescaped(review, 'content')
77
+ end
78
+ end
79
+
80
+ ## Test item_lookup
81
+ def test_item_lookup
82
+ resp = Amazon::Ecs.item_lookup('0974514055')
83
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
84
+ resp.first_item.get("itemattributes/title")
85
+ end
86
+
87
+ def test_item_lookup_with_invalid_request
88
+ resp = Amazon::Ecs.item_lookup(nil)
89
+ assert resp.has_error?
90
+ assert resp.error
91
+ end
92
+
93
+ def test_item_lookup_with_no_result
94
+ resp = Amazon::Ecs.item_lookup('abc')
95
+
96
+ assert resp.is_valid_request?
97
+ assert_match(/ABC is not a valid value for ItemId/, resp.error)
98
+ end
99
+
100
+ def test_search_and_convert
101
+ resp = Amazon::Ecs.item_lookup('0974514055')
102
+ title = resp.first_item.get("itemattributes/title")
103
+ authors = resp.first_item.search_and_convert("author")
104
+
105
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition", title
106
+ assert authors.is_a?(Array)
107
+ assert 3, authors.size
108
+ assert_equal "Dave Thomas", authors.first.get
109
+ end
110
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mct-amazon-ecs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.7
5
+ platform: ruby
6
+ authors:
7
+ - Herryanto Siatono
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.4"
24
+ version:
25
+ description:
26
+ email: herryanto@pluitsolutions.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ - CHANGELOG
34
+ files:
35
+ - lib/amazon-ecs.rb
36
+ - lib/amazon/aws_product_sign.rb
37
+ - lib/amazon/ecs.rb
38
+ - README
39
+ - CHANGELOG
40
+ has_rdoc: true
41
+ homepage: http://amazon-ecs.rubyforge.net/
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Generic Amazon E-commerce Service (ECS) REST API. Supports ECS 4.0.
68
+ test_files:
69
+ - test/amazon/ecs_test.rb
70
+ - test/amazon/aws_product_sign_test.rb