spoofer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +93 -0
  4. data/Rakefile +25 -0
  5. data/features/checking_requests_were_made.feature +39 -0
  6. data/features/configuring_spoofer_via_http.feature +175 -0
  7. data/features/echoing_request_in_response.feature +76 -0
  8. data/features/logging_requests.feature +13 -0
  9. data/features/resetting_stubs.feature +16 -0
  10. data/features/steps/http_client_steps.rb +60 -0
  11. data/features/steps/logging_steps.rb +3 -0
  12. data/features/steps/shell_steps.rb +10 -0
  13. data/features/steps/spoofer_steps.rb +21 -0
  14. data/features/stubbing_requests_by_method.feature +25 -0
  15. data/features/stubbing_requests_by_path.feature +60 -0
  16. data/features/stubbing_requests_from_a_file.feature +33 -0
  17. data/features/stubbing_requests_with_parameters.feature +28 -0
  18. data/features/support/env.rb +15 -0
  19. data/features/support/hash_key_path.rb +7 -0
  20. data/features/support/http_client.rb +50 -0
  21. data/features/support/spoofer_runner.rb +7 -0
  22. data/features/using_rack_middlewares.feature +31 -0
  23. data/lib/spoofer.rb +104 -0
  24. data/lib/spoofer/api.rb +50 -0
  25. data/lib/spoofer/api/api_request.rb +49 -0
  26. data/lib/spoofer/api/stub.rb +39 -0
  27. data/lib/spoofer/fake_host.rb +102 -0
  28. data/lib/spoofer/fake_host/helpers.rb +11 -0
  29. data/lib/spoofer/fake_host/request_echo.rb +42 -0
  30. data/lib/spoofer/fake_host/stubbed_request.rb +73 -0
  31. data/lib/spoofer/version.rb +3 -0
  32. data/spec/fixtures/import_stubs.spoof +3 -0
  33. data/spec/spec_helper.rb +21 -0
  34. data/spec/spoofer/fake_host_spec.rb +74 -0
  35. data/spec/spoofer/stubbed_request_spec.rb +16 -0
  36. data/spec/support/.keep +0 -0
  37. data/spec/support/matchers.rb +7 -0
  38. data/spec/support/pry.rb +1 -0
  39. data/spec/support/request_helper.rb +7 -0
  40. metadata +233 -0
@@ -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,11 @@
1
+ require 'spoofer'
2
+
3
+ module Spoofer
4
+ module FakeHost::Helpers
5
+ def echo_request!(format)
6
+ RequestEcho.new(request).response_as(format)
7
+ end
8
+ end
9
+ end
10
+
11
+
@@ -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
@@ -0,0 +1,3 @@
1
+ module Spoofer
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,3 @@
1
+ get "/imported/path" do
2
+ [200, {}, ""]
3
+ end
@@ -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