mirage 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +50 -0
- data/README.md +93 -0
- data/bin/mirage +54 -0
- data/features/checking_for_requests.feature +74 -0
- data/features/clearing_requests_and_responses.feature +81 -0
- data/features/client/checking_for_requests.feature +20 -0
- data/features/client/clearing_responses.feature +75 -0
- data/features/client/getting_responses.feature +30 -0
- data/features/client/mirage_client.feature +36 -0
- data/features/client/peeking.feature +32 -0
- data/features/client/setting_responses.feature +91 -0
- data/features/client/snapshotting.feature +34 -0
- data/features/command_line_iterface.feature +39 -0
- data/features/default_responses.feature +91 -0
- data/features/file_hosting.feature +8 -0
- data/features/logging.feature +7 -0
- data/features/peeking_at_response.feature +24 -0
- data/features/resources/test.zip +0 -0
- data/features/response_templates.feature +45 -0
- data/features/root_responses.feature +47 -0
- data/features/setting_responses.feature +40 -0
- data/features/setting_responses_with_a_delay.feature +10 -0
- data/features/setting_responses_with_pattern_matching.feature +72 -0
- data/features/snapshotting.feature +25 -0
- data/features/step_definitions/my_steps.rb +127 -0
- data/features/support/env.rb +89 -0
- data/features/web_user_interface.feature +39 -0
- data/full_build.sh +100 -0
- data/lib/config.ru +5 -0
- data/lib/mirage.rb +14 -0
- data/lib/mirage/client.rb +140 -0
- data/lib/mirage/core.rb +206 -0
- data/lib/mirage/util.rb +40 -0
- data/lib/mirage/web.rb +65 -0
- data/lib/start_mirage.rb +15 -0
- data/lib/view/mirage/index.xhtml +23 -0
- data/mirage.gemspec +40 -0
- data/rakefile +50 -0
- metadata +199 -0
data/full_build.sh
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
blue='\033[34m'
|
3
|
+
red='\033[31m'
|
4
|
+
green='\033[32m'
|
5
|
+
yellow='\033[33m'
|
6
|
+
white='\033[37m'
|
7
|
+
bold='\033[1m'
|
8
|
+
reset='\033[0m'
|
9
|
+
|
10
|
+
println(){
|
11
|
+
echo -e "$1${reset}"
|
12
|
+
}
|
13
|
+
|
14
|
+
print(){
|
15
|
+
echo -ne "$1${reset}"
|
16
|
+
}
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
usage(){
|
21
|
+
println "${bold}Usage:\n"
|
22
|
+
println "./full_build.sh [ruby_version]\n"
|
23
|
+
println "When running with out a ruby version, the full build is run for ruby versions:"
|
24
|
+
println "1.8.6\n1.8.7\n1.9.1\n1.9.2\njruby\n"
|
25
|
+
println "Else specify a particular ruby version to run the build against\n"
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
if [ -f "$HOME/.rvm/scripts/rvm" ]
|
30
|
+
then
|
31
|
+
source "$HOME/.rvm/scripts/rvm"
|
32
|
+
elif [ -f "/usr/local/rvm/scripts/rvm" ]
|
33
|
+
then
|
34
|
+
source "/usr/local/rvm/scripts/rvm"
|
35
|
+
else
|
36
|
+
println "${bold}RVM Not found"
|
37
|
+
println "I looked in $HOME/.rvm/scripts/rvm and /usr/local/rvm/scripts/rvm"
|
38
|
+
println "RVM must be installed to run this script. It's great! find out more: here ${bold}http://rvm.beginrescueend.com/"
|
39
|
+
println "Until it is installed simply run the default rake target to test Mirage against your active version of Ruby and installed gems"
|
40
|
+
exit 1
|
41
|
+
fi
|
42
|
+
|
43
|
+
while getopts ":h" opt; do
|
44
|
+
case $opt in
|
45
|
+
h)
|
46
|
+
usage
|
47
|
+
exit 0
|
48
|
+
;;
|
49
|
+
\?)
|
50
|
+
println "Invalid option: -$OPTARG"
|
51
|
+
usage
|
52
|
+
exit 1
|
53
|
+
;;
|
54
|
+
esac
|
55
|
+
done
|
56
|
+
|
57
|
+
message=""
|
58
|
+
result=true
|
59
|
+
|
60
|
+
run_build_for_ruby( ){
|
61
|
+
println "${green}Running build for: $1"
|
62
|
+
|
63
|
+
ruby_list=`rvm list`
|
64
|
+
if [[ ${ruby_list} == *$1* ]]
|
65
|
+
then
|
66
|
+
rvm --create $1@mirage
|
67
|
+
rvm --force gemset empty
|
68
|
+
[ -f Gemfile.lock ] && rm Gemfile.lock
|
69
|
+
gem install bundler
|
70
|
+
bundle install
|
71
|
+
rake
|
72
|
+
|
73
|
+
if [ $? == 0 ]
|
74
|
+
then
|
75
|
+
message="${message}${blue}$1: ${green}pass\n"
|
76
|
+
else
|
77
|
+
message="${message}${blue}$1: ${red}fail\n"
|
78
|
+
result=false
|
79
|
+
fi
|
80
|
+
else
|
81
|
+
message="${message}${blue}$1: ${yellow}Not installed\n"
|
82
|
+
result=false
|
83
|
+
fi
|
84
|
+
}
|
85
|
+
|
86
|
+
if [ $1 ]
|
87
|
+
then
|
88
|
+
run_build_for_ruby $1
|
89
|
+
else
|
90
|
+
run_build_for_ruby 'ruby-1.8.6'
|
91
|
+
run_build_for_ruby 'ruby-1.8.7'
|
92
|
+
run_build_for_ruby 'ruby-1.9.1'
|
93
|
+
run_build_for_ruby 'ruby-1.9.2'
|
94
|
+
run_build_for_ruby 'jruby'
|
95
|
+
fi
|
96
|
+
|
97
|
+
println "\n\n${message}"
|
98
|
+
print "${white}Result: "
|
99
|
+
[ ${result} == true ] && println "${green}Pass\n" || println "${red}Fail\n"
|
100
|
+
|
data/lib/config.ru
ADDED
data/lib/mirage.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'mechanize'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'mirage/web'
|
5
|
+
|
6
|
+
module Mirage
|
7
|
+
|
8
|
+
class MirageError < ::Exception
|
9
|
+
attr_reader :code
|
10
|
+
|
11
|
+
def initialize message, code
|
12
|
+
super message
|
13
|
+
@code = message, code
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InternalServerException < MirageError;
|
18
|
+
end
|
19
|
+
|
20
|
+
class ResponseNotFound < MirageError;
|
21
|
+
end
|
22
|
+
|
23
|
+
class Client
|
24
|
+
include ::Mirage::Web
|
25
|
+
|
26
|
+
# Creates an instance of the MIrage client that can be used to interact with the Mirage Server
|
27
|
+
#
|
28
|
+
# Client.new => a client that is configured to connect to Mirage on http://localhost:7001/mirage (the default settings for Mirage)
|
29
|
+
# Client.new(URL) => a client that is configured to connect to an instance of Mirage running on the specified url.
|
30
|
+
def initialize url="http://localhost:7001/mirage"
|
31
|
+
@url = url
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get a response at the given endpoint
|
35
|
+
# Mirage::Client.get(endpoint) => response as a string
|
36
|
+
# If a response is not found a ResponseNotFound exception is thrown
|
37
|
+
#
|
38
|
+
# Examples:
|
39
|
+
# Getting a response, passing request parameters
|
40
|
+
# Mirage::Client.new.get('greeting', :param1 => 'value1', param2=>'value2')
|
41
|
+
#
|
42
|
+
# Getting a response, passing a content in the body of the request
|
43
|
+
# Mirage::Client.new.get('greeting', :body => 'content')
|
44
|
+
|
45
|
+
def get endpoint, params={}
|
46
|
+
response(http_get("#{@url}/get/#{endpoint}", params))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set a text or file based response, to be hosted at a given end point optionally with a given pattern and delay
|
50
|
+
# Client.set(endpoint, params) => unique id that can be used to call back to the server
|
51
|
+
#
|
52
|
+
# Examples:
|
53
|
+
# Client.set('greeting', :response => 'hello':)
|
54
|
+
# Client.set('greeting', :response => 'hello', :pattern => 'regex or plain text':)
|
55
|
+
# Client.set('greeting', :response => 'hello', :delay => 5) # number of seconds
|
56
|
+
def set endpoint, params
|
57
|
+
response(http_post("#{@url}/set/#{endpoint}", params))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Use to look at what a response contains without actually triggering it.
|
61
|
+
# Client.peek(response_id) => response held on the server as a String
|
62
|
+
def peek response_id
|
63
|
+
response(http_get("#{@url}/peek/#{response_id}"))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Clear Content from Mirage
|
67
|
+
#
|
68
|
+
# If a response id is not valid, a ResponseNotFound exception will be thrown
|
69
|
+
#
|
70
|
+
# Examples:
|
71
|
+
# Client.new.clear # clear all responses and associated requests
|
72
|
+
# Client.new.clear(response_id) # Clear the response and tracked request for a given response id
|
73
|
+
# Client.new.clear(:requests) # Clear all tracked request information
|
74
|
+
# Client.new.clear(:request => response_id) # Clear the tracked request for a given response id
|
75
|
+
def clear thing=nil
|
76
|
+
case thing
|
77
|
+
when NilClass then
|
78
|
+
http_get("#{@url}/clear")
|
79
|
+
when Fixnum then
|
80
|
+
http_get("#{@url}/clear/#{thing}")
|
81
|
+
when :requests then
|
82
|
+
http_get("#{@url}/clear/requests")
|
83
|
+
when Hash then
|
84
|
+
case thing.keys.first
|
85
|
+
when :request then
|
86
|
+
http_get("#{@url}/clear/request/#{thing.values.first}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Retrieve the last request that triggered a response to be returned. If the request contained content in its body, this is returned. If the
|
93
|
+
# request did not have any content in its body then what ever was in the request query string is returned instead
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
# Client.new.check(response_id) => Tracked request as a String
|
97
|
+
def check response_id
|
98
|
+
response(http_get("#{@url}/check/#{response_id}"))
|
99
|
+
end
|
100
|
+
|
101
|
+
# Snapshot the state of the Mirage server so that it can be rolled back to that exact state at a later time.
|
102
|
+
def snapshot
|
103
|
+
http_post("#{@url}/snapshot").code == 200
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# Roll the state of Mirage back to that stored in the snapshot
|
108
|
+
# If there is no snapshot to rollback to, nothing happens
|
109
|
+
def rollback
|
110
|
+
http_post("#{@url}/rollback").code == 200
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Check to see if Mirage is up and running
|
115
|
+
def running?
|
116
|
+
!http_get(@url).is_a?(Errno::ECONNREFUSED)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Clear down the Mirage Server and load any defaults that are in Mirages default responses directory.
|
120
|
+
def load_defaults
|
121
|
+
response(http_post("#{@url}/load_defaults"))
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def response response
|
126
|
+
return Mirage::Web::FileResponse.new(response) if response.instance_of?(Mechanize::File)
|
127
|
+
case response.code
|
128
|
+
when 500 then
|
129
|
+
raise ::Mirage::InternalServerException.new(response.page.body, response.code)
|
130
|
+
when 404 then
|
131
|
+
raise ::Mirage::ResponseNotFound.new(response.page.body, response.code)
|
132
|
+
else
|
133
|
+
response.body
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
end
|
data/lib/mirage/core.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'ramaze'
|
2
|
+
require 'ramaze/helper/send_file'
|
3
|
+
|
4
|
+
class Object
|
5
|
+
def deep_clone
|
6
|
+
Marshal.load(Marshal.dump(self))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Mirage
|
11
|
+
|
12
|
+
class MockResponse
|
13
|
+
@@id_count = 0
|
14
|
+
attr_reader :response_id, :delay, :name, :pattern
|
15
|
+
attr_accessor :response_id
|
16
|
+
|
17
|
+
def initialize name, value, pattern=nil, delay=0, root_response=false
|
18
|
+
@name, @value, @pattern, @response_id, @delay, @root_response = name, value, pattern, @@id_count+=1, delay, root_response
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.reset_count
|
22
|
+
@@id_count = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def root_response?
|
26
|
+
@root_response
|
27
|
+
end
|
28
|
+
|
29
|
+
def file?
|
30
|
+
!@value.is_a?(String)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def value(body='', request_parameters={}, query_string='')
|
35
|
+
return @value if file?
|
36
|
+
|
37
|
+
value = @value
|
38
|
+
value.scan(/\$\{(.*)?\}/).flatten.each do |pattern|
|
39
|
+
|
40
|
+
if (parameter_match = request_parameters[pattern])
|
41
|
+
value = value.gsub("${#{pattern}}", parameter_match)
|
42
|
+
end
|
43
|
+
|
44
|
+
[body, query_string].each do |string|
|
45
|
+
if (string_match = find_match(string, pattern))
|
46
|
+
value = value.gsub("${#{pattern}}", string_match)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
value
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def find_match(string, regex)
|
56
|
+
string.scan(/#{regex}/).flatten.first
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
class MirageServer < Ramaze::Controller
|
62
|
+
include Ramaze::Helper::SendFile
|
63
|
+
map '/mirage'
|
64
|
+
RESPONSES, REQUESTS, SNAPSHOT= {}, {}, {}
|
65
|
+
|
66
|
+
def index
|
67
|
+
@responses = {}
|
68
|
+
|
69
|
+
RESPONSES.each do |name, responses|
|
70
|
+
@responses[name]=responses.default unless responses.default.nil?
|
71
|
+
|
72
|
+
responses.each do |pattern, response|
|
73
|
+
@responses["#{name}#{'/*' if response.root_response?}: #{pattern}"] = response
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def peek response_id
|
79
|
+
peeked_response = nil
|
80
|
+
RESPONSES.values.each do |responses|
|
81
|
+
peeked_response = responses[:default] if responses[:default] && responses[:default].response_id == response_id.to_i
|
82
|
+
peeked_response = responses.values.find { |response| response.response_id == response_id.to_i } if peeked_response.nil?
|
83
|
+
break unless peeked_response.nil?
|
84
|
+
end
|
85
|
+
respond("Can not peek reponse, id:#{response_id} does not exist}", 404) unless peeked_response
|
86
|
+
send_response(peeked_response)
|
87
|
+
end
|
88
|
+
|
89
|
+
def set *args
|
90
|
+
delay = (request['delay']||0)
|
91
|
+
pattern = request['pattern'] ? /#{request['pattern']}/ : :default
|
92
|
+
name = args.join('/')
|
93
|
+
is_root_response = request['root_response'] == 'true'
|
94
|
+
|
95
|
+
response = MockResponse.new(name, (request[:file]||response_value), pattern, delay.to_f, is_root_response)
|
96
|
+
|
97
|
+
stored_responses = RESPONSES[name]||={}
|
98
|
+
|
99
|
+
old_response = stored_responses.delete(pattern)
|
100
|
+
stored_responses[pattern] = response
|
101
|
+
|
102
|
+
# Right not an the main id count goes up by one even if the id is not used because the old id is reused from another response
|
103
|
+
response.response_id = old_response.response_id if old_response
|
104
|
+
response.response_id
|
105
|
+
end
|
106
|
+
|
107
|
+
def get *args
|
108
|
+
body, query_string = Rack::Utils.unescape(request.body.read.to_s), request.env['QUERY_STRING']
|
109
|
+
name = args.join('/')
|
110
|
+
stored_responses = RESPONSES[name]
|
111
|
+
|
112
|
+
if stored_responses
|
113
|
+
record = find_response(body, query_string, stored_responses)
|
114
|
+
else
|
115
|
+
root_responses, record = find_root_responses(name), nil
|
116
|
+
|
117
|
+
until record || root_responses.empty?
|
118
|
+
record = find_response(body, query_string, root_responses.delete_at(0))
|
119
|
+
record = record.root_response? ? record : nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
respond('Response not found', 404) unless record
|
124
|
+
REQUESTS[record.response_id] = body.empty? ? query_string : body
|
125
|
+
|
126
|
+
sleep record.delay
|
127
|
+
send_response(record, body, request, query_string)
|
128
|
+
end
|
129
|
+
|
130
|
+
def clear datatype=nil, response_id=nil
|
131
|
+
response_id = response_id.to_i
|
132
|
+
case datatype
|
133
|
+
when 'requests' then
|
134
|
+
REQUESTS.clear
|
135
|
+
when 'responses' then
|
136
|
+
RESPONSES.clear and REQUESTS.clear and MockResponse.reset_count
|
137
|
+
when /\d+/ then
|
138
|
+
response_id = datatype.to_i
|
139
|
+
delete_response(response_id)
|
140
|
+
REQUESTS.delete(response_id)
|
141
|
+
when 'request'
|
142
|
+
REQUESTS.delete(response_id)
|
143
|
+
when nil
|
144
|
+
[REQUESTS, RESPONSES].each { |map| map.clear }
|
145
|
+
MockResponse.reset_count
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def check id
|
150
|
+
REQUESTS[id.to_i] || respond("Nothing stored for: #{id}", 404)
|
151
|
+
end
|
152
|
+
|
153
|
+
def snapshot
|
154
|
+
SNAPSHOT.clear and SNAPSHOT.replace(RESPONSES.deep_clone)
|
155
|
+
end
|
156
|
+
|
157
|
+
def rollback
|
158
|
+
RESPONSES.clear and RESPONSES.replace(SNAPSHOT.deep_clone)
|
159
|
+
end
|
160
|
+
|
161
|
+
def load_defaults
|
162
|
+
clear
|
163
|
+
Dir["#{DEFAULT_RESPONSES_DIR}/**/*.rb"].each do |default|
|
164
|
+
begin
|
165
|
+
load default
|
166
|
+
rescue Exception
|
167
|
+
respond("Unable to load default responses from: #{default}", 500)
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
def find_response(body, query_string, stored_responses)
|
175
|
+
pattern_match = stored_responses.keys.find_all { |pattern| pattern != :default }.find { |pattern| body =~ pattern || query_string =~ pattern }
|
176
|
+
record = pattern_match ? stored_responses[pattern_match] : stored_responses[:default]
|
177
|
+
record
|
178
|
+
end
|
179
|
+
|
180
|
+
def response_value
|
181
|
+
return request['response'] unless request['response'].nil?
|
182
|
+
respond('response or file parameter required', 500)
|
183
|
+
end
|
184
|
+
|
185
|
+
def find_root_responses(name)
|
186
|
+
matches = RESPONSES.keys.find_all { |key| name.index(key) == 0 }.sort { |a, b| b.length <=> a.length }
|
187
|
+
matches.collect { |key| RESPONSES[key] }
|
188
|
+
end
|
189
|
+
|
190
|
+
def delete_response(response_id)
|
191
|
+
RESPONSES.each do |name, response_set|
|
192
|
+
response_set.each { |key, response| response_set.delete(key) if response.response_id == response_id }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def send_response(response, body='', request={}, query_string='')
|
197
|
+
if response.file?
|
198
|
+
tempfile, filename, type = response.value.values_at(:tempfile, :filename, :type)
|
199
|
+
send_file(tempfile.path, type, "Content-Disposition: attachment; filename=#{filename}")
|
200
|
+
else
|
201
|
+
response.value(body, request, query_string)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|