mock_server 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.
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: []