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