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 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