amazon_associate 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ module AmazonAssociate
2
+ class RequestError < RuntimeError
3
+ end
4
+ end
@@ -0,0 +1,74 @@
1
+ module AmazonAssociate
2
+ # Response object returned after a REST call to Amazon service.
3
+ class Response
4
+
5
+ attr_accessor :request_url, :unsigned_url
6
+ # XML input is in string format
7
+ def initialize(xml, request_url)
8
+ @doc = Hpricot(xml)
9
+ @items = nil
10
+ @item_page = nil
11
+ @total_results = nil
12
+ @total_pages = nil
13
+
14
+ self.request_url = request_url
15
+ end
16
+
17
+ # Return Hpricot object.
18
+ def doc
19
+ @doc
20
+ end
21
+
22
+ # Return true if request is valid.
23
+ def is_valid_request?
24
+ (@doc/"isvalid").inner_html == "True"
25
+ end
26
+
27
+ # Return true if response has an error.
28
+ def has_error?
29
+ !(error.nil? || error.empty?)
30
+ end
31
+
32
+ # Return error message.
33
+ def error
34
+ Element.get(@doc, "error/message")
35
+ end
36
+
37
+ # Return an array of AmazonAssociate::Element item objects.
38
+ def items
39
+ unless @items
40
+ @items = (@doc/"item").collect {|item| Element.new(item)}
41
+ end
42
+ @items
43
+ end
44
+
45
+ # Return the first item (AmazonAssociate::Element)
46
+ def first_item
47
+ items.first
48
+ end
49
+
50
+ # Return current page no if :item_page option is when initiating the request.
51
+ def item_page
52
+ unless @item_page
53
+ @item_page = (@doc/"itemsearchrequest/itempage").inner_html.to_i
54
+ end
55
+ @item_page
56
+ end
57
+
58
+ # Return total results.
59
+ def total_results
60
+ unless @total_results
61
+ @total_results = (@doc/"totalresults").inner_html.to_i
62
+ end
63
+ @total_results
64
+ end
65
+
66
+ # Return total pages.
67
+ def total_pages
68
+ unless @total_pages
69
+ @total_pages = (@doc/"totalpages").inner_html.to_i
70
+ end
71
+ @total_pages
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+
3
+ class AmazonAssociate::BrowseNodeLookupTest < Test::Unit::TestCase
4
+
5
+ ## Test browse_node_lookup
6
+ def test_browse_node_lookup
7
+ resp = AmazonAssociate::Request.browse_node_lookup("5", :response_group => "TopSellers")
8
+ assert resp.is_valid_request?
9
+ browse_node_tags = resp.doc.get_elements_by_tag_name("browsenodeid")
10
+ browse_node_tags.each { |node| assert_equal("5", node.inner_text) }
11
+ assert_equal "TopSellers", resp.doc.get_elements_by_tag_name("responsegroup").inner_text
12
+ end
13
+
14
+ def test_browse_node_lookup_with_browse_node_info_response
15
+ resp = AmazonAssociate::Request.browse_node_lookup("5", :response_group => "BrowseNodeInfo")
16
+ assert resp.is_valid_request?
17
+ assert_equal "BrowseNodeInfo", resp.doc.get_elements_by_tag_name("responsegroup").inner_text
18
+ end
19
+
20
+ def test_browse_node_lookup_with_new_releases_response
21
+ resp = AmazonAssociate::Request.browse_node_lookup("5", :response_group => "NewReleases")
22
+ assert resp.is_valid_request?
23
+ assert_equal "NewReleases", resp.doc.get_elements_by_tag_name("responsegroup").inner_text
24
+ end
25
+
26
+ def test_browse_node_lookup_with_invalid_request
27
+ resp = AmazonAssociate::Request.browse_node_lookup(nil)
28
+ assert resp.has_error?
29
+ assert resp.error
30
+ end
31
+
32
+ def test_browse_node_lookup_with_no_result
33
+ resp = AmazonAssociate::Request.browse_node_lookup("abc")
34
+
35
+ assert resp.is_valid_request?
36
+ assert_match(/abc is not a valid value for BrowseNodeId/, resp.error)
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+
3
+ class AmazonAssociate::CacheTest < Test::Unit::TestCase
4
+ include FilesystemTestHelper
5
+ context "caching get" do
6
+ setup do
7
+ get_cache_directory
8
+ get_valid_caching_options
9
+ end
10
+
11
+ teardown do
12
+ destroy_cache_directory
13
+ destroy_caching_options
14
+ end
15
+
16
+ should "optionally allow for a caching strategy in configuration" do
17
+ assert_nothing_raised do
18
+ AmazonAssociate::Request.configure do |options|
19
+ options[:caching_strategy] = :filesystem
20
+ end
21
+ end
22
+ end
23
+
24
+ should "raise an exception if a caching strategy is specified that is not found" do
25
+ assert_raises(AmazonAssociate::ConfigurationError) do
26
+ AmazonAssociate::Request.configure do |options|
27
+ options[:caching_strategy] = "foo"
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,193 @@
1
+ require File.dirname(__FILE__) + "/../../test_helper"
2
+
3
+ class AmazonAssociate::CachingStrategy::FilesystemTest < Test::Unit::TestCase
4
+ include FilesystemTestHelper
5
+ context "setting up filesystem caching" do
6
+ teardown do
7
+ AmazonAssociate::Request.configure do |options|
8
+ options[:caching_strategy] = nil
9
+ options[:caching_options] = nil
10
+ end
11
+ end
12
+
13
+ should "require a caching options hash with a cache_path key" do
14
+ assert_raises(AmazonAssociate::ConfigurationError) do
15
+ AmazonAssociate::Request.configure do |options|
16
+ options[:caching_strategy] = :filesystem
17
+ options[:caching_options] = nil
18
+ end
19
+ end
20
+ end
21
+
22
+ should "raise an exception when a cache_path is specified that doesn't exist" do
23
+ assert_raises(AmazonAssociate::ConfigurationError) do
24
+ AmazonAssociate::Request.configure do |options|
25
+ options[:caching_strategy] = :filesystem
26
+ options[:caching_options] = {:cache_path => "foo123"}
27
+ end
28
+ end
29
+ end
30
+
31
+ should "raise an exception when a cache_path is nil" do
32
+ assert_raise(AmazonAssociate::ConfigurationError) do
33
+ AmazonAssociate::Request.configure do |options|
34
+ options[:caching_strategy] = :filesystem
35
+ options[:caching_options] = {}
36
+ end
37
+ end
38
+ end
39
+
40
+ should "set default values for disk_quota and sweep_frequency" do
41
+ AmazonAssociate::Request.configure do |options|
42
+ options[:caching_strategy] = :filesystem
43
+ options[:caching_options] = {:cache_path => "."}
44
+ end
45
+
46
+ assert_equal AmazonAssociate::CachingStrategy::Filesystem.disk_quota, AmazonAssociate::CachingStrategy::Filesystem.disk_quota
47
+ assert_equal AmazonAssociate::CachingStrategy::Filesystem.sweep_frequency, AmazonAssociate::CachingStrategy::Filesystem.sweep_frequency
48
+ end
49
+
50
+ should "override the default value for disk quota if I specify one" do
51
+ quota = 400
52
+ AmazonAssociate::Request.configure do |options|
53
+ options[:caching_strategy] = :filesystem
54
+ options[:caching_options] = {:cache_path => ".", :disk_quota => quota}
55
+ end
56
+
57
+ assert_equal quota, AmazonAssociate::CachingStrategy::Filesystem.disk_quota
58
+ end
59
+
60
+ should "override the default value for cache_frequency if I specify one" do
61
+ frequency = 4
62
+ AmazonAssociate::Request.configure do |options|
63
+ options[:caching_strategy] = :filesystem
64
+ options[:caching_options] = {:cache_path => ".", :sweep_frequency => frequency}
65
+ end
66
+
67
+ assert_equal frequency, AmazonAssociate::CachingStrategy::Filesystem.sweep_frequency
68
+ end
69
+ end
70
+
71
+ context "caching a request" do
72
+
73
+ setup do
74
+ get_cache_directory
75
+ get_valid_caching_options
76
+ @resp = AmazonAssociate::Request.item_lookup("0974514055")
77
+ @filename = Digest::SHA1.hexdigest(@resp.unsigned_url)
78
+ end
79
+
80
+ teardown do
81
+ destroy_cache_directory
82
+ destroy_caching_options
83
+ end
84
+
85
+ should "create a folder in the cache path with the first three letters of the digested filename" do
86
+ filename = Digest::SHA1.hexdigest(@resp.request_url)
87
+ FileTest.exists?(File.join(@@cache_path, @filename[0..2]))
88
+ end
89
+
90
+ should "create a file in the cache path with a digested version of the url " do
91
+
92
+ filename = Digest::SHA1.hexdigest(@resp.request_url)
93
+ assert FileTest.exists?(File.join(@@cache_path, @filename[0..2], @filename))
94
+ end
95
+
96
+ should "create a file in the cache path with the response inside it" do
97
+ assert FileTest.exists?(File.join(@@cache_path + @filename[0..2], @filename))
98
+ assert_equal @resp.doc.to_s, File.read(File.join(@@cache_path + @filename[0..2], @filename)).chomp
99
+ end
100
+
101
+ should "not send cache parameters in the request" do
102
+ [:caching_strategy].each do |param|
103
+ assert_no_match /#{param.to_s}/, @resp.request_url
104
+ end
105
+ end
106
+ end
107
+
108
+ context "getting a cached request" do
109
+ setup do
110
+ get_cache_directory
111
+ get_valid_caching_options
112
+ do_request
113
+ end
114
+
115
+ teardown do
116
+ destroy_cache_directory
117
+ destroy_caching_options
118
+ end
119
+
120
+ should "not do an http request the second time the lookup is performed due a cached copy" do
121
+ Net::HTTP.expects(:get_response).never
122
+ do_request
123
+ end
124
+
125
+ end
126
+
127
+ context "sweeping cached requests" do
128
+ setup do
129
+ get_cache_directory
130
+ get_valid_caching_options
131
+ do_request
132
+ end
133
+
134
+ teardown do
135
+ destroy_cache_directory
136
+ destroy_caching_options
137
+ end
138
+
139
+ should "not perform the sweep if the timestamp is within the range of the sweep frequency and quota is not exceeded" do
140
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:sweep_time_expired?).returns(false)
141
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:disk_quota_exceeded?).returns(false)
142
+
143
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:perform_sweep).never
144
+
145
+ do_request
146
+ end
147
+
148
+ should "perform a sweep if the quota is exceeded" do
149
+ AmazonAssociate::CachingStrategy::Filesystem.stubs(:sweep_time_expired?).returns(false)
150
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:disk_quota_exceeded?).once.returns(true)
151
+
152
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:perform_sweep).once
153
+
154
+ do_request
155
+ end
156
+
157
+ should "perform a sweep if the sweep time is expired" do
158
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:sweep_time_expired?).once.returns(true)
159
+ AmazonAssociate::CachingStrategy::Filesystem.stubs(:disk_quota_exceeded?).returns(false)
160
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:perform_sweep).once
161
+
162
+ do_request
163
+ end
164
+
165
+ should "create a timestamp file after performing a sweep" do
166
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:sweep_time_expired?).once.returns(true)
167
+
168
+ do_request
169
+ assert FileTest.exists?(File.join(@@cache_path, ".amz_timestamp"))
170
+ end
171
+
172
+ should "purge the cache when performing a sweep" do
173
+ (0..9).each do |n|
174
+ test = File.open(File.join(@@cache_path, "test_file_#{n}"), "w")
175
+ test.puts Time.now
176
+ test.close
177
+ end
178
+
179
+ AmazonAssociate::CachingStrategy::Filesystem.expects(:sweep_time_expired?).once.returns(true)
180
+ do_request
181
+
182
+ (0..9).each do |n|
183
+ assert !FileTest.exists?(File.join(@@cache_path, "test_file_#{n}"))
184
+ end
185
+ end
186
+
187
+ end
188
+
189
+ protected
190
+ def do_request
191
+ @resp = AmazonAssociate::Request.item_lookup("0974514055")
192
+ end
193
+ end
@@ -0,0 +1,90 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+
3
+ class AmazonAssociate::CartTest < Test::Unit::TestCase
4
+
5
+ # create a cart to store cart_id and hmac for add, get, modify, and clear tests
6
+ def setup
7
+ sleep(2)
8
+ @asin = "0672328844"
9
+ resp = AmazonAssociate::Request.cart_create(@asin)
10
+ @cart_id = resp.doc.get_elements_by_tag_name("cartid").inner_text
11
+ @hmac = resp.doc.get_elements_by_tag_name("hmac").inner_text
12
+ item = resp.first_item
13
+ # run tests for cart_create with default quantity while we"re at it
14
+ assert resp.is_valid_request?
15
+ assert_equal @asin, item.get("asin")
16
+ assert_equal "1", item.get("quantity")
17
+ assert_not_nil @cart_id
18
+ assert_not_nil @hmac
19
+ end
20
+
21
+ # Test cart_get
22
+ def test_cart_get
23
+ resp = AmazonAssociate::Request.cart_get(@cart_id, @hmac)
24
+ assert resp.is_valid_request?
25
+ assert_not_nil resp.doc.get_elements_by_tag_name("purchaseurl").inner_text
26
+ end
27
+
28
+ # Test cart_modify
29
+ def test_cart_modify
30
+ resp = AmazonAssociate::Request.cart_get(@cart_id, @hmac)
31
+ cart_item_id = resp.doc.get_elements_by_tag_name("cartitemid").inner_text
32
+ resp = AmazonAssociate::Request.cart_modify(cart_item_id, @cart_id, @hmac, 2)
33
+ item = resp.first_item
34
+
35
+ assert resp.is_valid_request?
36
+ assert_equal "2", item.get("quantity")
37
+ assert_not_nil resp.doc.get_elements_by_tag_name("purchaseurl").inner_text
38
+ end
39
+
40
+ # Test cart_clear
41
+ def test_cart_clear
42
+ resp = AmazonAssociate::Request.cart_clear(@cart_id, @hmac)
43
+ assert resp.is_valid_request?
44
+ end
45
+
46
+ ## Test cart_create with a specified quantity
47
+ ## note this will create a separate cart
48
+ def test_cart_create_with_quantity
49
+ asin = "0672328844"
50
+ resp = AmazonAssociate::Request.cart_create(asin, :quantity => 2)
51
+ assert resp.is_valid_request?
52
+ item = resp.first_item
53
+ assert_equal asin, item.get("asin")
54
+ assert_equal "2", item.get("quantity")
55
+ assert_not_nil resp.doc.get_elements_by_tag_name("cartid").inner_text
56
+ assert_not_nil resp.doc.get_elements_by_tag_name("hmac").inner_text
57
+ end
58
+
59
+ # Test cart_create with an array of hashes representing multiple items
60
+ def test_cart_create_with_multiple_items
61
+ items = [ { :asin => "0974514055", :quantity => 2 }, { :asin => "0672328844", :quantity => 3 } ]
62
+ resp = AmazonAssociate::Request.cart_create(items)
63
+ assert resp.is_valid_request?
64
+ first_item, second_item = resp.items.reverse[0], resp.items.reverse[1]
65
+
66
+ assert_equal items[0][:asin], first_item.get("asin")
67
+ assert_equal items[0][:quantity].to_s, first_item.get("quantity")
68
+
69
+ assert_equal items[1][:asin], second_item.get("asin")
70
+ assert_equal items[1][:quantity].to_s, second_item.get("quantity")
71
+
72
+ assert_not_nil resp.doc.get_elements_by_tag_name("cartid").inner_text
73
+ assert_not_nil resp.doc.get_elements_by_tag_name("hmac").inner_text
74
+ end
75
+
76
+ # Test cart_create with offer_listing_id instead of asin
77
+ def test_cart_create_with_offer_listing_id
78
+ items = [ { :offer_listing_id => "MCK%2FnCXIges8tpX%2B222nOYEqeZ4AzbrFyiHuP6pFf45N3vZHTm8hFTytRF%2FLRONNkVmt182%2BmeX72n%2BbtUcGEtpLN92Oy9Y7", :quantity => 2 } ]
79
+ resp = AmazonAssociate::Request.cart_create(items)
80
+ assert resp.is_valid_request?
81
+ first_item = resp.items.first
82
+
83
+ assert_equal items[0][:offer_listing_id], first_item.get("offerlistingid")
84
+ assert_equal items[0][:quantity].to_s, first_item.get("quantity")
85
+
86
+ assert_not_nil resp.doc.get_elements_by_tag_name("cartid").inner_text
87
+ assert_not_nil resp.doc.get_elements_by_tag_name("hmac").inner_text
88
+ end
89
+
90
+ end
@@ -0,0 +1,108 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+
3
+ class AmazonAssociate::RequestTest < Test::Unit::TestCase
4
+ def setup
5
+ sleep(1)
6
+ AmazonAssociate::Request.configure do |options|
7
+ options[:response_group] = "Large"
8
+ end
9
+ end
10
+
11
+ ## Test item_search
12
+ def test_item_search
13
+ resp = AmazonAssociate::Request.item_search("ruby")
14
+ assert(resp.is_valid_request?)
15
+ assert(resp.total_results >= 3600)
16
+ assert(resp.total_pages >= 360)
17
+ end
18
+
19
+ def test_item_search_with_paging
20
+ resp = AmazonAssociate::Request.item_search("ruby", :item_page => 2)
21
+ assert resp.is_valid_request?
22
+ assert 2, resp.item_page
23
+ end
24
+
25
+ def test_item_search_with_invalid_request
26
+ resp = AmazonAssociate::Request.item_search(nil)
27
+ assert !resp.is_valid_request?
28
+ end
29
+
30
+ def test_item_search_with_no_result
31
+ resp = AmazonAssociate::Request.item_search("afdsafds")
32
+
33
+ assert resp.is_valid_request?
34
+ assert_equal "We did not find any matches for your request.",
35
+ resp.error
36
+ end
37
+
38
+ def test_item_search_uk
39
+ resp = AmazonAssociate::Request.item_search("ruby", :country => :uk)
40
+ assert resp.is_valid_request?
41
+ end
42
+
43
+ def test_item_search_by_author
44
+ resp = AmazonAssociate::Request.item_search("dave", :type => :author)
45
+ assert resp.is_valid_request?
46
+ end
47
+
48
+ def test_item_get
49
+ resp = AmazonAssociate::Request.item_search("0974514055")
50
+ item = resp.first_item
51
+
52
+ # test get
53
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
54
+ item.get("itemattributes/title")
55
+
56
+ # test get_array
57
+ assert_equal ["Dave Thomas", "Chad Fowler", "Andy Hunt"],
58
+ item.get_array("author")
59
+
60
+ # test get_hash
61
+ small_image = item.get_hash("smallimage")
62
+
63
+ assert_equal 3, small_image.keys.size
64
+ assert small_image[:url] != nil
65
+ assert_equal "75", small_image[:height]
66
+ assert_equal "59", small_image[:width]
67
+
68
+ # test /
69
+ reviews = item/"editorialreview"
70
+ reviews.each do |review|
71
+ # returns unescaped HTML content, Hpricot escapes all text values
72
+ assert AmazonAssociate::Element.get_unescaped(review, "source")
73
+ assert AmazonAssociate::Element.get_unescaped(review, "content")
74
+ end
75
+ end
76
+
77
+ ## Test item_lookup
78
+ def test_item_lookup
79
+ resp = AmazonAssociate::Request.item_lookup("0974514055")
80
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition",
81
+ resp.first_item.get("itemattributes/title")
82
+ end
83
+
84
+ def test_item_lookup_with_invalid_request
85
+ resp = AmazonAssociate::Request.item_lookup(nil)
86
+ assert resp.has_error?
87
+ assert resp.error
88
+ end
89
+
90
+ def test_item_lookup_with_no_result
91
+ resp = AmazonAssociate::Request.item_lookup("abc")
92
+
93
+ assert resp.is_valid_request?
94
+ assert_match(/ABC is not a valid value for ItemId/, resp.error)
95
+ end
96
+
97
+ def test_search_and_convert
98
+ resp = AmazonAssociate::Request.item_lookup("0974514055")
99
+ title = resp.first_item.get("itemattributes/title")
100
+ authors = resp.first_item.search_and_convert("author")
101
+
102
+ assert_equal "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition", title
103
+ assert authors.is_a?(Array)
104
+ assert 3, authors.size
105
+ assert_equal "Dave Thomas", authors.first.get
106
+ end
107
+
108
+ end