sucker 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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.to_h["ItemLookupResponse"]["Items"]["Item"]
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" => ["0393329259", "0393317757"] }
41
+ "ItemId" => 10.more.asins }
40
42
  response = worker.get
41
43
 
42
- For some more examples, check the integration specs.
44
+ For more examples, check the integration specs.
43
45
 
44
- The unit specs should run out of the box, 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. Of course, bundle install first.
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.
@@ -58,9 +58,10 @@ module Sucker
58
58
 
59
59
  private
60
60
 
61
- # Escapes parameters and concatenates them into a query string
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 timestamp_parameters
96
- self.parameters["Timestamp"] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
94
+ def timestamp
95
+ { "Timestamp" => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ') }
97
96
  end
98
97
  end
99
98
  end
@@ -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
@@ -16,6 +16,8 @@ module Sucker
16
16
  "Condition" => "All",
17
17
  "MerchantId" => "All",
18
18
  "ResponseGroup" => ["ItemAttributes", "OfferFull"] }
19
+
20
+ Sucker.stub(@worker)
19
21
  end
20
22
 
21
23
  context "single item" do
@@ -11,6 +11,8 @@ module Sucker
11
11
  @worker << {
12
12
  "Operation" => "ItemLookup",
13
13
  "IdType" => "ASIN" }
14
+
15
+ Sucker.stub(@worker)
14
16
  end
15
17
 
16
18
  context "single item" do
@@ -13,6 +13,9 @@ module Sucker
13
13
  @worker << {
14
14
  "Operation" => "SellerListingSearch",
15
15
  "SellerId" => "A31N271NVIORU3" }
16
+
17
+ Sucker.stub(@worker)
18
+
16
19
  @listings = @worker.get.to_h["SellerListingSearchResponse"]["SellerListings"]
17
20
  end
18
21
 
@@ -26,6 +26,8 @@ module Sucker
26
26
  "ItemLookup.1.ItemId" => @asins[0, 10],
27
27
  "ItemLookup.2.ItemId" => @asins[10, 10] }
28
28
 
29
+ Sucker.stub(@worker)
30
+
29
31
  @items = @worker.get.to_h["ItemLookupResponse"]["Items"].map { |items| items["Item"] }.flatten!
30
32
  end
31
33
 
@@ -0,0 +1,3 @@
1
+ require "sucker/stub"
2
+
3
+ Sucker.fixtures_path = File.dirname(__FILE__) + "/../fixtures"
@@ -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 eql default_parameters
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.locale = "us"
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 be_an_instance_of Response
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 = "key"
59
- @worker.parameters["AWSAccessKeyId"].should eql "key"
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 eql "Service=AWSECommerceService&Version=#{Sucker::AMAZON_API_VERSION}"
65
+ query.should match /Service=([^&]+)&Timestamp=([^&]+)&Version=([^&]+)/
68
66
  end
69
67
 
70
68
  it "sorts parameters" do
71
- @worker.parameters["Foo"] = "bar"
69
+ @worker.parameters["AAA"] = "foo"
72
70
  query = @worker.send(:build_query)
73
- query.should match /^Foo=bar/
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 /^Foo=bar%2Cbaz/
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 = "us"
86
- @worker.send(:host).should eql "ecs.amazonaws.com"
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 "#timestamp_parameters" do
100
- it "upserts a timestamp to the parameters" do
101
- @worker.send :timestamp_parameters
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(fixture("multiple_item_lookup.us"))
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: 19
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.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-29 00:00:00 +01:00
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/curb_stubber.rb
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/curb_stubber.rb
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
@@ -1,7 +0,0 @@
1
- def fixture(filename)
2
- File.new(File.dirname(__FILE__) + "/../fixtures/#{filename}.xml", "r").read
3
- end
4
-
5
- def record(filename, output)
6
- File.open(File.dirname(__FILE__) + "/../fixtures/#{filename}.xml", "w") { |f| f.write output }
7
- end