harper 0.0.2

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