sucker 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -19
- data/lib/sucker.rb +4 -4
- data/lib/sucker/parameters.rb +28 -0
- data/lib/sucker/request.rb +100 -88
- data/lib/sucker/response.rb +10 -0
- data/lib/sucker/version.rb +1 -1
- data/spec/fixtures/cassette_library/{unit → spec}/sucker/request.yml +4 -4
- data/spec/fixtures/cassette_library/spec/sucker/response.yml +26 -0
- data/spec/sucker/parameters_spec.rb +61 -0
- data/spec/sucker/request_spec.rb +276 -0
- data/spec/{unit/sucker → sucker}/response_spec.rb +39 -60
- data/spec/{unit/sucker_spec.rb → sucker_spec.rb} +0 -4
- data/spec/support/vcr.rb +0 -1
- metadata +69 -114
- data/spec/fixtures/cassette_library/integration/alternate_versions.yml +0 -26
- data/spec/fixtures/cassette_library/integration/errors.yml +0 -26
- data/spec/fixtures/cassette_library/integration/france.yml +0 -26
- data/spec/fixtures/cassette_library/integration/images.yml +0 -26
- data/spec/fixtures/cassette_library/integration/item_lookup/multiple.yml +0 -26
- data/spec/fixtures/cassette_library/integration/item_lookup/single.yml +0 -26
- data/spec/fixtures/cassette_library/integration/item_search.yml +0 -26
- data/spec/fixtures/cassette_library/integration/japan.yml +0 -26
- data/spec/fixtures/cassette_library/integration/keyword_search.yml +0 -26
- data/spec/fixtures/cassette_library/integration/kindle.yml +0 -26
- data/spec/fixtures/cassette_library/integration/kindle_2.yml +0 -26
- data/spec/fixtures/cassette_library/integration/multiple_locales.yml +0 -151
- data/spec/fixtures/cassette_library/integration/power_search.yml +0 -26
- data/spec/fixtures/cassette_library/integration/related_items/child.yml +0 -26
- data/spec/fixtures/cassette_library/integration/related_items/parent.yml +0 -26
- data/spec/fixtures/cassette_library/integration/seller_listings_search.yml +0 -26
- data/spec/fixtures/cassette_library/integration/twenty_items.yml +0 -26
- data/spec/fixtures/cassette_library/unit/sucker/response.yml +0 -26
- data/spec/integration/alternate_versions_spec.rb +0 -35
- data/spec/integration/errors_spec.rb +0 -40
- data/spec/integration/france_spec.rb +0 -42
- data/spec/integration/images_spec.rb +0 -41
- data/spec/integration/item_lookup_spec.rb +0 -71
- data/spec/integration/item_search_spec.rb +0 -41
- data/spec/integration/japan_spec.rb +0 -35
- data/spec/integration/keyword_search_spec.rb +0 -33
- data/spec/integration/kindle_spec.rb +0 -55
- data/spec/integration/multiple_locales_spec.rb +0 -70
- data/spec/integration/power_search_spec.rb +0 -41
- data/spec/integration/related_items_spec.rb +0 -53
- data/spec/integration/seller_listing_search_spec.rb +0 -32
- data/spec/integration/twenty_items_spec.rb +0 -49
- data/spec/unit/sucker/request_spec.rb +0 -282
@@ -1,70 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require "spec_helper"
|
3
|
-
|
4
|
-
module Sucker
|
5
|
-
|
6
|
-
describe "Item lookup" do
|
7
|
-
|
8
|
-
use_vcr_cassette "integration/multiple_locales", :record => :new_episodes
|
9
|
-
|
10
|
-
context "when using Curl::Multi to search all locales simultaneously" do
|
11
|
-
|
12
|
-
it "returns matches for all locales" do
|
13
|
-
worker = Sucker.new(
|
14
|
-
:key => amazon["key"],
|
15
|
-
:secret => amazon["secret"])
|
16
|
-
worker << {
|
17
|
-
"Operation" => "ItemLookup",
|
18
|
-
"IdType" => "ASIN",
|
19
|
-
"ResponseGroup" => "ItemAttributes",
|
20
|
-
"ItemId" => "0816614024" }
|
21
|
-
|
22
|
-
bindings = worker.get_all.map do |response|
|
23
|
-
item = response.find("Item").first
|
24
|
-
item["ItemAttributes"]["Binding"]
|
25
|
-
end
|
26
|
-
|
27
|
-
bindings.uniq.should =~ %w{ Paperback Taschenbuch Broché ペーパーバック }
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
context "when using threads to search all locales simultaneously" do
|
34
|
-
|
35
|
-
# Use this approach to be able to throttle requests individually.
|
36
|
-
|
37
|
-
it "returns matches for all locales" do
|
38
|
-
locales = %w{us uk de ca fr jp}
|
39
|
-
|
40
|
-
params = {
|
41
|
-
"Operation" => "ItemLookup",
|
42
|
-
"IdType" => "ASIN",
|
43
|
-
"ResponseGroup" => "ItemAttributes",
|
44
|
-
"ItemId" => "0816614024" }
|
45
|
-
|
46
|
-
threads = locales.map do |locale|
|
47
|
-
Thread.new do
|
48
|
-
worker = Sucker.new(
|
49
|
-
:locale => locale,
|
50
|
-
:key => amazon["key"],
|
51
|
-
:secret => amazon["secret"])
|
52
|
-
worker << params
|
53
|
-
Thread.current[:response] = worker.get
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
bindings = threads.map do |thread|
|
58
|
-
thread.join
|
59
|
-
item = thread[:response].find("Item").first
|
60
|
-
item["ItemAttributes"]["Binding"]
|
61
|
-
end
|
62
|
-
|
63
|
-
bindings.uniq.should =~ %w{ Paperback Taschenbuch Broché ペーパーバック }
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
# http://docs.amazonwebservices.com/AWSECommerceService/2010-09-01/DG/index.html?PowerSearchSyntax.html
|
4
|
-
#
|
5
|
-
# author: ambrose and binding: (abridged or large print) and pubdate: after 11-1996
|
6
|
-
# subject: history and (Spain or Mexico) and not military and language: Spanish
|
7
|
-
# (subject: marketing and author: kotler) or (publisher: harper and subject: "high technology")
|
8
|
-
# keywords: "high tech*" and not fiction and pubdate: during 1999
|
9
|
-
# isbn: 0446394319 or 0306806819 or 1567993850
|
10
|
-
|
11
|
-
module Sucker
|
12
|
-
|
13
|
-
describe "Power search" do
|
14
|
-
|
15
|
-
use_vcr_cassette "integration/power_search", :record => :new_episodes
|
16
|
-
|
17
|
-
let(:worker) do
|
18
|
-
worker = Sucker.new(
|
19
|
-
:locale => "us",
|
20
|
-
:key => amazon["key"],
|
21
|
-
:secret => amazon["secret"])
|
22
|
-
worker << {
|
23
|
-
"Operation" => "ItemSearch",
|
24
|
-
"SearchIndex" => "Books",
|
25
|
-
"Power" => "author:lacan or deleuze and not fiction",
|
26
|
-
"Sort" => "relevancerank" }
|
27
|
-
worker
|
28
|
-
end
|
29
|
-
|
30
|
-
it "returns matches" do
|
31
|
-
items = worker.get.find("Item")
|
32
|
-
items.size.should > 0
|
33
|
-
items.each do |item|
|
34
|
-
item["ItemAttributes"]["Title"].should_not be_nil
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Sucker
|
4
|
-
|
5
|
-
describe "Item lookup" do
|
6
|
-
|
7
|
-
context "when response group includes related items" do
|
8
|
-
|
9
|
-
let(:worker) do
|
10
|
-
worker = Sucker.new(
|
11
|
-
:locale => "us",
|
12
|
-
:key => amazon["key"],
|
13
|
-
:secret => amazon["secret"])
|
14
|
-
|
15
|
-
worker << {
|
16
|
-
"Operation" => "ItemLookup",
|
17
|
-
"IdType" => "ASIN",
|
18
|
-
"ResponseGroup" => ["RelatedItems"],
|
19
|
-
"RelationshipType" => "AuthorityTitle" }
|
20
|
-
|
21
|
-
worker
|
22
|
-
end
|
23
|
-
|
24
|
-
context "when item is a child" do
|
25
|
-
|
26
|
-
use_vcr_cassette "integration/related_items/child", :record => :new_episodes
|
27
|
-
|
28
|
-
it "finds parent and related items" do
|
29
|
-
worker << { "ItemId" => "0415246334" }
|
30
|
-
response = worker.get
|
31
|
-
response.find("RelatedItem").size.should eql 1
|
32
|
-
parent_asin = response.find("RelatedItem").first["Item"]["ASIN"]
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
context "when item is a parent" do
|
38
|
-
|
39
|
-
use_vcr_cassette "integration/related_items/parent", :record => :new_episodes
|
40
|
-
|
41
|
-
it "finds related items" do
|
42
|
-
worker << { "ItemId" => "B000ASPUES" }
|
43
|
-
response = worker.get
|
44
|
-
response.find("RelatedItem").size.should > 1
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Sucker
|
4
|
-
describe "Seller listing search" do
|
5
|
-
|
6
|
-
use_vcr_cassette "integration/seller_listings_search", :record => :new_episodes
|
7
|
-
|
8
|
-
let(:listings) do
|
9
|
-
worker = Sucker.new(
|
10
|
-
:locale => "us",
|
11
|
-
:key => amazon["key"],
|
12
|
-
:secret => amazon["secret"])
|
13
|
-
|
14
|
-
worker << {
|
15
|
-
"Operation" => "SellerListingSearch",
|
16
|
-
"SellerId" => "A2JYSO6W6KEP83" }
|
17
|
-
|
18
|
-
worker.get.find("SellerListings").first
|
19
|
-
end
|
20
|
-
|
21
|
-
it "returns page count" do
|
22
|
-
listings["TotalPages"].to_i.should be > 0
|
23
|
-
end
|
24
|
-
|
25
|
-
it "returns listings" do
|
26
|
-
listings["SellerListing"].size.should be > 0
|
27
|
-
listings["SellerListing"].first.has_key?("Price").should be_true
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Sucker
|
4
|
-
describe "Item lookup" do
|
5
|
-
|
6
|
-
context "when querying for twenty items" do
|
7
|
-
|
8
|
-
use_vcr_cassette "integration/twenty_items", :record => :new_episodes
|
9
|
-
|
10
|
-
let(:asins) do
|
11
|
-
%w{
|
12
|
-
0816614024 0143105825 0485113600 0816616779 0942299078
|
13
|
-
0816614008 144006654X 0486400360 0486417670 087220474X
|
14
|
-
0486454398 0268018359 1604246014 184467598X 0312427182
|
15
|
-
1844674282 0745640974 0745646441 0826489540 1844672972 }
|
16
|
-
end
|
17
|
-
|
18
|
-
let(:items) do
|
19
|
-
worker = Sucker.new(
|
20
|
-
:locale => "us",
|
21
|
-
:key => amazon["key"],
|
22
|
-
:secret => amazon["secret"])
|
23
|
-
|
24
|
-
# Prep worker
|
25
|
-
worker << {
|
26
|
-
"Operation" => "ItemLookup",
|
27
|
-
"ItemLookup.Shared.IdType" => "ASIN",
|
28
|
-
"ItemLookup.Shared.Condition" => "All",
|
29
|
-
"ItemLookup.Shared.MerchantId" => "All",
|
30
|
-
"ItemLookup.Shared.ResponseGroup" => "OfferFull" }
|
31
|
-
|
32
|
-
# Push twenty ASINs to worker
|
33
|
-
worker << {
|
34
|
-
"ItemLookup.1.ItemId" => asins[0, 10],
|
35
|
-
"ItemLookup.2.ItemId" => asins[10, 10] }
|
36
|
-
|
37
|
-
worker.get.find("Item")
|
38
|
-
end
|
39
|
-
|
40
|
-
it "returns all items" do
|
41
|
-
items.count.should eql 20
|
42
|
-
items.map { |item| item["ASIN"] }.should eql asins
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
@@ -1,282 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Sucker
|
4
|
-
|
5
|
-
describe Request do
|
6
|
-
|
7
|
-
use_vcr_cassette "unit/sucker/request", :record => :new_episodes
|
8
|
-
|
9
|
-
let(:worker) do
|
10
|
-
Sucker.new(
|
11
|
-
:locale => "us",
|
12
|
-
:key => "key",
|
13
|
-
:secret => "secret")
|
14
|
-
end
|
15
|
-
|
16
|
-
describe ".new" do
|
17
|
-
|
18
|
-
it "sets default parameters" do
|
19
|
-
default_parameters = {
|
20
|
-
"Service" => "AWSECommerceService",
|
21
|
-
"Version" => Sucker::CURRENT_AMAZON_API_VERSION }
|
22
|
-
worker.parameters.should include default_parameters
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "#<<" do
|
28
|
-
|
29
|
-
it "merges a hash into the parameters" do
|
30
|
-
worker << { "foo" => "bar" }
|
31
|
-
worker.parameters["foo"].should eql "bar"
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
describe "#version=" do
|
37
|
-
|
38
|
-
it "sets the Amazon API version" do
|
39
|
-
worker.version = "foo"
|
40
|
-
worker.parameters["Version"].should eql "foo"
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
describe "#associate_tag" do
|
46
|
-
|
47
|
-
it "returns the associate tag for the current locale" do
|
48
|
-
worker.instance_variable_set(:@associate_tags, { :us => 'foo-bar'})
|
49
|
-
|
50
|
-
worker.associate_tag.should eql 'foo-bar'
|
51
|
-
end
|
52
|
-
|
53
|
-
it "returns nil if an associate tag is not set for the current locale" do
|
54
|
-
worker.associate_tag.should eql nil
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
describe "#associate_tag=" do
|
60
|
-
|
61
|
-
it "sets the associate tag for the current locale" do
|
62
|
-
worker.associate_tag = "foo-bar"
|
63
|
-
associate_tags = worker.instance_variable_get(:@associate_tags)
|
64
|
-
|
65
|
-
associate_tags.keys.size.should eql 1
|
66
|
-
associate_tags[:us].should eql 'foo-bar'
|
67
|
-
worker.associate_tag.should eql 'foo-bar'
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "#associate_tags=" do
|
73
|
-
|
74
|
-
it "sets associate tags for the locales" do
|
75
|
-
tags = {
|
76
|
-
:us => 'foo',
|
77
|
-
:uk => 'bar',
|
78
|
-
:de => 'baz',
|
79
|
-
:ca => 'foo',
|
80
|
-
:fr => 'bar',
|
81
|
-
:jp => 'baz' }
|
82
|
-
worker.associate_tags = tags
|
83
|
-
|
84
|
-
worker.instance_variable_get(:@associate_tags).should eql tags
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
describe "#curl_opts" do
|
90
|
-
|
91
|
-
it "returns options for curl" do
|
92
|
-
worker.curl_opts.should be_an_instance_of Hash
|
93
|
-
end
|
94
|
-
|
95
|
-
context "when given a block" do
|
96
|
-
|
97
|
-
it "yields options for curl" do
|
98
|
-
worker.curl_opts { |c| c.interface = "eth1" }
|
99
|
-
|
100
|
-
worker.curl_opts[:interface].should eql "eth1"
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
describe "#get" do
|
108
|
-
|
109
|
-
it "returns a response" do
|
110
|
-
worker.get.class.ancestors.should include Response
|
111
|
-
end
|
112
|
-
|
113
|
-
it "raises an argument error if no key is provided" do
|
114
|
-
worker.key = nil
|
115
|
-
expect do
|
116
|
-
worker.get
|
117
|
-
end.to raise_error(/AWS access key missing/)
|
118
|
-
end
|
119
|
-
|
120
|
-
it "raises an argument error if no locale is provided" do
|
121
|
-
worker.locale = nil
|
122
|
-
expect do
|
123
|
-
worker.get
|
124
|
-
end.to raise_error(/Locale missing/)
|
125
|
-
end
|
126
|
-
|
127
|
-
it "sets options on curl" do
|
128
|
-
easy = mock
|
129
|
-
easy.should_receive(:interface=).once.with("eth1")
|
130
|
-
Curl::Easy.stub!(:perform).and_yield(easy)
|
131
|
-
Response.should_receive(:new).once
|
132
|
-
|
133
|
-
worker.curl_opts { |c| c.interface = 'eth1' }
|
134
|
-
worker.get
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
describe "#get_all" do
|
140
|
-
|
141
|
-
it "returns an array of responses" do
|
142
|
-
responses = worker.get_all
|
143
|
-
|
144
|
-
responses.should be_an_instance_of Array
|
145
|
-
responses.each { |resp| resp.should be_an_instance_of Response }
|
146
|
-
end
|
147
|
-
|
148
|
-
context "when given a block" do
|
149
|
-
|
150
|
-
it "yields responses" do
|
151
|
-
count = 0
|
152
|
-
worker.get_all do |resp|
|
153
|
-
resp.should be_an_instance_of Response
|
154
|
-
count += 1
|
155
|
-
end
|
156
|
-
|
157
|
-
count.should eql Request::HOSTS.size
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
describe "#key" do
|
165
|
-
|
166
|
-
it "returns the Amazon AWS access key for the current locale" do
|
167
|
-
worker.instance_variable_set(:@keys, { :us => 'foo' })
|
168
|
-
|
169
|
-
worker.key.should eql 'foo'
|
170
|
-
end
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
describe "#key=" do
|
175
|
-
|
176
|
-
it "sets a global Amazon AWS access key" do
|
177
|
-
worker.key = "foo"
|
178
|
-
keys = worker.instance_variable_get(:@keys)
|
179
|
-
|
180
|
-
keys.size.should eql Request::HOSTS.size
|
181
|
-
keys.values.uniq.should eql ["foo"]
|
182
|
-
end
|
183
|
-
|
184
|
-
end
|
185
|
-
|
186
|
-
describe "#keys=" do
|
187
|
-
|
188
|
-
it "sets distinct Amazon AWS access keys for the locales" do
|
189
|
-
keys = {
|
190
|
-
:us => 'foo',
|
191
|
-
:uk => 'bar',
|
192
|
-
:de => 'baz',
|
193
|
-
:ca => 'foo',
|
194
|
-
:fr => 'bar',
|
195
|
-
:jp => 'baz' }
|
196
|
-
worker.keys = keys
|
197
|
-
|
198
|
-
worker.instance_variable_get(:@keys).should eql keys
|
199
|
-
end
|
200
|
-
|
201
|
-
end
|
202
|
-
|
203
|
-
context "private methods" do
|
204
|
-
|
205
|
-
describe "#build_query" do
|
206
|
-
|
207
|
-
let(:query) { worker.send(:build_query) }
|
208
|
-
|
209
|
-
it "canonicalizes parameters" do
|
210
|
-
query.should match /Service=([^&]+)&Timestamp=([^&]+)&Version=([^&]+)/
|
211
|
-
end
|
212
|
-
|
213
|
-
it "includes the key for the current locale" do
|
214
|
-
worker.instance_variable_set(:@keys, { :us => 'foo' })
|
215
|
-
query.should include 'AWSAccessKeyId=foo'
|
216
|
-
end
|
217
|
-
|
218
|
-
it "includes a timestamp" do
|
219
|
-
query.should include 'Timestamp='
|
220
|
-
end
|
221
|
-
|
222
|
-
it "sorts parameters" do
|
223
|
-
worker.parameters["AAA"] = "foo"
|
224
|
-
query.should match /^AAA=foo/
|
225
|
-
end
|
226
|
-
|
227
|
-
it "converts a parameter whose value is an array to a string" do
|
228
|
-
worker.parameters["Foo"] = ["bar", "baz"]
|
229
|
-
query.should match /Foo=bar%2Cbaz/
|
230
|
-
end
|
231
|
-
|
232
|
-
it "handles integer parameter values" do
|
233
|
-
worker.parameters["Foo"] = 1
|
234
|
-
query.should match /Foo=1/
|
235
|
-
end
|
236
|
-
|
237
|
-
it "handles floating-point parameter values" do
|
238
|
-
worker.parameters["Foo"] = 1.0
|
239
|
-
query.should match /Foo=1/
|
240
|
-
end
|
241
|
-
|
242
|
-
end
|
243
|
-
|
244
|
-
describe "#host" do
|
245
|
-
|
246
|
-
it "returns a host" do
|
247
|
-
worker.locale = "fr"
|
248
|
-
worker.send(:host).should eql "ecs.amazonaws.fr"
|
249
|
-
end
|
250
|
-
|
251
|
-
end
|
252
|
-
|
253
|
-
describe "#build_signed_query" do
|
254
|
-
|
255
|
-
it "returns a signed query string" do
|
256
|
-
query = worker.send :build_signed_query
|
257
|
-
query.should match /&Signature=.*/
|
258
|
-
end
|
259
|
-
|
260
|
-
end
|
261
|
-
|
262
|
-
describe "#timestamp" do
|
263
|
-
|
264
|
-
it "returns a timestamp" do
|
265
|
-
worker.send(:timestamp)["Timestamp"].should match /^\d+-\d+-\d+T\d+:\d+:\d+Z$/
|
266
|
-
end
|
267
|
-
|
268
|
-
end
|
269
|
-
|
270
|
-
describe "#uri" do
|
271
|
-
|
272
|
-
it "returns the URI with which to query Amazon" do
|
273
|
-
worker.send(:uri).should be_an_instance_of URI::HTTP
|
274
|
-
end
|
275
|
-
|
276
|
-
end
|
277
|
-
|
278
|
-
end
|
279
|
-
|
280
|
-
end
|
281
|
-
|
282
|
-
end
|