ephemeral_response 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.markdown +18 -0
- data/README.markdown +53 -16
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/examples/simple_benchmark.rb +19 -0
- data/examples/white_list.rb +29 -0
- data/lib/ephemeral_response/configuration.rb +14 -0
- data/lib/ephemeral_response/fixture.rb +53 -22
- data/lib/ephemeral_response/net_http.rb +13 -3
- data/lib/ephemeral_response.rb +2 -1
- data/spec/ephemeral_response/configuration_spec.rb +48 -0
- data/spec/ephemeral_response/fixture_spec.rb +221 -21
- data/spec/ephemeral_response/net_http_spec.rb +15 -0
- data/spec/ephemeral_response_spec.rb +7 -1
- data/spec/integration/{net_spec.rb → normal_flow_spec.rb} +23 -11
- data/spec/integration/unique_fixtures_spec.rb +153 -0
- data/spec/integration/white_list_spec.rb +25 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/clear_fixtures.rb +1 -0
- data/spec/support/rack_reflector.rb +63 -0
- data/spec/support/time.rb +3 -2
- metadata +34 -10
- data/README.html +0 -72
- /data/{LICENSE → MIT_LICENSE} +0 -0
data/History.markdown
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
History
|
2
|
+
=======
|
3
|
+
|
4
|
+
0.2.0 / master
|
5
|
+
--------------
|
6
|
+
|
7
|
+
#### Enhancements
|
8
|
+
|
9
|
+
* Fixtures now have use .yml extension instead of .fixture.
|
10
|
+
* Varying POST data and query strings create new fixtures. Previously, GET /
|
11
|
+
and GET /?foo=bar resulted in the same fixture.
|
12
|
+
* Ability to reset configuration with EphemeralResponse::Configuration.reset
|
13
|
+
* Ability to white list certain hosts. Responses will not be saved for Requests
|
14
|
+
made to hosts in the white list.
|
15
|
+
Use EphemeralResponse::Configuration.white_list = "localhost"
|
16
|
+
* Ephemeral response prints to the Net/HTTP debugger when establishing a
|
17
|
+
connection. Just set http.set_debug_output = $stdout to see when Ephemeral
|
18
|
+
Response connects to a host.
|
data/README.markdown
CHANGED
@@ -3,50 +3,87 @@ Ephemeral Response
|
|
3
3
|
|
4
4
|
_Save HTTP responses to give your tests a hint of reality._
|
5
5
|
|
6
|
-
This is pretty much NetRecorder without the fakeweb dependency.
|
7
|
-
|
8
6
|
## Premise
|
9
7
|
|
10
|
-
|
8
|
+
Web responses are volatile. Servers go down, API's change, responses change and
|
9
|
+
everytime something changes, your tests should fail. Mocking out web responses
|
10
|
+
may speed up your test suite but the tests essentially become lies. Ephemeral
|
11
|
+
Response encourages you to run your tests against real web services while
|
12
|
+
keeping your test suite snappy by caching the responses and reusing them until
|
13
|
+
they expire.
|
14
|
+
|
15
|
+
1. run test suite
|
11
16
|
2. all responses are saved to fixtures
|
12
|
-
3.
|
13
|
-
4.
|
17
|
+
3. disconnect from the network
|
18
|
+
4. run test suite
|
19
|
+
|
20
|
+
## Example
|
21
|
+
|
22
|
+
require 'benchmark'
|
23
|
+
require 'ephemeral_response'
|
14
24
|
|
15
|
-
|
25
|
+
EphemeralResponse.activate
|
16
26
|
|
17
|
-
|
27
|
+
5.times do
|
28
|
+
puts Benchmark.realtime { Net::HTTP.get "example.com", "/" }
|
29
|
+
end
|
18
30
|
|
19
|
-
|
31
|
+
1.44242906570435 # First request caches the response as a fixture
|
32
|
+
0.000689029693603516
|
33
|
+
0.000646829605102539
|
34
|
+
0.00064396858215332
|
35
|
+
0.000645875930786133
|
20
36
|
|
21
|
-
|
37
|
+
## With Rspec
|
22
38
|
|
23
39
|
require 'ephemeral_response'
|
24
40
|
|
25
41
|
Spec::Runner.configure do |config|
|
42
|
+
|
26
43
|
config.before(:suite) do
|
27
44
|
EphemeralResponse.activate
|
28
45
|
end
|
46
|
+
|
29
47
|
config.after(:suite) do
|
30
48
|
EphemeralResponse.deactivate
|
31
49
|
end
|
50
|
+
|
32
51
|
end
|
33
52
|
|
53
|
+
All responses are cached in yaml files within spec/fixtures/ephemeral\_response.
|
54
|
+
|
55
|
+
I'd recommend git ignoring this directory to ensure your tests always hit the
|
56
|
+
remote service at least once and to prevent credentials (like API keys) from
|
57
|
+
being stored in your repo.
|
58
|
+
|
34
59
|
### Configuration
|
35
|
-
|
60
|
+
|
61
|
+
Change the fixture directory; defaults to "spec/fixtures/ephemeral\_response"
|
36
62
|
|
37
63
|
EphemeralResponse::Configuration.fixture_directory = "test/fixtures/ephemeral_response"
|
38
64
|
|
39
|
-
|
65
|
+
Change the elapsed time for when a fixture will expire; defaults to 24 hours
|
40
66
|
|
41
67
|
EphemeralResponse::Configuration.expiration = 86400 # 24 hours in seconds
|
42
68
|
|
43
|
-
|
44
|
-
|
69
|
+
Pass a block when setting expiration to gain access to the awesome helper
|
70
|
+
method `one_day`
|
45
71
|
|
46
|
-
EphemeralResponse::Configuration.expiration do
|
47
|
-
one_day * 30 # 60 * 60 * 24 * 30
|
72
|
+
EphemeralResponse::Configuration.expiration = lambda do
|
73
|
+
one_day * 30 # Expire in thirty days: 60 * 60 * 24 * 30
|
48
74
|
end
|
49
75
|
|
76
|
+
Always allow requests to be made to a host by adding it to the white list.
|
77
|
+
Helpful when running ephemeral response with selenium which makes requests to
|
78
|
+
the local server.
|
79
|
+
|
80
|
+
EphemeralResponse::Configuration.white_list = "localhost", "smackaho.st"
|
81
|
+
|
82
|
+
## Similar Projects
|
83
|
+
* [Net Recorder](http://github.com/chrisyoung/netrecorder)
|
84
|
+
* [Stalefish](http://github.com/jsmestad/stale_fish)
|
85
|
+
* [VCR](http://github.com/myronmarston/vcr)
|
86
|
+
|
50
87
|
## Note on Patches/Pull Requests
|
51
88
|
|
52
89
|
* Fork the project.
|
@@ -59,4 +96,4 @@ giving you access to the awesome helper method `one_day`
|
|
59
96
|
|
60
97
|
## Copyright
|
61
98
|
|
62
|
-
Copyright (c) 2010 Sandro Turriate. See
|
99
|
+
Copyright (c) 2010 Sandro Turriate. See MIT_LICENSE for details.
|
data/Rakefile
CHANGED
@@ -14,8 +14,9 @@ begin
|
|
14
14
|
gem.homepage = "http://github.com/sandro/ephemeral_response"
|
15
15
|
gem.authors = ["Sandro Turriate"]
|
16
16
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
17
|
-
gem.add_development_dependency "yard", ">= 0"
|
17
|
+
gem.add_development_dependency "yard", ">= 0.5.0"
|
18
18
|
gem.add_development_dependency "fakefs", ">= 0.2.1"
|
19
|
+
gem.add_development_dependency "unicorn", ">= 1.0.0"
|
19
20
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
21
|
end
|
21
22
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift("lib")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'lib/ephemeral_response'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
EphemeralResponse::Configuration.expiration = 5
|
7
|
+
EphemeralResponse.activate
|
8
|
+
|
9
|
+
# Run benchmarks against thefuckingweather.com
|
10
|
+
# The first request takes much longer than the rest
|
11
|
+
def benchmark_request(number=1)
|
12
|
+
uri = URI.parse('http://thefuckingweather.com/?RANDLOC=')
|
13
|
+
time = Benchmark.realtime do
|
14
|
+
Net::HTTP.get(uri)
|
15
|
+
end
|
16
|
+
puts "Request #{number} took #{time} secs"
|
17
|
+
end
|
18
|
+
|
19
|
+
5.times {|n| benchmark_request n + 1 }
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift("lib")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'lib/ephemeral_response'
|
4
|
+
|
5
|
+
# Don't create fixtures for the localhost domain
|
6
|
+
EphemeralResponse::Configuration.white_list = 'localhost'
|
7
|
+
|
8
|
+
EphemeralResponse::Configuration.expiration = 5
|
9
|
+
EphemeralResponse.activate
|
10
|
+
|
11
|
+
# Start an HTTP server on port 19876 using netcat
|
12
|
+
process = IO.popen %(echo "HTTP/1.1 200 OK\n\n" | nc -l 19876)
|
13
|
+
at_exit { Process.kill :KILL, process.pid }
|
14
|
+
sleep 1
|
15
|
+
|
16
|
+
# Make a request to the server started above
|
17
|
+
# No new fixtures are created in spec/fixtures/ephemeral_response/
|
18
|
+
uri = URI.parse('http://localhost:19876/')
|
19
|
+
Net::HTTP.get(uri)
|
20
|
+
|
21
|
+
# Fixtures are still created for Google
|
22
|
+
uri = URI.parse('http://www.google.com/')
|
23
|
+
Net::HTTP.get(uri)
|
24
|
+
|
25
|
+
puts "The following directory should only contain a fixture for google"
|
26
|
+
puts
|
27
|
+
listing_cmd = %(ls #{File.expand_path(EphemeralResponse::Configuration.fixture_directory)})
|
28
|
+
puts listing_cmd
|
29
|
+
puts %x(#{listing_cmd})
|
@@ -19,6 +19,20 @@ module EphemeralResponse
|
|
19
19
|
@expiration || one_day
|
20
20
|
end
|
21
21
|
|
22
|
+
def reset
|
23
|
+
@expiration = nil
|
24
|
+
@fixture_directory = nil
|
25
|
+
@white_list = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def white_list
|
29
|
+
@white_list ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def white_list=(*hosts)
|
33
|
+
@white_list = hosts.flatten
|
34
|
+
end
|
35
|
+
|
22
36
|
protected
|
23
37
|
|
24
38
|
def one_day
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module EphemeralResponse
|
2
2
|
class Fixture
|
3
3
|
attr_accessor :response
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :request, :uri, :created_at
|
5
5
|
|
6
6
|
def self.fixtures
|
7
7
|
@fixtures ||= {}
|
@@ -11,20 +11,27 @@ module EphemeralResponse
|
|
11
11
|
@fixtures = {}
|
12
12
|
end
|
13
13
|
|
14
|
-
def self.find(uri,
|
15
|
-
fixtures[Fixture.new(uri,
|
14
|
+
def self.find(uri, request)
|
15
|
+
fixtures[Fixture.new(uri, request).identifier]
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.load_all
|
19
19
|
clear
|
20
20
|
if File.directory?(Configuration.fixture_directory)
|
21
|
-
Dir.glob("#{Configuration.fixture_directory}/*.
|
21
|
+
Dir.glob("#{Configuration.fixture_directory}/*.yml", &method(:load_fixture))
|
22
22
|
end
|
23
23
|
fixtures
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.load_fixture(file_name)
|
27
|
-
|
27
|
+
register YAML.load_file(file_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_or_initialize(uri, request, &block)
|
31
|
+
find(uri, request) || new(uri, request, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.register(fixture)
|
28
35
|
if fixture.expired?
|
29
36
|
FileUtils.rm fixture.path
|
30
37
|
else
|
@@ -32,26 +39,22 @@ module EphemeralResponse
|
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
35
|
-
def self.respond_to(uri,
|
36
|
-
|
37
|
-
unless fixtures[fixture.identifier]
|
42
|
+
def self.respond_to(uri, request)
|
43
|
+
find_or_initialize(uri, request) do |fixture|
|
38
44
|
fixture.response = yield
|
39
|
-
fixture.
|
40
|
-
|
41
|
-
end
|
42
|
-
fixtures[fixture.identifier].response
|
45
|
+
fixture.register
|
46
|
+
end.response
|
43
47
|
end
|
44
48
|
|
45
|
-
def initialize(uri,
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@uri.normalize!
|
49
|
+
def initialize(uri, request)
|
50
|
+
@uri = uri.normalize
|
51
|
+
@request = deep_dup request
|
49
52
|
@created_at = Time.now
|
50
53
|
yield self if block_given?
|
51
54
|
end
|
52
55
|
|
53
56
|
def ==(other)
|
54
|
-
%w(
|
57
|
+
%w(request_identifier uri_identifier created_at response).all? do |attribute|
|
55
58
|
send(attribute) == other.send(attribute)
|
56
59
|
end
|
57
60
|
end
|
@@ -65,12 +68,19 @@ module EphemeralResponse
|
|
65
68
|
end
|
66
69
|
|
67
70
|
def identifier
|
68
|
-
Digest::SHA1.hexdigest(
|
71
|
+
Digest::SHA1.hexdigest("#{uri_identifier}#{request_identifier}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def method
|
75
|
+
request.method
|
69
76
|
end
|
70
77
|
|
71
78
|
def normalized_name
|
72
|
-
|
73
|
-
|
79
|
+
[uri.host, method, fs_path].compact.join("_").gsub(/[\/.]/, '-')
|
80
|
+
end
|
81
|
+
|
82
|
+
def fs_path
|
83
|
+
uri.path.dup.sub!(/^\/(.+)$/, '\1')
|
74
84
|
end
|
75
85
|
|
76
86
|
def path
|
@@ -78,7 +88,14 @@ module EphemeralResponse
|
|
78
88
|
end
|
79
89
|
|
80
90
|
def register
|
81
|
-
|
91
|
+
unless Configuration.white_list.include? uri.host
|
92
|
+
save
|
93
|
+
self.class.register self
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def request_identifier
|
98
|
+
request.to_yaml.split(//).sort
|
82
99
|
end
|
83
100
|
|
84
101
|
def save
|
@@ -88,10 +105,24 @@ module EphemeralResponse
|
|
88
105
|
end
|
89
106
|
end
|
90
107
|
|
108
|
+
def uri_identifier
|
109
|
+
if uri.query
|
110
|
+
parts = uri.to_s.split("?", 2)
|
111
|
+
parts[1] = parts[1].split('&').sort
|
112
|
+
parts
|
113
|
+
else
|
114
|
+
uri.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
91
118
|
protected
|
92
119
|
|
120
|
+
def deep_dup(object)
|
121
|
+
Marshal.load(Marshal.dump(object))
|
122
|
+
end
|
123
|
+
|
93
124
|
def generate_file_name
|
94
|
-
"#{normalized_name}_#{identifier}.
|
125
|
+
"#{normalized_name}_#{identifier[0..6]}.yml"
|
95
126
|
end
|
96
127
|
end
|
97
128
|
end
|
@@ -3,18 +3,28 @@ module Net
|
|
3
3
|
alias request_without_ephemeral_response request
|
4
4
|
alias connect_without_ephemeral_response connect
|
5
5
|
|
6
|
+
attr_reader :uri
|
7
|
+
|
6
8
|
def connect
|
7
9
|
end
|
8
10
|
private :connect
|
9
11
|
|
12
|
+
def do_start_without_ephemeral_response
|
13
|
+
D "EphemeralResponse: establishing connection to #{uri}"
|
14
|
+
connect_without_ephemeral_response
|
15
|
+
@started = true
|
16
|
+
end
|
17
|
+
private :do_start_without_ephemeral_response
|
18
|
+
|
10
19
|
def generate_uri(request)
|
11
20
|
scheme = use_ssl? ? "https" : "http"
|
12
|
-
URI.parse("#{scheme}://#{conn_address}:#{conn_port}#{request.path}")
|
21
|
+
@uri = URI.parse("#{scheme}://#{conn_address}:#{conn_port}#{request.path}")
|
13
22
|
end
|
14
23
|
|
15
24
|
def request(request, body = nil, &block)
|
16
|
-
|
17
|
-
|
25
|
+
generate_uri(request)
|
26
|
+
EphemeralResponse::Fixture.respond_to(uri, request) do
|
27
|
+
do_start_without_ephemeral_response
|
18
28
|
request_without_ephemeral_response(request, body, &block)
|
19
29
|
end
|
20
30
|
end
|
data/lib/ephemeral_response.rb
CHANGED
@@ -7,7 +7,7 @@ require 'ephemeral_response/configuration'
|
|
7
7
|
require 'ephemeral_response/fixture'
|
8
8
|
|
9
9
|
module EphemeralResponse
|
10
|
-
VERSION = "0.
|
10
|
+
VERSION = "0.2.0".freeze
|
11
11
|
|
12
12
|
def self.activate
|
13
13
|
deactivate
|
@@ -18,6 +18,7 @@ module EphemeralResponse
|
|
18
18
|
def self.deactivate
|
19
19
|
Net::HTTP.class_eval do
|
20
20
|
remove_method(:generate_uri) if method_defined?(:generate_uri)
|
21
|
+
remove_method(:uri) if method_defined?(:uri)
|
21
22
|
alias_method(:connect, :connect_without_ephemeral_response) if private_method_defined?(:connect_without_ephemeral_response)
|
22
23
|
alias_method(:request, :request_without_ephemeral_response) if method_defined?(:request_without_ephemeral_response)
|
23
24
|
end
|
@@ -2,6 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe EphemeralResponse::Configuration do
|
4
4
|
subject { EphemeralResponse::Configuration }
|
5
|
+
|
5
6
|
describe "#fixture_directory" do
|
6
7
|
it "has a default" do
|
7
8
|
subject.fixture_directory.should == "spec/fixtures/ephemeral_response"
|
@@ -36,4 +37,51 @@ describe EphemeralResponse::Configuration do
|
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
40
|
+
|
41
|
+
describe "#reset" do
|
42
|
+
it "resets expiration, fixture directory, and white list to the defaults" do
|
43
|
+
subject.fixture_directory = "test/fixtures/ephemeral_response"
|
44
|
+
subject.expiration = 1
|
45
|
+
subject.white_list = 'localhost'
|
46
|
+
subject.fixture_directory.should == "test/fixtures/ephemeral_response"
|
47
|
+
subject.expiration.should == 1
|
48
|
+
subject.white_list.should == ['localhost']
|
49
|
+
|
50
|
+
subject.reset
|
51
|
+
|
52
|
+
subject.fixture_directory.should == "spec/fixtures/ephemeral_response"
|
53
|
+
subject.expiration.should == 86400
|
54
|
+
subject.white_list.should == []
|
55
|
+
end
|
56
|
+
|
57
|
+
it "resets white list after the default has been modified" do
|
58
|
+
subject.white_list << "localhost"
|
59
|
+
subject.reset
|
60
|
+
subject.white_list.should be_empty
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#white_list" do
|
65
|
+
it "defaults to an empty array" do
|
66
|
+
subject.white_list.should == []
|
67
|
+
end
|
68
|
+
|
69
|
+
it "allows hosts to be pushed onto the white list" do
|
70
|
+
subject.white_list << 'localhost'
|
71
|
+
subject.white_list << 'smackaho.st'
|
72
|
+
subject.white_list.should == %w(localhost smackaho.st)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#white_list=" do
|
77
|
+
it "sets a single host" do
|
78
|
+
subject.white_list = 'localhost'
|
79
|
+
subject.white_list.should == ['localhost']
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets multiple hosts" do
|
83
|
+
subject.white_list = 'localhost', 'smackaho.st'
|
84
|
+
subject.white_list.should == ['localhost', 'smackaho.st']
|
85
|
+
end
|
86
|
+
end
|
39
87
|
end
|