mock_server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2011 Charles Barbier <http://github.com/unixcharles>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # MockServer
2
+
3
+ So you have spec for your models and controller for your API.
4
+ Now you're building a neat javascript application that consumer that JSON.
5
+ And you're going to test it with something like capybara?
6
+ The thing is that its going to be slow! And you're testing testing all you're application logic again!
7
+
8
+ MockServer allow you to record your interaction with the server and give you a way to match
9
+ the record request with the real request. Its like a rack [VCR](https://github.com/myronmarston/vcr) for your own API!
10
+
11
+ # Recording mode
12
+
13
+ Create mount the rack application, in rails
14
+
15
+ require 'mock_server/record'
16
+ config.middleware.use MockServer::Record, { :path => 'fixtures/records', :filename => 'uploads'
17
+ :routes => [ '/api/*/**', '/api/*/**' ] }
18
+
19
+ And you're ready to record. Run the test, build so seed by clicking around. whatever.
20
+
21
+ # Playback mode
22
+
23
+ require 'mock_server/playback'
24
+ config.middleware.use MockServer::Playback, { :path => 'fixtures/records' ] }
25
+
26
+ Recording your own playback? Noes, don't use the two Rack middleware at the same time.
27
+
28
+ # Rspec
29
+
30
+ require 'mock_server/spec/helpers'
31
+ RSpec.configure do |config|
32
+ config.include MockServer::Spec::Helpers
33
+ end
34
+
35
+ In your spec
36
+
37
+ before :each do
38
+
39
+ # Set the filename where you store the records
40
+ mock_server_use_record 'uploads'
41
+
42
+ # From now on, those path belong to MockServer.
43
+ #
44
+ # if we can't match to a record, the server return a 404 and populate the errors stack.
45
+ mock_server_enable_routes '**/uploads/*', '**/uploads', '**/folders'
46
+
47
+ end
48
+
49
+ after :each do
50
+ mock_server_disable_all_routes!
51
+ mock_server_clear_matchers!
52
+ mock_server_response_stack_clear!
53
+ end
54
+
55
+ scenario "Some json api fun" do
56
+ mock_server_get('/api/2/projects')
57
+ mock_server_post('/api/2/projects') do |request, recorded_request|
58
+
59
+ # I use the should helpers because it return the method
60
+ # but it won't raise a failure in the test, since it happen
61
+ # inside a proc ouside the scenario. But It will populate the
62
+ # error stack if can't match anything.
63
+
64
+ recorded_request.body.name.should == recorded_request.body.name and
65
+ recorded_request.body.name.should == 'MockServer'
66
+
67
+ # Its a normal detect block, so it has to return true to match
68
+ # the record response.
69
+ }
70
+
71
+ ... fun stuff ...
72
+
73
+ mock_server_success_stack.should include('/api/2/projects')
74
+ mock_server_errors_stack.should be_empty
75
+ end
76
+
77
+ # Pull request?
78
+
79
+ Yes.
@@ -0,0 +1,69 @@
1
+ require 'mock_server/utils'
2
+
3
+ module MockServer
4
+ class Playback
5
+ include MockServer::Utils
6
+
7
+ def initialize(app, options = {})
8
+ @options = options
9
+ @app = app
10
+ $mock_server_options ||= options
11
+ end
12
+
13
+ def call(env)
14
+ return @app.call(env) unless $mock_server_options and
15
+ $mock_server_options[:routes] and
16
+ lazy_match $mock_server_options[:routes], env["PATH_INFO"]
17
+
18
+ @options.merge!($mock_server_options)
19
+
20
+ @request = Rack::Request.new(env)
21
+ @data = load_data
22
+
23
+ record = match_request
24
+
25
+ if record
26
+ $mock_server_options[:success_stack] ||= []
27
+ $mock_server_options[:success_stack] << @request.path
28
+
29
+ response = record[:response]
30
+ [response[:status], response[:headers], [response[:body]]]
31
+ else
32
+ $mock_server_options[:errors_stack] ||= []
33
+ error = { @request.path => "Couldn't match #{@request.request_method} #{@request.path}" }
34
+ $mock_server_options[:errors_stack] << error
35
+ [404, {}, ['RECORD NOT FOUND!']]
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def match_request
42
+ request = Hashie::Mash.new hashified_request
43
+
44
+ # Filter out data records by path and method
45
+ @data.select! { |record|
46
+ record[:request][:path] == request[:path] and record[:request][:method] == request[:method]
47
+ }
48
+
49
+ # Filter out matchers by path and method
50
+ matchers = @options[:matchers].select { |match|
51
+ request[:method].to_s.upcase == match[:method].to_s.upcase and request[:path] == match[:path]
52
+ }
53
+
54
+ # Match the request with a record by validating against the matcher if any.
55
+ @data.detect { |entry|
56
+ recorded_request = Hashie::Mash.new entry[:request]
57
+
58
+ matchers.detect { |matcher|
59
+ if matcher[:matcher]
60
+ matcher[:matcher].call(request, recorded_request) rescue false
61
+ else
62
+ true
63
+ end
64
+ }
65
+ }
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,40 @@
1
+ require 'mock_server/utils'
2
+
3
+ module MockServer
4
+ class Record
5
+ include MockServer::Utils
6
+
7
+ def initialize(app, options = {})
8
+ @options = options
9
+ @app = app
10
+ $mock_server_options ||= options
11
+ end
12
+
13
+ def call(env)
14
+ return @app.call(env) unless $mock_server_options and
15
+ $mock_server_options[:routes] and
16
+ lazy_match $mock_server_options[:routes], env["PATH_INFO"]
17
+
18
+ @options.merge!($mock_server_options)
19
+
20
+ @request = Rack::Request.new(env)
21
+ @data = load_data
22
+
23
+ @app.call(env).tap do |response|
24
+ record_response(response)
25
+ response
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def record_response(response)
32
+ request = hashified_request
33
+ @data.reject! { |record| record[:request] == request }
34
+
35
+ @data << { :request => request, :response => hashify_response(response) }
36
+ save_data(@data)
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,158 @@
1
+ # The purpose of the helper is to manpulate MockServer global var
2
+ # in an rspec way. $mock_server_options
3
+ #
4
+ #
5
+ # The basic:
6
+ #
7
+ # $mock_server_options = { :path => 'fixtures/records', :filename => 'uploads' }
8
+ #
9
+ #
10
+ # $mock_server_options tell you application which URL to do its magic for both,
11
+ # record and playback mode.
12
+ #
13
+ # Example:
14
+ #
15
+ # $mock_server_options[:routes] = [ '**/uploads.json', '**/uploads/*',
16
+ # '**/folders.json', '**/folders', '**/folders' ]
17
+ #
18
+ # $mock_server_options[:matchers] is an array of path, url, and matchers proc
19
+ #
20
+ # Example:
21
+ #
22
+ # $mock_server_options[:matchers] = [
23
+ # { :method => :get, :path => '/api/2/projects/1/uploads.json',
24
+ # :matcher => Proc.new { |request, recorded_request|
25
+ # request.query == recorded_request.query and
26
+ # request.query == 'count=0'
27
+ # }
28
+ # },
29
+ # { :method => :put, :path => '/api/2/uploads/20',
30
+ # :matcher => Proc.new { |request, recorded_request|
31
+ # request.query == recorded_request.query and
32
+ # request.query == 'count=0'
33
+ # }
34
+ # },
35
+ # { :method => :delete, :path => '/api/2/uploads/20' },
36
+ # { :method => :get, :path => '/api/2/projects/1/folders.json' },
37
+ # { :method => :post, :path => '/api/2/folders',
38
+ # :matcher => Proc.new { |request, recorded_request|
39
+ # request.body.project_id == recorded_request.body.project_id and
40
+ # request.body.project_id == 1 and
41
+ # recorded_request.body.name == recorded_request.body.name and
42
+ # recorded_request.body.name == 'Mockup'
43
+ # }
44
+ # },
45
+ # { :method => :put, :path => '/api/2/uploads/20',
46
+ # :matcher => Proc.new { |request, recorded_request|
47
+ # request.body.name == recorded_request.body.name
48
+ # }
49
+ # },
50
+ # { :method => :delete, :path => '/api/2/folders/3' }
51
+ # ]
52
+
53
+
54
+
55
+ module MockServer
56
+ module Spec
57
+ module Helpers
58
+
59
+ # Inspect
60
+ def mock_server_inspect
61
+ $mock_server_options.inspect
62
+ end
63
+
64
+ # Configuration
65
+ def mock_server_use_record(filename)
66
+ mock_server_config_set(:filename, filename)
67
+ end
68
+
69
+ def mock_server_set_fixture_path(path)
70
+ mock_server_config_set(:path, path)
71
+ end
72
+
73
+ # Active path config
74
+ def mock_server_enable_routes(*arguments)
75
+ $mock_server_options ||= {}
76
+ $mock_server_options[:routes] ||= []
77
+ $mock_server_options[:routes] << arguments
78
+ $mock_server_options[:routes].flatten!
79
+ end
80
+
81
+ def mock_server_disable_path(path)
82
+ return unless $mock_server_options and $mock_server_options[:routes]
83
+ $mock_server_options[:routes] = if path.is_an? Array
84
+ path unless path.empty?
85
+ else
86
+ $mock_server_options[:routes] - [path]
87
+ end
88
+ end
89
+
90
+ def mock_server_disable_all_routes!
91
+ $mock_server_options[:routes] = nil
92
+ end
93
+
94
+ # Matchers helpers
95
+ def mock_server_get(path, &block)
96
+ mock_server_request :get, path, block
97
+ end
98
+
99
+ def mock_server_post(path, &block)
100
+ mock_server_request :post, path, block
101
+ end
102
+
103
+ def mock_server_put(path, &block)
104
+ mock_server_request :put, path, block
105
+ end
106
+
107
+ def mock_server_delete(path, &block)
108
+ mock_server_request :delete, path, block
109
+ end
110
+
111
+ def mock_server_clear_matchers!
112
+ mock_server_config_set(:matchers, [])
113
+ end
114
+
115
+ # Errors / Success stack
116
+
117
+ def mock_server_errors_stack
118
+ $mock_server_options[:errors_stack] || []
119
+ end
120
+
121
+ def mock_server_errors_stack_clear!
122
+ $mock_server_options[:errors_stack] = []
123
+ end
124
+
125
+ def mock_server_success_stack
126
+ $mock_server_options[:success_stack] || []
127
+ end
128
+
129
+ def mock_server_success_stack_clear!
130
+ $mock_server_options[:success_stack] = []
131
+ end
132
+
133
+ def mock_server_response_stack_clear!
134
+ mock_server_success_stack_clear!
135
+ mock_server_errors_stack_clear!
136
+ end
137
+
138
+ protected
139
+ # Configuration
140
+ def mock_server_config_set(key, value)
141
+ $mock_server_options ||= {}
142
+ $mock_server_options[key] = value
143
+ end
144
+
145
+ # Matchers helpers
146
+ def mock_server_request(method, path, matcher)
147
+ add_mock_server_matcher({ :method => method, :path => path, :matcher => matcher })
148
+ end
149
+
150
+ def add_mock_server_matcher(matcher)
151
+ $mock_server_options ||= {}
152
+ $mock_server_options[:matchers] ||= []
153
+ $mock_server_options[:matchers] << matcher
154
+ end
155
+
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,65 @@
1
+ module MockServer
2
+ module Utils
3
+
4
+ private
5
+
6
+ def lazy_match(strings, path)
7
+ regexps = strings.map { |str|
8
+ escaped = Regexp.escape(str)
9
+ escaped.gsub!('\\*\\*', '[\w|\/]+')
10
+ escaped.gsub!('\\*', '[\w]+')
11
+ Regexp.new("^#{escaped}$")
12
+ }
13
+
14
+ regexps.any? { |regex| regex.match(path) }
15
+ end
16
+
17
+ def hashified_request
18
+ body = JSON.parse(@request.body.to_a.join) rescue ''
19
+ {
20
+ :method => @request.request_method,
21
+ :path => @request.path,
22
+ :query => @request.query_string,
23
+ :body => body
24
+ }
25
+ end
26
+
27
+ def hashify_response(response)
28
+ status = response[0]
29
+ headers = response[1]
30
+ body = response[2].body rescue ''
31
+
32
+ {
33
+ :method => @request.request_method,
34
+ :path => @request.path,
35
+ :status => status,
36
+ :headers => headers,
37
+ :body => body
38
+ }
39
+ end
40
+
41
+ # File utils
42
+ def records_path
43
+ File.join( @options[:path], @options[:filename] + '.yml' )
44
+ end
45
+
46
+ def load_data
47
+ FileUtils.mkdir_p(@options[:path]) unless File.exists? @options[:path]
48
+
49
+ data = YAML.load_file(records_path) rescue []
50
+
51
+ if data.is_a? Array
52
+ data
53
+ else
54
+ []
55
+ end
56
+ end
57
+
58
+ def save_data(data)
59
+ File.open(records_path, 'w') do |f|
60
+ YAML.dump(data, f)
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ # Don't load both at the same time anyway.
2
+ require 'mock_server/record'
3
+ require 'mock_server/playback'
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mock_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Charles Barbier
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-03 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: unixcharles@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - LICENSE
22
+ - lib/mock_server.rb
23
+ - lib/mock_server/record.rb
24
+ - lib/mock_server/playback.rb
25
+ - lib/mock_server/utils.rb
26
+ - lib/mock_server/spec/helpers.rb
27
+ homepage: http://www.github.com/unixcharles/mock_server
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 1.8.10
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: Mock you're entire application
51
+ test_files: []