mct-amazon-ecs 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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