fakettp 0.2.4.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/README.rdoc +201 -0
- data/Rakefile +113 -0
- data/VERSION +1 -0
- data/features/control.feature +12 -0
- data/features/dashboard.feature +38 -0
- data/features/expectations.feature +27 -0
- data/features/expectations/expect_get +3 -0
- data/features/expectations/get_foo +3 -0
- data/features/expectations/get_root +3 -0
- data/features/expectations/pass_and_fail +5 -0
- data/features/expectations/set_response +5 -0
- data/features/step_definitions/expectations.rb +5 -0
- data/features/step_definitions/http.rb +42 -0
- data/features/step_definitions/simulator.rb +25 -0
- data/features/step_definitions/webrat_steps.rb +99 -0
- data/features/support/env.rb +5 -0
- data/features/support/fakettp.rb +2 -0
- data/features/support/paths.rb +12 -0
- data/features/support/xpath.rb +10 -0
- data/features/verification.feature +49 -0
- data/lib/fakettp/commands/fakettp_command.rb +31 -19
- data/lib/fakettp/controller.rb +12 -0
- data/lib/fakettp/db.rb +7 -0
- data/lib/fakettp/error.rb +6 -18
- data/lib/fakettp/expectation.rb +28 -44
- data/lib/fakettp/fakettp.yml +3 -0
- data/lib/fakettp/public/fakettp.css +33 -0
- data/lib/fakettp/schema.rb +14 -0
- data/lib/fakettp/simulator.rb +16 -10
- data/lib/fakettp/views/index.erb +14 -0
- data/spec/fakettp/commands/fakettp_command_spec.rb +128 -0
- data/spec/fakettp/controller_spec.rb +250 -0
- data/spec/fakettp/error_spec.rb +40 -0
- data/spec/fakettp/expectation_helper_spec.rb +33 -0
- data/spec/fakettp/expectation_spec.rb +142 -0
- data/spec/fakettp/simulator_spec.rb +149 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +18 -0
- metadata +73 -24
- data/README.html +0 -113
data/.gitignore
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
= FakeTTP
|
2
|
+
|
3
|
+
== Purpose
|
4
|
+
|
5
|
+
When you are writing acceptance/integration tests for an application which
|
6
|
+
makes HTTP requests to a remote application, sometimes you need to be able
|
7
|
+
to test the interactions in different scenarios without talking to a real
|
8
|
+
instance of the remote application.
|
9
|
+
|
10
|
+
FakeTTP is a standalone web application that allows you to mock requests (ie
|
11
|
+
set and verify expectations on the requests your application makes, and
|
12
|
+
return suitable responses to those requests).
|
13
|
+
|
14
|
+
== Installation
|
15
|
+
|
16
|
+
=== Install the gem
|
17
|
+
|
18
|
+
Add GemCutter as a gem source (you only need to do this once):
|
19
|
+
|
20
|
+
gem sources -a http://gemcutter.org
|
21
|
+
|
22
|
+
Then install FakeTTP:
|
23
|
+
|
24
|
+
sudo gem install fakettp
|
25
|
+
|
26
|
+
Alternatively, you can specify the source when you install the gem:
|
27
|
+
|
28
|
+
sudo gem install fakettp --source http://gemcutter.org
|
29
|
+
|
30
|
+
=== Create a FakeTTP directory
|
31
|
+
|
32
|
+
You can install FakeTTP anywhere that your web server user can see it:
|
33
|
+
|
34
|
+
fakettp install <directory> <hostname>
|
35
|
+
|
36
|
+
Set _hostname_ to the host you want to use for FakeTTP control requests (the
|
37
|
+
examples below use <tt>fakettp.local</tt>). You can install multiple copies
|
38
|
+
and run them independently by using different directories and hostnames (for
|
39
|
+
example on a CI server to prevent clashes when building multiple projects
|
40
|
+
in parallel).
|
41
|
+
|
42
|
+
=== Point your web server at the directory
|
43
|
+
|
44
|
+
FakeTTP should work with any Rack-compatible server: just point the server to
|
45
|
+
the correct directory. For example, using Passenger[http://www.modrails.com/]
|
46
|
+
(mod_rails) with Apache, create a virtual host along these lines:
|
47
|
+
|
48
|
+
<VirtualHost *:80>
|
49
|
+
ServerName fakettp.local
|
50
|
+
DocumentRoot "/path/to/fakettp/public"
|
51
|
+
<directory "/path/to/fakettp/public">
|
52
|
+
Order deny,allow
|
53
|
+
Deny from all
|
54
|
+
Allow from 127.0.0.1
|
55
|
+
</directory>
|
56
|
+
</VirtualHost>
|
57
|
+
|
58
|
+
Then make sure <tt>fakettp.local</tt> resolves to 127.0.0.1 (assuming you're
|
59
|
+
running the simulator on the same machine as the application under test), eg
|
60
|
+
by adding the following line to <tt>/etc/hosts</tt>:
|
61
|
+
|
62
|
+
127.0.0.1 fakettp.local
|
63
|
+
|
64
|
+
=== IMPORTANT: security note
|
65
|
+
|
66
|
+
Because expectations are set by posting Ruby code to be executed on the
|
67
|
+
server, you probably don't want any old Tom, Dick or Harry to be able to
|
68
|
+
connect. The security settings in the virtual host config example above
|
69
|
+
restrict access to clients running on the local machine.
|
70
|
+
|
71
|
+
== Usage
|
72
|
+
|
73
|
+
The examples below assume you've installed using the hostname
|
74
|
+
<tt>fakettp.local</tt>. If you've used a different host, adjust appropriately.
|
75
|
+
|
76
|
+
=== Resetting
|
77
|
+
|
78
|
+
To reset FakeTTP (ie remove all expectations and errors), make an HTTP POST
|
79
|
+
request to <tt>http://fakettp.local/reset</tt>.
|
80
|
+
|
81
|
+
==== Calling from curl
|
82
|
+
|
83
|
+
curl fakettp.local/reset -X POST
|
84
|
+
|
85
|
+
=== Setting expectations
|
86
|
+
|
87
|
+
To create a new expectation, make an HTTP POST request to
|
88
|
+
<tt>http://fakettp.local/expect</tt>, with a <em>Content-Type</em> header of
|
89
|
+
'text/plain' and the request data containing a Ruby block to execute.
|
90
|
+
|
91
|
+
The supplied code should be in the following format, and will generally
|
92
|
+
consist of a number of assertions on the request, followed by creation of
|
93
|
+
the response to return to the application under test.
|
94
|
+
|
95
|
+
expect "GET of /foo" do
|
96
|
+
request.host.should == 'fakettp.local'
|
97
|
+
request.path_info.should == '/foo'
|
98
|
+
|
99
|
+
content_type 'text/plain'
|
100
|
+
"All is well\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
The label on the first line is used in error reporting.
|
104
|
+
|
105
|
+
The expectation code has access to the underlying Sinatra request and
|
106
|
+
response objects etc, as well as RSpec[http://rspec.info] matchers.
|
107
|
+
|
108
|
+
==== Calling from curl
|
109
|
+
|
110
|
+
Assuming the expectation is in <tt>expectation_file</tt>:
|
111
|
+
|
112
|
+
curl -X POST fakettp.local/expect -X POST -H 'Content-Type:text/plain' --binary-data <expectation_file>
|
113
|
+
|
114
|
+
=== Verifying
|
115
|
+
|
116
|
+
To verify that all expectations have been met, make an HTTP GET request
|
117
|
+
to <tt>http://fakettp.local/verify</tt>.
|
118
|
+
|
119
|
+
If all is well, the response will be a <em>200 OK</em> with a body of 'OK'. Otherwise
|
120
|
+
the status will be <em>400 Bad Request</em>, with a list of failures in the body. The
|
121
|
+
failure messages include the complete details of the unexpected request that
|
122
|
+
was received, to assist debugging.
|
123
|
+
|
124
|
+
==== Calling from curl
|
125
|
+
|
126
|
+
curl fakettp.local/verify
|
127
|
+
|
128
|
+
=== Web console
|
129
|
+
|
130
|
+
Point your browser at http://fakettp.local/
|
131
|
+
|
132
|
+
Currently this is very basic, just showing the expectations from the last run.
|
133
|
+
|
134
|
+
=== Multiple faked hosts
|
135
|
+
|
136
|
+
To have FakeTTP respond to multiple hostnames, create the appropriate hosts
|
137
|
+
entries. If you're using name-based virtual hosts in Apache, add a
|
138
|
+
_ServerAlias_ entry to the virtual host config, under the _ServerName_ line, eg:
|
139
|
+
|
140
|
+
ServerAlias foo.com bar.com
|
141
|
+
|
142
|
+
== Change log
|
143
|
+
|
144
|
+
0.3.0 (18 May 2009)
|
145
|
+
|
146
|
+
* Now uses SQLite and ActiveRecord to store expectations, instead of the filesystem.
|
147
|
+
* Supports specification of hostname on installation
|
148
|
+
* HTML page showing expectations from the last run
|
149
|
+
|
150
|
+
0.2.4.1 (5 May 2009)
|
151
|
+
|
152
|
+
* Temporarily depend on edge version of Sinatra, to gain Rack 1.0 compatibility.
|
153
|
+
|
154
|
+
0.2.4 (25 Mar 2009)
|
155
|
+
|
156
|
+
* Fixed a bug which caused expectations to be run in the wrong order if there
|
157
|
+
were more than nine of them.
|
158
|
+
|
159
|
+
0.2.3 (19 Mar 2009)
|
160
|
+
|
161
|
+
* Fixed a bug where expectations were being overwritten on Linux due to
|
162
|
+
Dir.entries not returning results in the expected order
|
163
|
+
|
164
|
+
0.2.2 (18 Mar 2009)
|
165
|
+
|
166
|
+
* Only accept control requests (reset, expect, verify) on fakettp.local
|
167
|
+
|
168
|
+
0.2.1 (17 Mar 2009)
|
169
|
+
|
170
|
+
* Fixed issue where rspec matchers weren't available to expectations
|
171
|
+
|
172
|
+
0.2 (14 Mar 2009)
|
173
|
+
|
174
|
+
* Complete rewrite, with tests and classes and stuff this time.
|
175
|
+
|
176
|
+
If you get an 'unexpected return' error, remove the return statement from your
|
177
|
+
expectation, and just put the return value on the last line of the _expect_ block.
|
178
|
+
|
179
|
+
0.1.2 (13 Feb 2009)
|
180
|
+
|
181
|
+
* Make sure README.html appears in generated gem
|
182
|
+
|
183
|
+
0.1.1 (13 Feb 2009)
|
184
|
+
|
185
|
+
* Fix permissions on installed tmp directory.
|
186
|
+
|
187
|
+
0.1.0 (13 Feb 2009)
|
188
|
+
|
189
|
+
* First release as a gem.
|
190
|
+
|
191
|
+
== To Do
|
192
|
+
|
193
|
+
* Add examples
|
194
|
+
* Make control requests RESTful?
|
195
|
+
* Show label in verification error for expected requests that weren't received
|
196
|
+
* Add facility to stub as well as mock requests
|
197
|
+
* Allow more flexibility in request ordering
|
198
|
+
* Allow user-specific helper files in installation dir
|
199
|
+
* Provide Ruby API to set expectations etc
|
200
|
+
* Return the expected content type even if expectation fails
|
201
|
+
* Highlight failed lines in console
|
data/Rakefile
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'spec/rake/verify_rcov'
|
5
|
+
|
6
|
+
desc 'run specs, create gem, install and test'
|
7
|
+
task :default => [:rcov, :verify_rcov, :gemspec, :build, :local_install, :test_install, :'cucumber:all', :ok]
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |gem|
|
12
|
+
gem.name = "fakettp"
|
13
|
+
gem.summary = %Q{HTTP server mocking tool}
|
14
|
+
gem.description = gem.summary
|
15
|
+
gem.email = "kerryjbuckley@gmail.com"
|
16
|
+
gem.homepage = "http://github.com/kerryb/fakettp"
|
17
|
+
gem.authors = ["Kerry Buckley"]
|
18
|
+
gem.add_dependency 'sinatra-sinatra'
|
19
|
+
gem.add_dependency 'sqlite3-ruby'
|
20
|
+
gem.add_development_dependency 'jeweler'
|
21
|
+
gem.add_development_dependency 'rcov'
|
22
|
+
gem.add_development_dependency "rspec"
|
23
|
+
gem.add_development_dependency "cucumber"
|
24
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
25
|
+
end
|
26
|
+
Jeweler::GemcutterTasks.new
|
27
|
+
rescue LoadError
|
28
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'spec/rake/spectask'
|
32
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
33
|
+
spec.libs << 'lib' << 'spec'
|
34
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
35
|
+
end
|
36
|
+
|
37
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
38
|
+
spec.libs << 'lib' << 'spec'
|
39
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
40
|
+
spec.rcov = true
|
41
|
+
spec.rcov_opts = ['--exclude', 'spec']
|
42
|
+
end
|
43
|
+
|
44
|
+
task :spec => :check_dependencies
|
45
|
+
|
46
|
+
desc 'Check spec coverage'
|
47
|
+
RCov::VerifyTask.new(:verify_rcov) do |t|
|
48
|
+
t.threshold = 100.0
|
49
|
+
t.index_html = 'coverage/index.html'
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
require 'cucumber/rake/task'
|
54
|
+
namespace :cucumber do
|
55
|
+
Cucumber::Rake::Task.new({:ok => :check_dependencies}, 'Run features that should pass') do |t|
|
56
|
+
t.cucumber_opts = "--color --tags ~@wip --strict --format 'pretty'"
|
57
|
+
end
|
58
|
+
|
59
|
+
Cucumber::Rake::Task.new({:wip => :check_dependencies}, 'Run features that are being worked on') do |t|
|
60
|
+
t.cucumber_opts = "--color --tags @wip:2 --wip --format 'pretty'"
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'Run all features'
|
64
|
+
task :all => [:ok, :wip]
|
65
|
+
end
|
66
|
+
desc 'Alias for cucumber:ok'
|
67
|
+
task :cucumber => 'cucumber:ok'
|
68
|
+
rescue LoadError
|
69
|
+
task :cucumber do
|
70
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
require 'rake/rdoctask'
|
75
|
+
Rake::RDocTask.new do |rdoc|
|
76
|
+
if File.exist?('VERSION')
|
77
|
+
version = File.read('VERSION')
|
78
|
+
else
|
79
|
+
version = ""
|
80
|
+
end
|
81
|
+
|
82
|
+
rdoc.rdoc_dir = 'rdoc'
|
83
|
+
rdoc.title = "fakettp2 #{version}"
|
84
|
+
rdoc.rdoc_files.include('README*')
|
85
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'remove all build products'
|
89
|
+
task :clean do
|
90
|
+
FileUtils.rm_rf %w(tmp install coverage pkg)
|
91
|
+
end
|
92
|
+
|
93
|
+
task :local_install do
|
94
|
+
system 'sudo gem in -l pkg/*'
|
95
|
+
end
|
96
|
+
|
97
|
+
desc 'Install FakeTTP into local install directory'
|
98
|
+
task :test_install do
|
99
|
+
rm_rf 'install'
|
100
|
+
system 'fakettp install install fakettp.local'
|
101
|
+
touch 'install/tmp/restart.txt'
|
102
|
+
end
|
103
|
+
|
104
|
+
task :ok do
|
105
|
+
red = "\e[31m"
|
106
|
+
yellow = "\e[33m"
|
107
|
+
green = "\e[32m"
|
108
|
+
blue = "\e[34m"
|
109
|
+
purple = "\e[35m"
|
110
|
+
bold = "\e[1m"
|
111
|
+
normal = "\e[0m"
|
112
|
+
puts "", "#{bold}#{red}*#{yellow}*#{green}*#{blue}*#{purple}* #{green} ALL TESTS PASSED #{purple}*#{blue}*#{green}*#{yellow}*#{red}*#{normal}"
|
113
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Controlling the simulator
|
2
|
+
Scenario: Attempting to reset the simulator using the wrong host
|
3
|
+
When we post to /reset on foo.fake.local
|
4
|
+
Then the response body should contain 'Simulator received mismatched request'
|
5
|
+
|
6
|
+
Scenario: Attempting to create an expectation using the wrong host
|
7
|
+
When we post to /expect on foo.fake.local
|
8
|
+
Then the response body should contain 'Simulator received mismatched request'
|
9
|
+
|
10
|
+
Scenario: Attempting to verify the simulator using the wrong host
|
11
|
+
When we get /verify on foo.fake.local
|
12
|
+
Then the response body should contain 'Simulator received mismatched request'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Feature: Dashboard for debugging failures
|
2
|
+
Scenario: Dashboard web page served from /
|
3
|
+
When we get / on fakettp.local
|
4
|
+
Then the response should have a content type of 'text/html'
|
5
|
+
And /html/head/title in the response should be 'FakeTTP'
|
6
|
+
|
7
|
+
Scenario: Expectations are listed
|
8
|
+
Given the simulator is reset
|
9
|
+
And there are 3 expectations
|
10
|
+
When we get / on fakettp.local
|
11
|
+
Then there should be 3 /html/body/div elements in the response
|
12
|
+
|
13
|
+
Scenario: Show expectation heading and contents
|
14
|
+
Given the simulator is reset
|
15
|
+
And we expect get_root
|
16
|
+
When we get / on fakettp.local
|
17
|
+
Then //h1[1] in the response should be '1'
|
18
|
+
And //div[1]/pre in the response should be:
|
19
|
+
"""
|
20
|
+
expect "GET /" do
|
21
|
+
request.path_info.should == '/'
|
22
|
+
end
|
23
|
+
"""
|
24
|
+
|
25
|
+
@wip
|
26
|
+
Scenario: Highlight passed and failed lines
|
27
|
+
Given the simulator is reset
|
28
|
+
And we expect pass_and_fail
|
29
|
+
And we get / on foo.fake.local
|
30
|
+
When we get / on fakettp.local
|
31
|
+
Then //div[1]/pre in the response should be:
|
32
|
+
"""
|
33
|
+
<span class="pass">expect "pass and fail" do
|
34
|
+
(2 + 2).should == 4</span>
|
35
|
+
<span class="fail"> true.should be_false</span>
|
36
|
+
'will never get here'
|
37
|
+
end
|
38
|
+
"""
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: Expectations
|
2
|
+
Scenario: Using rspec matchers
|
3
|
+
Given the simulator is reset
|
4
|
+
And we expect expect_get
|
5
|
+
And we get / on foo.fake.local
|
6
|
+
Then verifying the simulator should report success
|
7
|
+
|
8
|
+
Scenario: Setting response headers
|
9
|
+
Given the simulator is reset
|
10
|
+
And we expect set_response
|
11
|
+
And we get / on foo.fake.local
|
12
|
+
Then the response should have a 'foo' header with a value of 'bar'
|
13
|
+
And verifying the simulator should report success
|
14
|
+
|
15
|
+
Scenario: Setting response content-type
|
16
|
+
Given the simulator is reset
|
17
|
+
And we expect set_response
|
18
|
+
And we get / on foo.fake.local
|
19
|
+
Then the response should have a content type of 'application/xml'
|
20
|
+
And verifying the simulator should report success
|
21
|
+
|
22
|
+
Scenario: Setting response code
|
23
|
+
Given the simulator is reset
|
24
|
+
And we expect set_response
|
25
|
+
And we get / on foo.fake.local
|
26
|
+
Then the response body should be '<foo />'
|
27
|
+
And verifying the simulator should report success
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
|
3
|
+
When /^we get (\S*) on (\S*)$/ do |path, host|
|
4
|
+
req = Net::HTTP::Get.new path
|
5
|
+
@@response = Net::HTTP.new(host).start {|http| http.request(req) }
|
6
|
+
end
|
7
|
+
When /^we post to (\S*) on (\S*)$/ do |path, host|
|
8
|
+
req = Net::HTTP::Post.new path
|
9
|
+
@@response = Net::HTTP.new(host).start {|http| http.request(req) }
|
10
|
+
end
|
11
|
+
|
12
|
+
Then /^the response should have a '(.*)' header with a value of '(.*)'$/ do |name, value|
|
13
|
+
@@response[name].should == value
|
14
|
+
end
|
15
|
+
|
16
|
+
Then /^the response should have a content type of '(.*)'$/ do |value|
|
17
|
+
@@response.content_type.should == value
|
18
|
+
end
|
19
|
+
|
20
|
+
Then /^the response body should be '(.*)'$/ do |value|
|
21
|
+
@@response.body.should == value
|
22
|
+
end
|
23
|
+
|
24
|
+
Then /^the response body should contain '(.*)'$/ do |value|
|
25
|
+
@@response.body.should include(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
Then /^(\S*) in the response should be '(.*)'$/ do |locator, value|
|
29
|
+
check_value locator, value
|
30
|
+
end
|
31
|
+
|
32
|
+
Then /^(\S*) in the response should be:$/ do |locator, value|
|
33
|
+
check_value locator, value
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_value locator, value
|
37
|
+
(Hpricot(@@response.body)/locator).inner_html.should == value
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^there should be (\d*) (.*) elements in the response$/ do |count, locator|
|
41
|
+
Hpricot(@@response.body).search(locator).size.should == count.to_i
|
42
|
+
end
|