spoofer 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +93 -0
- data/Rakefile +25 -0
- data/features/checking_requests_were_made.feature +39 -0
- data/features/configuring_spoofer_via_http.feature +175 -0
- data/features/echoing_request_in_response.feature +76 -0
- data/features/logging_requests.feature +13 -0
- data/features/resetting_stubs.feature +16 -0
- data/features/steps/http_client_steps.rb +60 -0
- data/features/steps/logging_steps.rb +3 -0
- data/features/steps/shell_steps.rb +10 -0
- data/features/steps/spoofer_steps.rb +21 -0
- data/features/stubbing_requests_by_method.feature +25 -0
- data/features/stubbing_requests_by_path.feature +60 -0
- data/features/stubbing_requests_from_a_file.feature +33 -0
- data/features/stubbing_requests_with_parameters.feature +28 -0
- data/features/support/env.rb +15 -0
- data/features/support/hash_key_path.rb +7 -0
- data/features/support/http_client.rb +50 -0
- data/features/support/spoofer_runner.rb +7 -0
- data/features/using_rack_middlewares.feature +31 -0
- data/lib/spoofer.rb +104 -0
- data/lib/spoofer/api.rb +50 -0
- data/lib/spoofer/api/api_request.rb +49 -0
- data/lib/spoofer/api/stub.rb +39 -0
- data/lib/spoofer/fake_host.rb +102 -0
- data/lib/spoofer/fake_host/helpers.rb +11 -0
- data/lib/spoofer/fake_host/request_echo.rb +42 -0
- data/lib/spoofer/fake_host/stubbed_request.rb +73 -0
- data/lib/spoofer/version.rb +3 -0
- data/spec/fixtures/import_stubs.spoof +3 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/spoofer/fake_host_spec.rb +74 -0
- data/spec/spoofer/stubbed_request_spec.rb +16 -0
- data/spec/support/.keep +0 -0
- data/spec/support/matchers.rb +7 -0
- data/spec/support/pry.rb +1 -0
- data/spec/support/request_helper.rb +7 -0
- metadata +233 -0
data/lib/spoofer/api.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spoofer'
|
2
|
+
|
3
|
+
module Spoofer
|
4
|
+
class API < Sinatra::Base
|
5
|
+
class << self
|
6
|
+
attr_accessor :host
|
7
|
+
end
|
8
|
+
|
9
|
+
def host
|
10
|
+
self.class.host
|
11
|
+
end
|
12
|
+
|
13
|
+
%w{get post put delete head}.each do |verb|
|
14
|
+
post "/#{verb}" do
|
15
|
+
api_request = APIRequest.from_request(request, verb)
|
16
|
+
api_request.setup_stubs_on(host)
|
17
|
+
[201, {"Content-Type" => api_request.request_content_type}, api_request.response]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
post "/multi" do
|
22
|
+
api_request = APIRequest.from_request(request)
|
23
|
+
api_request.setup_stubs_on(host)
|
24
|
+
[201, {"Content-Type" => api_request.request_content_type}, api_request.response]
|
25
|
+
end
|
26
|
+
|
27
|
+
post "/clear" do
|
28
|
+
response_body = self.host.inspect
|
29
|
+
self.host.clear
|
30
|
+
[200, {}, "Cleared stubs: #{response_body}"]
|
31
|
+
end
|
32
|
+
|
33
|
+
get "/ping" do
|
34
|
+
[200, {}, "OK"]
|
35
|
+
end
|
36
|
+
|
37
|
+
get "/debug" do
|
38
|
+
[200, {}, self.host.inspect]
|
39
|
+
end
|
40
|
+
|
41
|
+
get "/requests" do
|
42
|
+
[200, {"Content-Type" => "application/json"}, {"requests" => host.received_requests.map(&:to_hash)}.to_json]
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
autoload :ApiRequest, 'spoofer/api/api_request'
|
48
|
+
autoload :Stub, 'spoofer/api/stub'
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spoofer'
|
2
|
+
|
3
|
+
module Spoofer
|
4
|
+
class API::APIRequest
|
5
|
+
attr_reader :request_content_type
|
6
|
+
|
7
|
+
def self.from_request(request, method = nil)
|
8
|
+
case request.content_type
|
9
|
+
when /json/
|
10
|
+
data = JSON.parse(request.body.string)
|
11
|
+
when /plist/
|
12
|
+
data = Plist.parse_xml(request.body.string)
|
13
|
+
else
|
14
|
+
data = JSON.parse(request.body.string)
|
15
|
+
end
|
16
|
+
new(data, method, request.content_type)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(data, method = nil, request_content_type = '')
|
20
|
+
@data = data
|
21
|
+
@method = (method || "GET")
|
22
|
+
@stubs = []
|
23
|
+
@request_content_type = request_content_type
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
@data.inspect
|
28
|
+
end
|
29
|
+
|
30
|
+
def response
|
31
|
+
response = {"stubs" => @stubs.map(&:to_hash)}
|
32
|
+
|
33
|
+
case request_content_type
|
34
|
+
when /json/
|
35
|
+
response.to_json
|
36
|
+
when /plist/
|
37
|
+
response.to_plist
|
38
|
+
else
|
39
|
+
response.to_json
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def setup_stubs_on(host)
|
44
|
+
(@data["stubs"] || [@data]).each do |stub_data|
|
45
|
+
@stubs << Stub.new(stub_data, stub_data['method'] || @method).on(host)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Spoofer
|
2
|
+
class API::Stub
|
3
|
+
def initialize(data, method = nil)
|
4
|
+
@data = data
|
5
|
+
@method = method
|
6
|
+
end
|
7
|
+
|
8
|
+
def on(host)
|
9
|
+
host.send(@method.downcase.to_sym, path).returning(body, code, headers).tap do |stub|
|
10
|
+
stub.with_query_parameters(params)
|
11
|
+
stub.echo_request!(echo_format)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def echo_format
|
16
|
+
@data['echo'].to_sym rescue nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
@data['path'] || '/'
|
21
|
+
end
|
22
|
+
|
23
|
+
def body
|
24
|
+
@data['body'] || ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def code
|
28
|
+
@data['code'] || 200
|
29
|
+
end
|
30
|
+
|
31
|
+
def headers
|
32
|
+
@data['headers'] || {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def params
|
36
|
+
@data['params'] || {}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spoofer'
|
2
|
+
|
3
|
+
module Spoofer
|
4
|
+
class FakeHost
|
5
|
+
autoload :Helpers, 'spoofer/fake_host/helpers'
|
6
|
+
autoload :RequestEcho, 'spoofer/fake_host/request_echo'
|
7
|
+
autoload :StubbedRequest, 'spoofer/fake_host/stubbed_request'
|
8
|
+
|
9
|
+
attr_reader :hostname, :url_map
|
10
|
+
attr_accessor :log
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@hostname = options[:hostname]
|
14
|
+
@remote_configuration_path = options[:remote_configuration_path]
|
15
|
+
@log = options[:log]
|
16
|
+
@imports = []
|
17
|
+
clear
|
18
|
+
build_url_map!
|
19
|
+
end
|
20
|
+
|
21
|
+
def received_requests
|
22
|
+
@stubs.select { |s| s.received }
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(path, &block)
|
26
|
+
request("GET", path, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def post(path, &block)
|
30
|
+
request("POST", path, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def put(path, &block)
|
34
|
+
request("PUT", path, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(path, &block)
|
38
|
+
request("DELETE", path, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def head(path, &block)
|
42
|
+
request("HEAD", path, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def import(path)
|
46
|
+
if File.exists?(path)
|
47
|
+
@imports << path unless @imports.include?(path)
|
48
|
+
instance_eval(File.read(path))
|
49
|
+
else
|
50
|
+
raise "Could not locate file for stub import: #{path}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def call(env)
|
55
|
+
@stubs.each(&:build)
|
56
|
+
@app.call(env)
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear
|
60
|
+
@stubs = []
|
61
|
+
@app = Sinatra.new
|
62
|
+
@app.use(Rack::CommonLogger, self.log) if self.log
|
63
|
+
@app.not_found do
|
64
|
+
[404, {}, ""]
|
65
|
+
end
|
66
|
+
@app.helpers do
|
67
|
+
include Helpers
|
68
|
+
end
|
69
|
+
@imports.each { |file| import(file) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
@stubs.inspect
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def method_missing(method, *args, &block)
|
79
|
+
@app.send(method, *args, &block)
|
80
|
+
end
|
81
|
+
|
82
|
+
def request(method, path, &block)
|
83
|
+
if block_given?
|
84
|
+
@app.send(method.downcase, path, &block)
|
85
|
+
else
|
86
|
+
@stubs << StubbedRequest.new(@app, method, path)
|
87
|
+
@stubs.last
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_url_map!
|
92
|
+
routes = {'/' => self}
|
93
|
+
|
94
|
+
if @remote_configuration_path
|
95
|
+
API.host = self
|
96
|
+
routes[@remote_configuration_path] = API
|
97
|
+
end
|
98
|
+
|
99
|
+
@url_map = Rack::URLMap.new(routes)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Spoofer
|
2
|
+
class FakeHost::RequestEcho
|
3
|
+
def initialize(request)
|
4
|
+
@request = request
|
5
|
+
end
|
6
|
+
|
7
|
+
def response_as(format)
|
8
|
+
content_type = case format
|
9
|
+
when :json, :plist
|
10
|
+
"application/#{format.to_s.downcase}"
|
11
|
+
else
|
12
|
+
"text/plain"
|
13
|
+
end
|
14
|
+
[200, {"Content-Type" => content_type}, to_s(format)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s(format)
|
18
|
+
case format
|
19
|
+
when :json
|
20
|
+
to_hash.to_json
|
21
|
+
when :plist
|
22
|
+
to_hash.to_plist
|
23
|
+
when :text
|
24
|
+
to_hash.inspect
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
{"echo" => {
|
30
|
+
"params" => @request.params,
|
31
|
+
"env" => env_without_rack_and_async_env,
|
32
|
+
"body" => @request.body.read
|
33
|
+
}}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def env_without_rack_and_async_env
|
39
|
+
Hash[*@request.env.select { |key, value| key !~ /^(rack|async)/i }.flatten]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spoofer'
|
2
|
+
|
3
|
+
module Spoofer
|
4
|
+
class FakeHost::StubbedRequest
|
5
|
+
attr_accessor :received
|
6
|
+
|
7
|
+
def initialize(app, method, path)
|
8
|
+
@method, @path = method, path
|
9
|
+
@code = 200
|
10
|
+
@headers = {}
|
11
|
+
@params = {}
|
12
|
+
@body = ""
|
13
|
+
@app = app
|
14
|
+
@received = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
token = "#{@method} #{@path}"
|
19
|
+
Digest::MD5.hexdigest(token)
|
20
|
+
end
|
21
|
+
|
22
|
+
def returning(body, code=200, headers={})
|
23
|
+
tap do
|
24
|
+
@body = body
|
25
|
+
@code = code
|
26
|
+
@headers = headers
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_query_parameters(params)
|
31
|
+
tap do
|
32
|
+
@params = params
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def echo_request!(format=:json)
|
37
|
+
@echo_request_format = format
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches?(request)
|
41
|
+
if @params.any?
|
42
|
+
request.params == @params
|
43
|
+
else
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def matched_response
|
49
|
+
[@code, @headers, @body]
|
50
|
+
end
|
51
|
+
|
52
|
+
def unmatched_response
|
53
|
+
[404, "", {}]
|
54
|
+
end
|
55
|
+
|
56
|
+
def response_for_request(request)
|
57
|
+
if @echo_request_format
|
58
|
+
@body = RequestEcho.new(request).to_s(@echo_request_format)
|
59
|
+
end
|
60
|
+
|
61
|
+
matches?(request) ? matched_response : unmatched_response
|
62
|
+
end
|
63
|
+
|
64
|
+
def build
|
65
|
+
stub = self
|
66
|
+
|
67
|
+
@app.send(@method.downcase, @path) do
|
68
|
+
stub.received = true
|
69
|
+
stub.response_for_request(request)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'spoofer'
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
# Load support files
|
7
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
config.filter_run :focus
|
13
|
+
|
14
|
+
config.tty = true
|
15
|
+
|
16
|
+
# Run specs in random order to surface order dependencies. If you find an
|
17
|
+
# order dependency and want to debug it, you can fix the order by providing
|
18
|
+
# the seed, which is printed after each run.
|
19
|
+
# --seed 1234
|
20
|
+
config.order = 'random'
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spoofer::FakeHost do
|
4
|
+
let(:host) { described_class.new(:hostname => "www.example.com") }
|
5
|
+
|
6
|
+
it "should handle stubbed requests" do
|
7
|
+
host.get("/some/path")
|
8
|
+
host.call(request_for("/some/path")).should match_rack_response(200, {}, "")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should handle stubbed requests that return a response" do
|
12
|
+
host.get("/some/path").returning("hello world")
|
13
|
+
host.call(request_for("/some/path")).should match_rack_response(200, {}, "hello world")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should handle stubbed requests that return a specific HTTP code" do
|
17
|
+
host.get("/some/path").returning("redirecting", 301)
|
18
|
+
host.call(request_for("/some/path")).should match_rack_response(301, {}, "redirecting")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should handle stubbed requests that return specific headers" do
|
22
|
+
host.get("/some/path").returning("redirecting", 301, {"Location" => "http://somewhereelse.com"})
|
23
|
+
host.call(request_for("/some/path")).should match_rack_response(301, {"Location" => "http://somewhereelse.com"}, "redirecting")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not recognize requests if they have the incorrect HTTP method" do
|
27
|
+
host.get("/some/path")
|
28
|
+
host.call(request_for("/some/path", :method => "POST")).should match_rack_response(404, {}, "")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not handle multiple requests to a path with different HTTP methods" do
|
32
|
+
host.get("/some/path").returning("GET Request", 200)
|
33
|
+
host.post("/some/path").returning("POST Request", 201)
|
34
|
+
host.call(request_for("/some/path", :method => "GET")).should match_rack_response(200, {}, "GET Request")
|
35
|
+
host.call(request_for("/some/path", :method => "POST")).should match_rack_response(201, {}, "POST Request")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should handle requests with behaviour specified in a block using the Sinatra API" do
|
39
|
+
host.get("/some/path") do
|
40
|
+
content_type 'text/plain'
|
41
|
+
'bobby'
|
42
|
+
end
|
43
|
+
host.call(request_for("/some/path", :method => "GET")).should match_rack_response(200, {'Content-Type' => 'text/plain;charset=utf-8'}, 'bobby')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should allow stubs to be cleared" do
|
47
|
+
host.get("/some/path")
|
48
|
+
host.call(request_for("/some/path")).should match_rack_response(200, {}, "")
|
49
|
+
host.clear
|
50
|
+
host.call(request_for("/some/path")).should match_rack_response(404, {}, "")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should allow stubs to be imported from a file" do
|
54
|
+
host.import(File.join(File.dirname(__FILE__), *%w[.. fixtures import_stubs.spoof]))
|
55
|
+
host.call(request_for("/imported/path")).should match_rack_response(200, {}, "")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not clear imported stubs" do
|
59
|
+
host.import(File.join(File.dirname(__FILE__), *%w[.. fixtures import_stubs.spoof]))
|
60
|
+
host.clear
|
61
|
+
host.call(request_for("/imported/path")).should match_rack_response(200, {}, "")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should raise if import file does not exist" do
|
65
|
+
proc {
|
66
|
+
host.import(File.join(File.dirname(__FILE__), *%w[.. fixtures doesnt_exist.spoof]))
|
67
|
+
}.should raise_error
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns a StubbedRequest" do
|
71
|
+
host.get("/some/path").should be_kind_of(Spoofer::FakeHost::StubbedRequest)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|