sucker 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +25 -4
- data/lib/sucker/request.rb +4 -5
- data/lib/sucker/stub.rb +37 -0
- data/spec/integration/item_lookup_spec.rb +2 -0
- data/spec/integration/japan_spec.rb +2 -0
- data/spec/integration/seller_listing_search_spec.rb +3 -0
- data/spec/integration/twenty_items_in_one_request_spec.rb +2 -0
- data/spec/support/sucker.rb +3 -0
- data/spec/unit/sucker/request_spec.rb +18 -26
- data/spec/unit/sucker/response_spec.rb +1 -1
- data/spec/unit/sucker/stub_spec.rb +44 -0
- metadata +9 -6
- data/spec/support/curb_stubber.rb +0 -7
data/README.md
CHANGED
@@ -31,14 +31,35 @@ Hit Amazon and do something with the response.
|
|
31
31
|
response = worker.get
|
32
32
|
p response.code
|
33
33
|
p response.time
|
34
|
-
p response.
|
34
|
+
p response.body
|
35
|
+
|
36
|
+
response.to_h["ItemLookupResponse"]["Items"]["Item"].each { ... }
|
35
37
|
|
36
38
|
Hit Amazon again.
|
37
39
|
|
38
40
|
worker << {
|
39
|
-
"ItemId" =>
|
41
|
+
"ItemId" => 10.more.asins }
|
40
42
|
response = worker.get
|
41
43
|
|
42
|
-
For
|
44
|
+
For more examples, check the integration specs.
|
43
45
|
|
44
|
-
|
46
|
+
Testing
|
47
|
+
-------
|
48
|
+
|
49
|
+
To fake web requests, I do the following:
|
50
|
+
|
51
|
+
In a file such as `spec/support/sucker.rb`, I prep things:
|
52
|
+
|
53
|
+
require "sucker/stub"
|
54
|
+
Sucker.fixtures_path = File.dirname(__FILE__) + "/../fixtures"
|
55
|
+
|
56
|
+
Then, in the spec, I set up a worker and stub the worker:
|
57
|
+
|
58
|
+
Sucker.stub(@worker)
|
59
|
+
|
60
|
+
The first time you run the spec, the worker will perform the actual web request and cache the response. Subsequent requests are then mocked with the cached response.
|
61
|
+
|
62
|
+
Specs
|
63
|
+
-----
|
64
|
+
|
65
|
+
The unit specs should run out of the box after you `bundle install`, but the integration specs require you to create [an amazon.yml file with valid credentials](http://github.com/papercavalier/sucker/blob/master/spec/support/amazon.yml.example) in the spec/support folder.
|
data/lib/sucker/request.rb
CHANGED
@@ -58,9 +58,10 @@ module Sucker
|
|
58
58
|
|
59
59
|
private
|
60
60
|
|
61
|
-
#
|
61
|
+
# Timestamps parameters and concatenates them into a query string
|
62
62
|
def build_query
|
63
63
|
parameters.
|
64
|
+
merge(timestamp).
|
64
65
|
sort.
|
65
66
|
collect do |k, v|
|
66
67
|
"#{CGI.escape(k)}=" + CGI.escape(v.is_a?(Array) ? v.join(",") : v)
|
@@ -74,8 +75,6 @@ module Sucker
|
|
74
75
|
|
75
76
|
# Returns a signed and timestamped query string
|
76
77
|
def build_signed_query
|
77
|
-
timestamp_parameters
|
78
|
-
|
79
78
|
query = build_query
|
80
79
|
|
81
80
|
digest = OpenSSL::Digest::Digest.new("sha256")
|
@@ -92,8 +91,8 @@ module Sucker
|
|
92
91
|
:query => build_signed_query)
|
93
92
|
end
|
94
93
|
|
95
|
-
def
|
96
|
-
|
94
|
+
def timestamp
|
95
|
+
{ "Timestamp" => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ') }
|
97
96
|
end
|
98
97
|
end
|
99
98
|
end
|
data/lib/sucker/stub.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sucker
|
2
|
+
class MockResponse < Response
|
3
|
+
def initialize(mock_response_body)
|
4
|
+
self.body = mock_response_body
|
5
|
+
self.code = 200
|
6
|
+
self.time = 0.1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :fixtures_path
|
12
|
+
|
13
|
+
# Records a request on first run and fakes subsequently
|
14
|
+
def stub(request)
|
15
|
+
request.instance_eval do
|
16
|
+
self.class.send :define_method, :fixture do
|
17
|
+
filename = parameters.values.flatten.sort.join.gsub(/[^\w\-\/]+/, '_')[0, 251]
|
18
|
+
"#{Sucker.fixtures_path}/#{filename}.xml"
|
19
|
+
end
|
20
|
+
|
21
|
+
self.class.send :define_method, :get do
|
22
|
+
if File.exists?(fixture)
|
23
|
+
MockResponse.new(File.new(fixture, "r").read)
|
24
|
+
else
|
25
|
+
curl.url = uri.to_s
|
26
|
+
curl.perform
|
27
|
+
response = Response.new(curl)
|
28
|
+
|
29
|
+
File.open(fixture, "w") { |f| f.write response.body }
|
30
|
+
|
31
|
+
response
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -3,7 +3,10 @@ require "spec_helper"
|
|
3
3
|
module Sucker
|
4
4
|
describe Request do
|
5
5
|
before do
|
6
|
-
@worker = Sucker.new
|
6
|
+
@worker = Sucker.new(
|
7
|
+
:locale => "us",
|
8
|
+
:key => "key",
|
9
|
+
:secret => "secret")
|
7
10
|
end
|
8
11
|
|
9
12
|
context ".new" do
|
@@ -11,7 +14,7 @@ module Sucker
|
|
11
14
|
default_parameters = {
|
12
15
|
"Service" => "AWSECommerceService",
|
13
16
|
"Version" => Sucker::AMAZON_API_VERSION }
|
14
|
-
@worker.parameters.should
|
17
|
+
@worker.parameters.should include default_parameters
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -40,23 +43,18 @@ module Sucker
|
|
40
43
|
|
41
44
|
context "#get" do
|
42
45
|
before do
|
43
|
-
@worker
|
44
|
-
@worker.secret = "secret"
|
45
|
-
|
46
|
-
curl = @worker.curl
|
47
|
-
curl.stub(:get).and_return(nil)
|
48
|
-
curl.stub!(:body_str).and_return(fixture("single_item_lookup.us"))
|
46
|
+
Sucker.stub(@worker)
|
49
47
|
end
|
50
48
|
|
51
49
|
it "returns a Response object" do
|
52
|
-
@worker.get.should
|
50
|
+
@worker.get.class.ancestors.should include Response
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
56
54
|
context "#key=" do
|
57
55
|
it "sets the Amazon AWS access key in the parameters" do
|
58
|
-
@worker.key = "
|
59
|
-
@worker.parameters["AWSAccessKeyId"].should eql "
|
56
|
+
@worker.key = "foo"
|
57
|
+
@worker.parameters["AWSAccessKeyId"].should eql "foo"
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
@@ -64,50 +62,44 @@ module Sucker
|
|
64
62
|
context "#build_query" do
|
65
63
|
it "canonicalizes parameters" do
|
66
64
|
query = @worker.send(:build_query)
|
67
|
-
query.should
|
65
|
+
query.should match /Service=([^&]+)&Timestamp=([^&]+)&Version=([^&]+)/
|
68
66
|
end
|
69
67
|
|
70
68
|
it "sorts parameters" do
|
71
|
-
@worker.parameters["
|
69
|
+
@worker.parameters["AAA"] = "foo"
|
72
70
|
query = @worker.send(:build_query)
|
73
|
-
query.should match /^
|
71
|
+
query.should match /^AAA=foo/
|
74
72
|
end
|
75
73
|
|
76
74
|
it "converts a parameter whose value is an array to a string" do
|
77
75
|
@worker.parameters["Foo"] = ["bar", "baz"]
|
78
76
|
query = @worker.send(:build_query)
|
79
|
-
query.should match
|
77
|
+
query.should match /Foo=bar%2Cbaz/
|
80
78
|
end
|
81
79
|
end
|
82
80
|
|
83
81
|
context "#host" do
|
84
82
|
it "returns a host" do
|
85
|
-
@worker.locale = "
|
86
|
-
@worker.send(:host).should eql "ecs.amazonaws.
|
83
|
+
@worker.locale = "fr"
|
84
|
+
@worker.send(:host).should eql "ecs.amazonaws.fr"
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
90
88
|
context "#build_signed_query" do
|
91
89
|
it "returns a signed query string" do
|
92
|
-
@worker.secret = "secret"
|
93
|
-
@worker.locale = "us"
|
94
90
|
query = @worker.send :build_signed_query
|
95
91
|
query.should match /&Signature=.*/
|
96
92
|
end
|
97
93
|
end
|
98
94
|
|
99
|
-
context "#
|
100
|
-
it "
|
101
|
-
@worker.send
|
102
|
-
@worker.parameters["Timestamp"].should match /^\d+-\d+-\d+T\d+:\d+:\d+Z$/
|
95
|
+
context "#timestamp" do
|
96
|
+
it "returns a timestamp" do
|
97
|
+
@worker.send(:timestamp)["Timestamp"].should match /^\d+-\d+-\d+T\d+:\d+:\d+Z$/
|
103
98
|
end
|
104
99
|
end
|
105
100
|
|
106
101
|
context "#uri" do
|
107
102
|
it "returns the URI with which to query Amazon" do
|
108
|
-
@worker.key = "key"
|
109
|
-
@worker.locale = "us"
|
110
|
-
@worker.secret = "secret"
|
111
103
|
@worker.send(:uri).should be_an_instance_of URI::HTTP
|
112
104
|
end
|
113
105
|
end
|
@@ -5,7 +5,7 @@ module Sucker
|
|
5
5
|
before do
|
6
6
|
curl = Sucker.new.curl
|
7
7
|
curl.stub(:get).and_return(nil)
|
8
|
-
curl.stub!(:body_str).and_return(
|
8
|
+
curl.stub!(:body_str).and_return("foo")
|
9
9
|
curl.stub!(:response_code).and_return(200)
|
10
10
|
curl.stub!(:total_time).and_return(1.0)
|
11
11
|
@response = Response.new(curl)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module Sucker
|
5
|
+
describe "Stub" do
|
6
|
+
before do
|
7
|
+
@worker = Sucker.new(
|
8
|
+
:locale => "us",
|
9
|
+
:secret => "secret")
|
10
|
+
end
|
11
|
+
|
12
|
+
context ".stub" do
|
13
|
+
before do
|
14
|
+
Sucker.stub(@worker)
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
FileUtils.rm @worker.fixture, :force => true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "defines Request#fixture" do
|
22
|
+
@worker.should respond_to :fixture
|
23
|
+
@worker.fixture.should include Sucker.fixtures_path
|
24
|
+
end
|
25
|
+
|
26
|
+
it "performs the request if fixture is not available" do
|
27
|
+
curl = @worker.curl
|
28
|
+
curl.stub(:get).and_return(nil)
|
29
|
+
curl.stub!(:body_str).and_return("foo")
|
30
|
+
|
31
|
+
@worker.get
|
32
|
+
|
33
|
+
File.exists?(@worker.fixture).should be_true
|
34
|
+
File.new(@worker.fixture, "r").read.should eql "foo"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "mocks the request if fixture is available" do
|
38
|
+
File.open(@worker.fixture, "w") { |f| f.write "bar" }
|
39
|
+
|
40
|
+
@worker.get.body.should eql "bar"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sucker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Hakan Ensari
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-07-
|
19
|
+
date: 2010-07-30 00:00:00 +01:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- lib/sucker.rb
|
67
67
|
- lib/sucker/request.rb
|
68
68
|
- lib/sucker/response.rb
|
69
|
+
- lib/sucker/stub.rb
|
69
70
|
- README.md
|
70
71
|
- spec/integration/item_lookup_spec.rb
|
71
72
|
- spec/integration/japan_spec.rb
|
@@ -73,9 +74,10 @@ files:
|
|
73
74
|
- spec/integration/twenty_items_in_one_request_spec.rb
|
74
75
|
- spec/spec_helper.rb
|
75
76
|
- spec/support/amazon_credentials.rb
|
76
|
-
- spec/support/
|
77
|
+
- spec/support/sucker.rb
|
77
78
|
- spec/unit/sucker/request_spec.rb
|
78
79
|
- spec/unit/sucker/response_spec.rb
|
80
|
+
- spec/unit/sucker/stub_spec.rb
|
79
81
|
- spec/unit/sucker_spec.rb
|
80
82
|
has_rdoc: true
|
81
83
|
homepage: http://github.com/papercavalier/sucker
|
@@ -118,7 +120,8 @@ test_files:
|
|
118
120
|
- spec/integration/twenty_items_in_one_request_spec.rb
|
119
121
|
- spec/spec_helper.rb
|
120
122
|
- spec/support/amazon_credentials.rb
|
121
|
-
- spec/support/
|
123
|
+
- spec/support/sucker.rb
|
122
124
|
- spec/unit/sucker/request_spec.rb
|
123
125
|
- spec/unit/sucker/response_spec.rb
|
126
|
+
- spec/unit/sucker/stub_spec.rb
|
124
127
|
- spec/unit/sucker_spec.rb
|