mirage 0.1.2
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/.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
|