harper 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.log
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_install_on_use_flag=1
2
+ rvm --create use ruby-1.8.7-p330@harper
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ # source :gemcutter
6
+
7
+ # gem "rake", "0.8.7"
8
+ # gem "sinatra"
9
+ # gem "httparty"
10
+ # gem "json", "~> 1.4.6"
11
+
12
+ # group :test do
13
+ # gem "rack-test"
14
+ # gem "sham_rack"
15
+ # gem "rspec"
16
+ # gem "machinist"
17
+ # gem "faker"
18
+ # gem "cucumber", ">= 0.10"
19
+ # gem "cucumber-sinatra"
20
+ # end
data/Gemfile.lock ADDED
@@ -0,0 +1,62 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ harper (0.0.1)
5
+ httparty
6
+ json (>= 1.4.6)
7
+ sinatra (>= 1.0.0)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ builder (3.0.0)
13
+ crack (0.1.8)
14
+ cucumber (0.10.3)
15
+ builder (>= 2.1.2)
16
+ diff-lcs (>= 1.1.2)
17
+ gherkin (>= 2.3.8)
18
+ json (>= 1.4.6)
19
+ term-ansicolor (>= 1.0.5)
20
+ cucumber-sinatra (0.3.1)
21
+ templater (>= 1.0.0)
22
+ diff-lcs (1.1.2)
23
+ extlib (0.9.15)
24
+ gherkin (2.3.9)
25
+ json (>= 1.4.6)
26
+ highline (1.6.2)
27
+ httparty (0.7.7)
28
+ crack (= 0.1.8)
29
+ json (1.4.6)
30
+ rack (1.3.0)
31
+ rack-test (0.6.0)
32
+ rack (>= 1.0)
33
+ rspec (2.6.0)
34
+ rspec-core (~> 2.6.0)
35
+ rspec-expectations (~> 2.6.0)
36
+ rspec-mocks (~> 2.6.0)
37
+ rspec-core (2.6.3)
38
+ rspec-expectations (2.6.0)
39
+ diff-lcs (~> 1.1.2)
40
+ rspec-mocks (2.6.0)
41
+ sham_rack (1.3.3)
42
+ rack
43
+ sinatra (1.2.6)
44
+ rack (~> 1.1)
45
+ tilt (>= 1.2.2, < 2.0)
46
+ templater (1.0.0)
47
+ diff-lcs (>= 1.1.2)
48
+ extlib (>= 0.9.5)
49
+ highline (>= 1.4.0)
50
+ term-ansicolor (1.0.5)
51
+ tilt (1.3.1)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ cucumber (>= 0.10)
58
+ cucumber-sinatra
59
+ harper!
60
+ rack-test
61
+ rspec
62
+ sham_rack
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'cucumber'
3
+ require 'cucumber/rake/task'
4
+ require 'bundler'
5
+
6
+ namespace :gem do
7
+ Bundler::GemHelper.install_tasks
8
+ end
9
+
10
+ namespace :test do
11
+ RSpec::Core::RakeTask.new(:rspec) do |t|
12
+ t.rspec_opts = "--color"
13
+ end
14
+
15
+ Cucumber::Rake::Task.new(:features) do |t|
16
+ t.cucumber_opts = "features --format pretty"
17
+ end
18
+ end
19
+
20
+ desc "Run all tests"
21
+ task :test => ['test:rspec', 'test:features']
data/bin/harper ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "harper"
4
+
5
+ port = ARGV.shift || 4567
6
+
7
+ Rack::Server.start :app => Harper, :Port => port
@@ -0,0 +1,67 @@
1
+ Feature: Mock an HTTP RPC web service
2
+ As a developer who cares about feedback cycles
3
+ I want to be able to easily mock service endpoints
4
+ So that I can fluently describe my test scenarios
5
+
6
+ Scenario: Submit a simple service request to mock
7
+ Given the following response mock, known as "original":
8
+ """
9
+ {
10
+ "url": "/service",
11
+ "method": "GET",
12
+ "content-type": "text/plain",
13
+ "body": "fake body"
14
+ }
15
+ """
16
+ When the application POSTs the mock "original" to "/h/mocks"
17
+ Then the response code should be "201"
18
+ And the "original" mock is available at the URL in the "Location" header
19
+ When the application issues a "GET" request for "/service"
20
+ Then the response code should be "200"
21
+ And the response "content-type" header should be "text/plain"
22
+ And the response body should be:
23
+ """
24
+ fake body
25
+ """
26
+ When the application removes the mock "original"
27
+ And the application issues a "GET" request for "/service"
28
+ Then the response code should be "503"
29
+
30
+ Scenario Outline: All HTTP methods can be mocked
31
+ Given a defined response mock with a "method" of "<METHOD>"
32
+ When the application issues a "<METHOD>" request to the mock
33
+ Then the response code should be "200"
34
+ Examples:
35
+ | METHOD |
36
+ | GET |
37
+ | POST |
38
+ | DELETE |
39
+ | PUT |
40
+
41
+ Scenario Outline: HTTP status codes can be mocked
42
+ Given a defined response mock with a "status" of "<STATUS>"
43
+ When the application issues a "GET" request to the mock
44
+ Then the response code should be "<STATUS>"
45
+ Examples:
46
+ | STATUS |
47
+ | 200 |
48
+ | 201 |
49
+ | 206 |
50
+ | 301 |
51
+ | 304 |
52
+ | 401 |
53
+ | 403 |
54
+ | 404 |
55
+ | 500 |
56
+ | 503 |
57
+
58
+ Scenario Outline: Responses from the server can be delayed
59
+ Given a defined response mock with a "delay" of "<MILLISECONDS>"
60
+ When the application issues a "GET" request to the mock
61
+ Then the response time should be at least "<MILLISECONDS>"
62
+ Examples:
63
+ | MILLISECONDS |
64
+ | 0 |
65
+ | 10 |
66
+ | 100 |
67
+ | 1000 |
@@ -0,0 +1,34 @@
1
+ When /^the application issues an? "([A-Z]+)" request for "([^"]*)"$/ do |method, url|
2
+ case method
3
+ when "GET"
4
+ get :from => url
5
+ when "POST"
6
+ post :to => url
7
+ when "DELETE"
8
+ delete :at => url
9
+ when "PUT"
10
+ put :to => url
11
+ else
12
+ method.should == "unsupported HTTP method"
13
+ end
14
+ end
15
+
16
+ When /^the application issues an? "([A-Z]+)" request to the mock$/ do |method|
17
+ When %{the application issues a "#{method}" request for "/service"}
18
+ end
19
+
20
+ Then /^the response code should be "(\d+)"$/ do |expected|
21
+ response.code.should == expected.to_i
22
+ end
23
+
24
+ Then /^the response "([^"]*)" header should be "([^"]*)"$/ do |header, expected|
25
+ response.headers[header].should match(expected)
26
+ end
27
+
28
+ Then /^the response body should be:$/ do |expected|
29
+ response.body.should == expected
30
+ end
31
+
32
+ Then /^the response time should be at least "(\d*)"$/ do |min_time|
33
+ response.time.should >= min_time.to_i
34
+ end
@@ -0,0 +1,26 @@
1
+ Given /^the following response mock, known as "([^"]*)":$/ do |name, mock|
2
+ known_mock name, :body => mock
3
+ end
4
+
5
+ Given /^a defined response mock with a "([^"]*)" of "([^"]*)"$/ do |field, value|
6
+ known_mock "defined", :body => { :url => "/service",
7
+ :method => "GET",
8
+ 'content-type' => "text/plain",
9
+ :body => "fake body" }.merge(field.to_sym => value).to_json
10
+ define_mock "defined"
11
+ end
12
+
13
+ When %r{^the application POSTs the mock "([^"]*)" to "/h/mocks"$} do |name|
14
+ define_mock name
15
+ end
16
+
17
+ When /^the application removes the mock "([^"]*)"$/ do |name|
18
+ remove_mock name
19
+ end
20
+
21
+ Then /^the "([^"]*)" mock is available at the URL in the "([^"]*)" header$/ do |name, header|
22
+ id_url = response.headers[header]
23
+ known_mock name, :url => id_url
24
+ get :from => id_url
25
+ response.code.should == 200
26
+ end
@@ -0,0 +1,219 @@
1
+ # Taken from the cucumber-rails project.
2
+ # IMPORTANT: This file is generated by cucumber-sinatra - edit at your own peril.
3
+ # It is recommended to regenerate this file in the future when you upgrade to a
4
+ # newer version of cucumber-sinatra. Consider adding your own code to a new file
5
+ # instead of editing this one. Cucumber will automatically load all features/**/*.rb
6
+ # files.
7
+
8
+ require 'uri'
9
+ require 'cgi'
10
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
11
+
12
+ module WithinHelpers
13
+ def with_scope(locator)
14
+ locator ? within(locator) { yield } : yield
15
+ end
16
+ end
17
+ World(WithinHelpers)
18
+
19
+ Given /^(?:|I )am on (.+)$/ do |page_name|
20
+ visit path_to(page_name)
21
+ end
22
+
23
+ When /^(?:|I )go to (.+)$/ do |page_name|
24
+ visit path_to(page_name)
25
+ end
26
+
27
+ When /^(?:|I )press "([^\"]*)"(?: within "([^\"]*)")?$/ do |button, selector|
28
+ with_scope(selector) do
29
+ click_button(button)
30
+ end
31
+ end
32
+
33
+ When /^(?:|I )follow "([^\"]*)"(?: within "([^\"]*)")?$/ do |link, selector|
34
+ with_scope(selector) do
35
+ click_link(link)
36
+ end
37
+ end
38
+
39
+ When /^(?:|I )fill in "([^\"]*)" with "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, value, selector|
40
+ with_scope(selector) do
41
+ fill_in(field, :with => value)
42
+ end
43
+ end
44
+
45
+ When /^(?:|I )fill in "([^\"]*)" for "([^\"]*)"(?: within "([^\"]*)")?$/ do |value, field, selector|
46
+ with_scope(selector) do
47
+ fill_in(field, :with => value)
48
+ end
49
+ end
50
+
51
+ # Use this to fill in an entire form with data from a table. Example:
52
+ #
53
+ # When I fill in the following:
54
+ # | Account Number | 5002 |
55
+ # | Expiry date | 2009-11-01 |
56
+ # | Note | Nice guy |
57
+ # | Wants Email? | |
58
+ #
59
+ # TODO: Add support for checkbox, select og option
60
+ # based on naming conventions.
61
+ #
62
+ When /^(?:|I )fill in the following(?: within "([^\"]*)")?:$/ do |selector, fields|
63
+ with_scope(selector) do
64
+ fields.rows_hash.each do |name, value|
65
+ When %{I fill in "#{name}" with "#{value}"}
66
+ end
67
+ end
68
+ end
69
+
70
+ When /^(?:|I )select "([^\"]*)" from "([^\"]*)"(?: within "([^\"]*)")?$/ do |value, field, selector|
71
+ with_scope(selector) do
72
+ select(value, :from => field)
73
+ end
74
+ end
75
+
76
+ When /^(?:|I )check "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, selector|
77
+ with_scope(selector) do
78
+ check(field)
79
+ end
80
+ end
81
+
82
+ When /^(?:|I )uncheck "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, selector|
83
+ with_scope(selector) do
84
+ uncheck(field)
85
+ end
86
+ end
87
+
88
+ When /^(?:|I )choose "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, selector|
89
+ with_scope(selector) do
90
+ choose(field)
91
+ end
92
+ end
93
+
94
+ When /^(?:|I )attach the file "([^\"]*)" to "([^\"]*)"(?: within "([^\"]*)")?$/ do |path, field, selector|
95
+ with_scope(selector) do
96
+ attach_file(field, path)
97
+ end
98
+ end
99
+
100
+ Then /^(?:|I )should see JSON:$/ do |expected_json|
101
+ require 'json'
102
+ expected = JSON.pretty_generate(JSON.parse(expected_json))
103
+ actual = JSON.pretty_generate(JSON.parse(response.body))
104
+ expected.should == actual
105
+ end
106
+
107
+ Then /^(?:|I )should see "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, selector|
108
+ with_scope(selector) do
109
+ if page.respond_to? :should
110
+ page.should have_content(text)
111
+ else
112
+ assert page.has_content?(text)
113
+ end
114
+ end
115
+ end
116
+
117
+ Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^\"]*)")?$/ do |regexp, selector|
118
+ regexp = Regexp.new(regexp)
119
+ with_scope(selector) do
120
+ if page.respond_to? :should
121
+ page.should have_xpath('//*', :text => regexp)
122
+ else
123
+ assert page.has_xpath?('//*', :text => regexp)
124
+ end
125
+ end
126
+ end
127
+
128
+ Then /^(?:|I )should not see "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, selector|
129
+ with_scope(selector) do
130
+ if page.respond_to? :should
131
+ page.should have_no_content(text)
132
+ else
133
+ assert page.has_no_content?(text)
134
+ end
135
+ end
136
+ end
137
+
138
+ Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^\"]*)")?$/ do |regexp, selector|
139
+ regexp = Regexp.new(regexp)
140
+ with_scope(selector) do
141
+ if page.respond_to? :should
142
+ page.should have_no_xpath('//*', :text => regexp)
143
+ else
144
+ assert page.has_no_xpath?('//*', :text => regexp)
145
+ end
146
+ end
147
+ end
148
+
149
+ Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should contain "([^\"]*)"$/ do |field, selector, value|
150
+ with_scope(selector) do
151
+ field = find_field(field)
152
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
153
+ if field_value.respond_to? :should
154
+ field_value.should =~ /#{value}/
155
+ else
156
+ assert_match(/#{value}/, field_value)
157
+ end
158
+ end
159
+ end
160
+
161
+ Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should not contain "([^\"]*)"$/ do |field, selector, value|
162
+ with_scope(selector) do
163
+ field = find_field(field)
164
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
165
+ if field_value.respond_to? :should_not
166
+ field_value.should_not =~ /#{value}/
167
+ else
168
+ assert_no_match(/#{value}/, field_value)
169
+ end
170
+ end
171
+ end
172
+
173
+ Then /^the "([^\"]*)" checkbox(?: within "([^\"]*)")? should be checked$/ do |label, selector|
174
+ with_scope(selector) do
175
+ field_checked = find_field(label)['checked']
176
+ if field_checked.respond_to? :should
177
+ field_checked.should == 'checked'
178
+ else
179
+ assert_equal 'checked', field_checked
180
+ end
181
+ end
182
+ end
183
+
184
+ Then /^the "([^\"]*)" checkbox(?: within "([^\"]*)")? should not be checked$/ do |label, selector|
185
+ with_scope(selector) do
186
+ field_checked = find_field(label)['checked']
187
+ if field_checked.respond_to? :should_not
188
+ field_checked.should_not == 'checked'
189
+ else
190
+ assert_not_equal 'checked', field_checked
191
+ end
192
+ end
193
+ end
194
+
195
+ Then /^(?:|I )should be on (.+)$/ do |page_name|
196
+ current_path = URI.parse(current_url).path
197
+ if current_path.respond_to? :should
198
+ current_path.should == path_to(page_name)
199
+ else
200
+ assert_equal path_to(page_name), current_path
201
+ end
202
+ end
203
+
204
+ Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
205
+ query = URI.parse(current_url).query
206
+ actual_params = query ? CGI.parse(query) : {}
207
+ expected_params = {}
208
+ expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
209
+
210
+ if actual_params.respond_to? :should
211
+ actual_params.should == expected_params
212
+ else
213
+ assert_equal expected_params, actual_params
214
+ end
215
+ end
216
+
217
+ Then /^show me the page$/ do
218
+ save_and_open_page
219
+ end
@@ -0,0 +1,55 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'benchmark'
4
+
5
+ module HTTParty
6
+ class Response
7
+ attr_accessor :time
8
+ end
9
+ end
10
+
11
+ class ClientApplication
12
+ include HTTParty
13
+
14
+ base_uri 'example.com'
15
+
16
+ attr_reader :response
17
+
18
+ def initialize
19
+ @known_mocks = Hash.new { |h, k| h[k] = {} }
20
+ end
21
+
22
+ def known_mock(name, description = {})
23
+ @known_mocks[name].merge!(description)
24
+ end
25
+
26
+ def define_mock(name)
27
+ @response = self.class.post "/h/mocks", :body => @known_mocks[name][:body]
28
+ end
29
+
30
+ def remove_mock(name)
31
+ @response = self.class.delete @known_mocks[name][:url]
32
+ end
33
+
34
+ def get(options)
35
+ timed { @response = self.class.get options[:from] }
36
+ end
37
+
38
+ def post(options)
39
+ timed { @response = self.class.post options[:to] }
40
+ end
41
+
42
+ def delete(options)
43
+ timed { @response = self.class.delete options[:at] }
44
+ end
45
+
46
+ def put(options)
47
+ timed { @response = self.class.put options[:to] }
48
+ end
49
+
50
+ def timed
51
+ val = nil
52
+ timed = time { val = yield }
53
+ val.time = timed
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ # Generated by cucumber-sinatra. (Wed May 25 23:29:21 -0700 2011)
2
+
3
+ ENV['RACK_ENV'] = 'test'
4
+
5
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib/harper.rb')
6
+ require File.join(File.dirname(__FILE__), 'client_application')
7
+
8
+ require 'sham_rack'
9
+ require 'rspec'
10
+
11
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec/spec_helper')
12
+
13
+ class HarperWorld
14
+ include RSpec::Expectations
15
+ include RSpec::Matchers
16
+ end
17
+
18
+ World do
19
+ HarperWorld.new
20
+ ClientApplication.new
21
+ end
22
+
23
+ ShamRack.at("example.com").rackup do
24
+ run Harper.new
25
+ end
@@ -0,0 +1,29 @@
1
+ # Taken from the cucumber-rails project.
2
+
3
+ module NavigationHelpers
4
+ # Maps a name to a path. Used by the
5
+ #
6
+ # When /^I go to (.+)$/ do |page_name|
7
+ #
8
+ # step definition in web_steps.rb
9
+ #
10
+ def path_to(page_name)
11
+ case page_name
12
+
13
+ when /the home\s?page/
14
+ '/'
15
+
16
+ # Add more mappings here.
17
+ # Here is an example that pulls values out of the Regexp:
18
+ #
19
+ # when /^(.*)'s profile page$/i
20
+ # user_profile_path(User.find_by_login($1))
21
+
22
+ else
23
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
24
+ "Now, go and add a mapping in #{__FILE__}"
25
+ end
26
+ end
27
+ end
28
+
29
+ World(NavigationHelpers)
data/harper.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "harper"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "harper"
7
+ s.version = Harper::Version
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Giles Alexander"]
10
+ s.email = ["giles.alexander@gmail.com"]
11
+ s.homepage = "http://github.com/gga/harper"
12
+ s.summary = %q{Dead simple mocking for HTTP services}
13
+ s.description = %q{Dead simple mocking for HTTP services, a Sinatra app}
14
+
15
+ s.rubyforge_project = "harper"
16
+
17
+ s.required_rubygems_version = ">= 1.3.6"
18
+
19
+ s.add_dependency "sinatra", ">= 1.0.0"
20
+ s.add_dependency "httparty"
21
+ s.add_dependency "json", ">= 1.4.6"
22
+
23
+ s.add_development_dependency "rack-test"
24
+ s.add_development_dependency "sham_rack"
25
+ s.add_development_dependency "rspec"
26
+ s.add_development_dependency "cucumber", ">= 0.10"
27
+ s.add_development_dependency "cucumber-sinatra"
28
+
29
+ s.files = `git ls-files`.split("\n")
30
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
31
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
+ s.require_paths = ["lib"]
33
+ end
data/lib/harper.rb ADDED
@@ -0,0 +1,81 @@
1
+ require 'base64'
2
+ require 'sinatra/base'
3
+ require 'logger'
4
+ require 'json'
5
+
6
+ class Harper < Sinatra::Base
7
+
8
+ Version = "0.0.2"
9
+
10
+ @@mocks = {}
11
+
12
+ enable :logging
13
+
14
+ configure do
15
+ LOGGER = Logger.new("sinatra.log")
16
+ end
17
+
18
+ helpers do
19
+ def logger
20
+ LOGGER
21
+ end
22
+
23
+ def mock_id(url)
24
+ [url].pack('m').tr("+/=", "-_.").gsub("\n", '')
25
+ end
26
+ end
27
+
28
+ post '/h/mocks' do
29
+ mock = JSON(request.body.read)
30
+
31
+ mock['url'] = mock['url'][1..-1] if mock['url'] =~ /^\//
32
+
33
+ mock['id'] = mock_id(mock['url'])
34
+ mock['method'].upcase!
35
+ mock['delay'] = mock['delay'].to_f / 1000.0
36
+ @@mocks[mock['id']] = mock
37
+
38
+ logger.info("Created mock for endpoint: '#{mock['url']}'")
39
+
40
+ headers['location'] = "/h/mocks/#{mock['id']}"
41
+ status "201"
42
+ end
43
+
44
+ delete '/h/mocks' do
45
+ @@mocks = {}
46
+ end
47
+
48
+ get '/h/mocks/:mock_id' do |mock_name|
49
+ content_type :json
50
+ status "200"
51
+ @@mocks[mock_name].to_json
52
+ end
53
+
54
+ delete '/h/mocks/:mock_id' do |mock_name|
55
+ @@mocks[mock_name] = nil
56
+
57
+ status "200"
58
+ end
59
+
60
+ [:get, :post, :put, :delete].each do |method|
61
+ self.send(method, '*') do
62
+ mock_id = mock_id(request.path[1..-1])
63
+
64
+ logger.debug("#{request.request_method} request for a mock: '#{request.path}'")
65
+
66
+ mock = @@mocks[mock_id]
67
+ if mock && request.request_method == mock['method']
68
+ content_type mock['content-type']
69
+ status mock['status'] || "200"
70
+ sleep mock['delay']
71
+
72
+ logger.info("Serving mocked body for endpoint: '#{mock['url']}'")
73
+
74
+ mock['body']
75
+ else
76
+ status "503"
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,151 @@
1
+ require 'spec/spec_helper'
2
+ require 'harper'
3
+
4
+ describe Harper do
5
+ include Rack::Test::Methods
6
+
7
+ supported_verbs = ["GET", "POST", "PUT", "DELETE"]
8
+
9
+ let(:app) { Harper.new }
10
+
11
+ let(:method) { "GET" }
12
+ let(:url) { "/service" }
13
+ let(:status_code) { 200 }
14
+ let(:content_type) { "text/plain" }
15
+ let(:body) { "fake body" }
16
+ let(:delay) { 0 }
17
+
18
+ let(:mock_def) do
19
+ { :method => method,
20
+ :url => url,
21
+ :delay => delay,
22
+ :'content-type' => content_type,
23
+ :body => body }.to_json
24
+ end
25
+
26
+ it "should allow all mocks to be deleted" do
27
+ post '/h/mocks', mock_def
28
+
29
+ delete '/h/mocks'
30
+ last_response.should be_ok
31
+ get url
32
+ last_response.status.should == 503
33
+ end
34
+
35
+ describe "mock methods are case insensitive:" do
36
+ supported_verbs.each do |http_verb|
37
+ [lambda { |v| v.upcase },
38
+ lambda { |v| v.downcase },
39
+ lambda { |v| v[0..0] + v[1..-1].downcase}].each do |fn|
40
+ cased_verb = fn.call(http_verb)
41
+ context cased_verb do
42
+ let(:method) { cased_verb }
43
+
44
+ before(:each) do
45
+ post '/h/mocks', mock_def
46
+ @created_mock = last_response.headers['location']
47
+ end
48
+
49
+ after(:each) do
50
+ delete @created_mock
51
+ end
52
+
53
+ it "is a valid method" do
54
+ self.send(cased_verb.downcase.to_sym, url)
55
+ last_response.should be_ok
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ supported_verbs.each do |http_verb|
63
+ context "#{http_verb} mock" do
64
+ let(:method) { http_verb }
65
+
66
+ before(:each) do
67
+ post '/h/mocks', mock_def
68
+ end
69
+
70
+ it "should respond with a 201 created" do
71
+ last_response.status.should == 201
72
+ end
73
+
74
+ it "should point to a newly created mock resource" do
75
+ last_response.headers['Location'].should match(%r{/h/mocks/})
76
+ end
77
+
78
+ context "long service urls" do
79
+ let(:url) { "http://www.averylong.com/url/thathas/multiple/slashes/and/thelike/" }
80
+ it "should be queryable" do
81
+ get last_response.headers['Location']
82
+ last_response.status.should == 200
83
+ end
84
+ end
85
+
86
+ context "is a mock" do
87
+ before(:each) do
88
+ self.send(method.downcase.to_sym, url)
89
+ end
90
+
91
+ it "has a mocked status code" do
92
+ last_response.status.should == status_code
93
+ end
94
+ it "has a mocked content-type" do
95
+ last_response.headers['Content-Type'].should match(content_type)
96
+ end
97
+ it "has a mocked body" do
98
+ last_response.body.should == body
99
+ end
100
+ it "should not support unmocked HTTP methods" do
101
+ (supported_verbs - [method]).each do |non_method|
102
+ self.send(non_method.downcase.to_sym, url)
103
+ last_response.status.should == 503
104
+ end
105
+ end
106
+ end
107
+
108
+ it "should allow the mock resource to be deleted" do
109
+ delete last_response.headers['Location']
110
+ last_response.should be_ok
111
+ self.send(method.downcase.to_sym, url)
112
+ last_response.status.should == 503
113
+ end
114
+ end
115
+ end
116
+
117
+ context "delayed mocks" do
118
+
119
+ before(:each) do
120
+ post '/h/mocks', mock_def
121
+ @created_mock = last_response.headers['location']
122
+ end
123
+
124
+ after(:each) do
125
+ delete @created_mock
126
+ end
127
+
128
+ context "short delay" do
129
+ let(:delay) { 100 }
130
+
131
+ it "should take at least the specified delay to provide a response" do
132
+ time { get url }.should >= delay
133
+ end
134
+ end
135
+ context "long delay" do
136
+ let(:delay) { 1000 }
137
+
138
+ it "should take at least the specified delay to provide a response" do
139
+ time { get url }.should >= delay
140
+ end
141
+ end
142
+ context "no delay specified" do
143
+ let(:delay) { nil }
144
+
145
+ it "should take close to 0 delay in providing a response" do
146
+ time { get url }.should <= 1.0
147
+ end
148
+ end
149
+ end
150
+
151
+ end
@@ -0,0 +1,11 @@
1
+ require 'rack/test'
2
+ require 'sinatra'
3
+ require 'rspec/mocks/standalone'
4
+
5
+ $: << File.dirname(__FILE__) + "/.."
6
+
7
+ def time
8
+ start = Time.now
9
+ yield
10
+ (Time.now - start) * 1000.0
11
+ end
metadata ADDED
@@ -0,0 +1,210 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: harper
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Giles Alexander
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-15 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: sinatra
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 1
32
+ - 0
33
+ - 0
34
+ version: 1.0.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: httparty
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: json
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 11
60
+ segments:
61
+ - 1
62
+ - 4
63
+ - 6
64
+ version: 1.4.6
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: rack-test
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: sham_rack
83
+ prerelease: false
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ type: :development
94
+ version_requirements: *id005
95
+ - !ruby/object:Gem::Dependency
96
+ name: rspec
97
+ prerelease: false
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ version_requirements: *id006
109
+ - !ruby/object:Gem::Dependency
110
+ name: cucumber
111
+ prerelease: false
112
+ requirement: &id007 !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 31
118
+ segments:
119
+ - 0
120
+ - 10
121
+ version: "0.10"
122
+ type: :development
123
+ version_requirements: *id007
124
+ - !ruby/object:Gem::Dependency
125
+ name: cucumber-sinatra
126
+ prerelease: false
127
+ requirement: &id008 !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ type: :development
137
+ version_requirements: *id008
138
+ description: Dead simple mocking for HTTP services, a Sinatra app
139
+ email:
140
+ - giles.alexander@gmail.com
141
+ executables:
142
+ - harper
143
+ extensions: []
144
+
145
+ extra_rdoc_files: []
146
+
147
+ files:
148
+ - .gitignore
149
+ - .rvmrc
150
+ - Gemfile
151
+ - Gemfile.lock
152
+ - Rakefile
153
+ - bin/harper
154
+ - features/mock_http_rpc_service.feature
155
+ - features/step_definitions/http_steps.rb
156
+ - features/step_definitions/mock_definition_steps.rb
157
+ - features/step_definitions/web_steps.rb
158
+ - features/support/client_application.rb
159
+ - features/support/env.rb
160
+ - features/support/paths.rb
161
+ - harper.gemspec
162
+ - lib/harper.rb
163
+ - spec/harper_spec.rb
164
+ - spec/spec_helper.rb
165
+ has_rdoc: true
166
+ homepage: http://github.com/gga/harper
167
+ licenses: []
168
+
169
+ post_install_message:
170
+ rdoc_options: []
171
+
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ none: false
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ hash: 3
180
+ segments:
181
+ - 0
182
+ version: "0"
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ none: false
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ hash: 23
189
+ segments:
190
+ - 1
191
+ - 3
192
+ - 6
193
+ version: 1.3.6
194
+ requirements: []
195
+
196
+ rubyforge_project: harper
197
+ rubygems_version: 1.6.2
198
+ signing_key:
199
+ specification_version: 3
200
+ summary: Dead simple mocking for HTTP services
201
+ test_files:
202
+ - features/mock_http_rpc_service.feature
203
+ - features/step_definitions/http_steps.rb
204
+ - features/step_definitions/mock_definition_steps.rb
205
+ - features/step_definitions/web_steps.rb
206
+ - features/support/client_application.rb
207
+ - features/support/env.rb
208
+ - features/support/paths.rb
209
+ - spec/harper_spec.rb
210
+ - spec/spec_helper.rb