ephemeral_response 0.1.0 → 0.2.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/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
|