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.
Files changed (41) hide show
  1. data/.rvmrc +1 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +50 -0
  4. data/README.md +93 -0
  5. data/bin/mirage +54 -0
  6. data/features/checking_for_requests.feature +74 -0
  7. data/features/clearing_requests_and_responses.feature +81 -0
  8. data/features/client/checking_for_requests.feature +20 -0
  9. data/features/client/clearing_responses.feature +75 -0
  10. data/features/client/getting_responses.feature +30 -0
  11. data/features/client/mirage_client.feature +36 -0
  12. data/features/client/peeking.feature +32 -0
  13. data/features/client/setting_responses.feature +91 -0
  14. data/features/client/snapshotting.feature +34 -0
  15. data/features/command_line_iterface.feature +39 -0
  16. data/features/default_responses.feature +91 -0
  17. data/features/file_hosting.feature +8 -0
  18. data/features/logging.feature +7 -0
  19. data/features/peeking_at_response.feature +24 -0
  20. data/features/resources/test.zip +0 -0
  21. data/features/response_templates.feature +45 -0
  22. data/features/root_responses.feature +47 -0
  23. data/features/setting_responses.feature +40 -0
  24. data/features/setting_responses_with_a_delay.feature +10 -0
  25. data/features/setting_responses_with_pattern_matching.feature +72 -0
  26. data/features/snapshotting.feature +25 -0
  27. data/features/step_definitions/my_steps.rb +127 -0
  28. data/features/support/env.rb +89 -0
  29. data/features/web_user_interface.feature +39 -0
  30. data/full_build.sh +100 -0
  31. data/lib/config.ru +5 -0
  32. data/lib/mirage.rb +14 -0
  33. data/lib/mirage/client.rb +140 -0
  34. data/lib/mirage/core.rb +206 -0
  35. data/lib/mirage/util.rb +40 -0
  36. data/lib/mirage/web.rb +65 -0
  37. data/lib/start_mirage.rb +15 -0
  38. data/lib/view/mirage/index.xhtml +23 -0
  39. data/mirage.gemspec +40 -0
  40. data/rakefile +50 -0
  41. metadata +199 -0
@@ -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
+
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require "#{::File.expand_path("#{::File.dirname(__FILE__)}/mirage/core")}"
3
+ Ramaze.start(:root => __DIR__, :started => true)
4
+ run Ramaze
5
+
@@ -0,0 +1,14 @@
1
+ require 'mirage/web'
2
+ require 'mirage/util'
3
+ require 'mirage/core'
4
+ require 'mirage/client'
5
+
6
+ module Mirage
7
+ def self.default
8
+ yield @@client
9
+ end
10
+
11
+ def self.client= client
12
+ @@client = client
13
+ end
14
+ end
@@ -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
@@ -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