wycats-merb-core 0.9.8
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/CHANGELOG +992 -0
- data/CONTRIBUTORS +94 -0
- data/LICENSE +20 -0
- data/PUBLIC_CHANGELOG +142 -0
- data/README +21 -0
- data/Rakefile +458 -0
- data/TODO +0 -0
- data/bin/merb +11 -0
- data/bin/merb-specs +5 -0
- data/lib/merb-core.rb +598 -0
- data/lib/merb-core/autoload.rb +31 -0
- data/lib/merb-core/bootloader.rb +717 -0
- data/lib/merb-core/config.rb +305 -0
- data/lib/merb-core/constants.rb +45 -0
- data/lib/merb-core/controller/abstract_controller.rb +568 -0
- data/lib/merb-core/controller/exceptions.rb +315 -0
- data/lib/merb-core/controller/merb_controller.rb +256 -0
- data/lib/merb-core/controller/mime.rb +107 -0
- data/lib/merb-core/controller/mixins/authentication.rb +123 -0
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/controller.rb +319 -0
- data/lib/merb-core/controller/mixins/render.rb +513 -0
- data/lib/merb-core/controller/mixins/responder.rb +469 -0
- data/lib/merb-core/controller/template.rb +254 -0
- data/lib/merb-core/core_ext.rb +9 -0
- data/lib/merb-core/core_ext/hash.rb +7 -0
- data/lib/merb-core/core_ext/kernel.rb +340 -0
- data/lib/merb-core/dispatch/cookies.rb +130 -0
- data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
- data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
- data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
- data/lib/merb-core/dispatch/dispatcher.rb +176 -0
- data/lib/merb-core/dispatch/request.rb +729 -0
- data/lib/merb-core/dispatch/router.rb +151 -0
- data/lib/merb-core/dispatch/router/behavior.rb +566 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/resources.rb +191 -0
- data/lib/merb-core/dispatch/router/route.rb +511 -0
- data/lib/merb-core/dispatch/session.rb +222 -0
- data/lib/merb-core/dispatch/session/container.rb +74 -0
- data/lib/merb-core/dispatch/session/cookie.rb +173 -0
- data/lib/merb-core/dispatch/session/memcached.rb +68 -0
- data/lib/merb-core/dispatch/session/memory.rb +99 -0
- data/lib/merb-core/dispatch/session/store_container.rb +150 -0
- data/lib/merb-core/dispatch/worker.rb +28 -0
- data/lib/merb-core/gem_ext/erubis.rb +77 -0
- data/lib/merb-core/logger.rb +203 -0
- data/lib/merb-core/plugins.rb +67 -0
- data/lib/merb-core/rack.rb +25 -0
- data/lib/merb-core/rack/adapter.rb +44 -0
- data/lib/merb-core/rack/adapter/ebb.rb +25 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
- data/lib/merb-core/rack/adapter/irb.rb +118 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/runner.rb +28 -0
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/thin.rb +39 -0
- data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
- data/lib/merb-core/rack/adapter/webrick.rb +36 -0
- data/lib/merb-core/rack/application.rb +32 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/rack/middleware.rb +20 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
- data/lib/merb-core/rack/middleware/content_length.rb +18 -0
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
- data/lib/merb-core/rack/middleware/profiler.rb +19 -0
- data/lib/merb-core/rack/middleware/static.rb +45 -0
- data/lib/merb-core/rack/middleware/tracer.rb +20 -0
- data/lib/merb-core/server.rb +284 -0
- data/lib/merb-core/tasks/audit.rake +68 -0
- data/lib/merb-core/tasks/gem_management.rb +229 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
- data/lib/merb-core/tasks/stats.rake +71 -0
- data/lib/merb-core/test.rb +11 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +393 -0
- data/lib/merb-core/test/helpers/route_helper.rb +39 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
- data/lib/merb-core/test/run_specs.rb +49 -0
- data/lib/merb-core/test/tasks/spectasks.rb +68 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/test/test_ext/string.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +342 -0
- data/lib/merb-core/version.rb +3 -0
- metadata +253 -0
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) / '*.rake'].each { |ext| load ext }
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'gem_management')
|
2
|
+
|
3
|
+
module Merb
|
4
|
+
module RakeHelper
|
5
|
+
|
6
|
+
extend GemManagement
|
7
|
+
|
8
|
+
def self.install(name, options = {})
|
9
|
+
defaults = { :cache => false }
|
10
|
+
defaults[:install_dir] = ENV['GEM_DIR'] if ENV['GEM_DIR']
|
11
|
+
opts = defaults.merge(options)
|
12
|
+
install_gem_from_src(Dir.pwd, opts)
|
13
|
+
ensure_wrapper(opts[:install_dir] || Gem.default_dir, name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.install_package(pkg, options = {})
|
17
|
+
defaults = { :cache => false }
|
18
|
+
defaults[:install_dir] = ENV['GEM_DIR'] if ENV['GEM_DIR']
|
19
|
+
opts = defaults.merge(options)
|
20
|
+
install_gem(pkg, opts)
|
21
|
+
name = File.basename(pkg, '.gem')[/^(.*?)-([\d\.]+)$/, 1]
|
22
|
+
ensure_wrapper(opts[:install_dir] || Gem.default_dir, name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.uninstall(name, options = {})
|
26
|
+
defaults = { :ignore => true, :executables => true }
|
27
|
+
defaults[:install_dir] = ENV['GEM_DIR'] if ENV['GEM_DIR']
|
28
|
+
uninstall_gem(name, defaults.merge(options))
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def self.ensure_wrapper(gemdir, name)
|
34
|
+
# See if there's a local bin dir - one directory up from ./gems
|
35
|
+
bindir = File.expand_path(File.join(gemdir, '..', 'bin'))
|
36
|
+
# Fall back to system wide bindir - usually needs sudo permissions
|
37
|
+
bindir = Gem.bindir unless File.directory?(bindir)
|
38
|
+
ensure_bin_wrapper_for(gemdir, bindir, name)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sudo
|
45
|
+
ENV['MERB_SUDO'] ||= "sudo"
|
46
|
+
sudo = windows? ? "" : ENV['MERB_SUDO']
|
47
|
+
end
|
48
|
+
|
49
|
+
def windows?
|
50
|
+
(PLATFORM =~ /win32|cygwin/) rescue nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def install_home
|
54
|
+
ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : ""
|
55
|
+
end
|
56
|
+
|
57
|
+
def install_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
58
|
+
options << " -i #{ENV['GEM_DIR']}" if ENV['GEM_DIR']
|
59
|
+
%{#{sudo} #{Gem.ruby} -S gem install #{install_home} --local pkg/#{gem_name}-#{gem_version}.gem #{options}}
|
60
|
+
end
|
61
|
+
|
62
|
+
def dev_install_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
63
|
+
options << ' --development'
|
64
|
+
install_command(gem_name, gem_version, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def jinstall_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
68
|
+
options << " -i #{ENV['GEM_DIR']}" if ENV['GEM_DIR']
|
69
|
+
%{#{sudo} jruby -S gem install #{install_home} --local pkg/#{gem_name}-#{gem_version}.gem #{options}}
|
70
|
+
end
|
71
|
+
|
72
|
+
def dev_jinstall_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
73
|
+
options << ' --development'
|
74
|
+
jinstall_command(gem_name, gem_version, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def uninstall_command(gem_name, options = '')
|
78
|
+
options << " -i #{ENV['GEM_DIR']}" if ENV['GEM_DIR']
|
79
|
+
%{#{sudo} #{Gem.ruby} -S gem uninstall #{gem_name} #{options}}
|
80
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
def show_line(name, stats, color = nil)
|
2
|
+
ce = color ? "\033[0m" : ""
|
3
|
+
puts "| #{color}#{name.to_s.capitalize.ljust(20)}#{ce} " +
|
4
|
+
"| #{color}#{stats[:lines].to_s.rjust(7)}#{ce} " +
|
5
|
+
"| #{color}#{stats[:loc].to_s.rjust(7)}#{ce} " +
|
6
|
+
"| #{color}#{stats[:classes].to_s.rjust(7)}#{ce} " +
|
7
|
+
"| #{color}#{stats[:modules].to_s.rjust(7)}#{ce} " +
|
8
|
+
"| #{color}#{stats[:methods].to_s.rjust(7)}#{ce} |"
|
9
|
+
puts separator
|
10
|
+
end
|
11
|
+
|
12
|
+
def separator
|
13
|
+
'+----------------------+---------+---------+---------+---------+---------+'
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_dir(dir)
|
17
|
+
Dir.foreach(dir) do |file_name|
|
18
|
+
if File.stat(dir / file_name).directory? and (/^\./ !~ file_name)
|
19
|
+
check_dir(dir / file_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
if file_name =~ /.*\.rb$/
|
23
|
+
File.open(dir / file_name).each_line do |line|
|
24
|
+
@stats[:lines] += 1
|
25
|
+
@stats[:loc] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
|
26
|
+
@stats[:classes] += 1 if line =~ /class [A-Z]/
|
27
|
+
@stats[:modules] += 1 if line =~ /module [A-Z]/
|
28
|
+
@stats[:methods] += 1 if line =~ /def [a-z]/
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Lines of code statistics"
|
35
|
+
task :stats do
|
36
|
+
STATISTICS_DIRS = {
|
37
|
+
:controllers => 'app/controllers',
|
38
|
+
:helpers => 'app/helpers',
|
39
|
+
:models => 'app/models',
|
40
|
+
:lib => 'lib',
|
41
|
+
:spec => 'spec'
|
42
|
+
}
|
43
|
+
EMPTY_STATS = { :lines => 0, :loc => 0, :classes => 0, :modules => 0, :methods => 0 }
|
44
|
+
|
45
|
+
@all = {}
|
46
|
+
total = EMPTY_STATS.clone
|
47
|
+
ce = "\033[0m"
|
48
|
+
cb = "\033[35m"
|
49
|
+
cg = "\033[4;32m"
|
50
|
+
cr = "\033[31m"
|
51
|
+
|
52
|
+
puts separator
|
53
|
+
puts "| #{cg}Name#{ce} | #{cg}Lines#{ce} | #{cg}LOC#{ce} | #{cg}Classes#{ce} | #{cg}Modules#{ce} | #{cg}Methods#{ce} |"
|
54
|
+
puts separator
|
55
|
+
|
56
|
+
STATISTICS_DIRS.each_pair do |name, dir|
|
57
|
+
@stats = EMPTY_STATS.clone
|
58
|
+
check_dir(dir)
|
59
|
+
@all[name] = @stats
|
60
|
+
show_line(name, @stats)
|
61
|
+
@stats.each_pair { |type, count| total[type] += count }
|
62
|
+
end
|
63
|
+
|
64
|
+
show_line('Total', total, cr)
|
65
|
+
|
66
|
+
code_loc = [:controllers, :helpers, :models].inject(0) { |sum, e| sum += @all[e][:loc] }
|
67
|
+
test_loc = @all[:spec][:loc]
|
68
|
+
|
69
|
+
puts " Code LOC: #{cb}#{code_loc}#{ce} Test LOC: #{cb}#{test_loc}#{ce} Test to code radio: #{cb}1:%0.2f#{ce}" % (test_loc.to_f / code_loc.to_f)
|
70
|
+
puts
|
71
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "hpricot"
|
2
|
+
|
3
|
+
require 'merb-core/test/test_ext/hpricot'
|
4
|
+
require 'merb-core/test/test_ext/object'
|
5
|
+
require 'merb-core/test/test_ext/string'
|
6
|
+
|
7
|
+
module Merb; module Test; end; end
|
8
|
+
|
9
|
+
require 'merb-core/test/helpers'
|
10
|
+
|
11
|
+
require 'merb-core/test/matchers'
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# This is a place holder to allow plugins etc a place to include
|
2
|
+
# testing helpers
|
3
|
+
module Merb::Test::Helpers; end
|
4
|
+
|
5
|
+
require "merb-core/test/helpers/request_helper"
|
6
|
+
require "merb-core/test/helpers/multipart_request_helper"
|
7
|
+
require "merb-core/test/helpers/controller_helper"
|
8
|
+
require "merb-core/test/helpers/route_helper"
|
9
|
+
require "merb-core/test/helpers/view_helper"
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Merb::Test::MultipartRequestHelper
|
2
|
+
require 'rubygems'
|
3
|
+
require 'mime/types'
|
4
|
+
|
5
|
+
class Param
|
6
|
+
attr_accessor :key, :value
|
7
|
+
|
8
|
+
# ==== Parameters
|
9
|
+
# key<~to_s>:: The parameter key.
|
10
|
+
# value<~to_s>:: The parameter value.
|
11
|
+
def initialize(key, value)
|
12
|
+
@key = key
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
# ==== Returns
|
17
|
+
# String:: The parameter in a form suitable for a multipart request.
|
18
|
+
def to_multipart
|
19
|
+
return %(Content-Disposition: form-data; name="#{key}"\r\n\r\n#{value}\r\n)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class FileParam
|
24
|
+
attr_accessor :key, :filename, :content
|
25
|
+
|
26
|
+
# ==== Parameters
|
27
|
+
# key<~to_s>:: The parameter key.
|
28
|
+
# filename<~to_s>:: Name of the file for this parameter.
|
29
|
+
# content<~to_s>:: Content of the file for this parameter.
|
30
|
+
def initialize(key, filename, content)
|
31
|
+
@key = key
|
32
|
+
@filename = filename
|
33
|
+
@content = content
|
34
|
+
end
|
35
|
+
|
36
|
+
# ==== Returns
|
37
|
+
# String::
|
38
|
+
# The file parameter in a form suitable for a multipart request.
|
39
|
+
def to_multipart
|
40
|
+
return %(Content-Disposition: form-data; name="#{key}"; filename="#{filename}"\r\n) + "Content-Type: #{MIME::Types.type_for(@filename)}\r\n\r\n" + content + "\r\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Post
|
45
|
+
BOUNDARY = '----------0xKhTmLbOuNdArY'
|
46
|
+
CONTENT_TYPE = "multipart/form-data, boundary=" + BOUNDARY
|
47
|
+
|
48
|
+
# ==== Parameters
|
49
|
+
# params<Hash>:: Optional params for the controller.
|
50
|
+
def initialize(params = {})
|
51
|
+
@multipart_params = []
|
52
|
+
push_params(params)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Saves the params in an array of multipart params as Param and
|
56
|
+
# FileParam objects.
|
57
|
+
#
|
58
|
+
# ==== Parameters
|
59
|
+
# params<Hash>:: The params to add to the multipart params.
|
60
|
+
# prefix<~to_s>:: An optional prefix for the request string keys.
|
61
|
+
def push_params(params, prefix = nil)
|
62
|
+
params.sort_by {|k| k.to_s}.each do |key, value|
|
63
|
+
param_key = prefix.nil? ? key : "#{prefix}[#{key}]"
|
64
|
+
if value.respond_to?(:read)
|
65
|
+
@multipart_params << FileParam.new(param_key, value.path, value.read)
|
66
|
+
else
|
67
|
+
if value.is_a?(Hash) || value.is_a?(Mash)
|
68
|
+
value.keys.each do |k|
|
69
|
+
push_params(value, param_key)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
@multipart_params << Param.new(param_key, value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# ==== Returns
|
79
|
+
# Array[String, String]:: The query and the content type.
|
80
|
+
def to_multipart
|
81
|
+
query = @multipart_params.collect { |param| "--" + BOUNDARY + "\r\n" + param.to_multipart }.join("") + "--" + BOUNDARY + "--"
|
82
|
+
return query, CONTENT_TYPE
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Similar to dispatch_to but allows for sending files inside params.
|
87
|
+
#
|
88
|
+
# ==== Paramters
|
89
|
+
# controller_klass<Controller>::
|
90
|
+
# The controller class object that the action should be dispatched to.
|
91
|
+
# action<Symbol>:: The action name, as a symbol.
|
92
|
+
# params<Hash>::
|
93
|
+
# An optional hash that will end up as params in the controller instance.
|
94
|
+
# env<Hash>::
|
95
|
+
# An optional hash that is passed to the fake request. Any request options
|
96
|
+
# should go here (see +fake_request+).
|
97
|
+
# &blk:: The block is executed in the context of the controller.
|
98
|
+
#
|
99
|
+
# ==== Example
|
100
|
+
# dispatch_multipart_to(MyController, :create, :my_file => @a_file ) do |controller|
|
101
|
+
# controller.stub!(:current_user).and_return(@user)
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# ==== Notes
|
105
|
+
# Set your option to contain a file object to simulate file uploads.
|
106
|
+
#
|
107
|
+
# Does not use routes.
|
108
|
+
#---
|
109
|
+
# @public
|
110
|
+
def dispatch_multipart_to(controller_klass, action, params = {}, env = {}, &blk)
|
111
|
+
request = multipart_fake_request(env, params)
|
112
|
+
dispatch_request(request, controller_klass, action, &blk)
|
113
|
+
end
|
114
|
+
|
115
|
+
# An HTTP POST request that operates through the router and uses multipart
|
116
|
+
# parameters.
|
117
|
+
#
|
118
|
+
# ==== Parameters
|
119
|
+
# path<String>:: The path that should go to the router as the request uri.
|
120
|
+
# params<Hash>::
|
121
|
+
# An optional hash that will end up as params in the controller instance.
|
122
|
+
# env<Hash>::
|
123
|
+
# An optional hash that is passed to the fake request. Any request options
|
124
|
+
# should go here (see +fake_request+).
|
125
|
+
# block<Proc>:: The block is executed in the context of the controller.
|
126
|
+
#
|
127
|
+
# ==== Notes
|
128
|
+
# To include an uploaded file, put a file object as a value in params.
|
129
|
+
def multipart_post(path, params = {}, env = {}, &block)
|
130
|
+
env[:request_method] = "POST"
|
131
|
+
env[:test_with_multipart] = true
|
132
|
+
request(path, params, env, &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
# An HTTP PUT request that operates through the router and uses multipart
|
136
|
+
# parameters.
|
137
|
+
#
|
138
|
+
# ==== Parameters
|
139
|
+
# path<String>:: The path that should go to the router as the request uri.
|
140
|
+
# params<Hash>::
|
141
|
+
# An optional hash that will end up as params in the controller instance.
|
142
|
+
# env<Hash>::
|
143
|
+
# An optional hash that is passed to the fake request. Any request options
|
144
|
+
# should go here (see +fake_request+).
|
145
|
+
# block<Proc>:: The block is executed in the context of the controller.
|
146
|
+
#
|
147
|
+
# ==== Notes
|
148
|
+
# To include an uplaoded file, put a file object as a value in params.
|
149
|
+
def multipart_put(path, params = {}, env = {}, &block)
|
150
|
+
env[:request_method] = "PUT"
|
151
|
+
env[:test_with_multipart] = true
|
152
|
+
request(path, params, env, &block)
|
153
|
+
end
|
154
|
+
|
155
|
+
# ==== Parameters
|
156
|
+
# env<Hash>::
|
157
|
+
# An optional hash that is passed to the fake request. Any request options
|
158
|
+
# should go here (see +fake_request+).
|
159
|
+
# params<Hash>::
|
160
|
+
# An optional hash that will end up as params in the controller instance.
|
161
|
+
#
|
162
|
+
# ==== Returns
|
163
|
+
# FakeRequest::
|
164
|
+
# A multipart Request object that is built based on the parameters.
|
165
|
+
def multipart_fake_request(env = {}, params = {})
|
166
|
+
if params.empty?
|
167
|
+
fake_request(env)
|
168
|
+
else
|
169
|
+
m = Post.new(params)
|
170
|
+
body, head = m.to_multipart
|
171
|
+
fake_request(env.merge( :content_type => head,
|
172
|
+
:content_length => body.length), :post_body => body)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Merb
|
4
|
+
module Test
|
5
|
+
module RequestHelper
|
6
|
+
# FakeRequest sets up a default enviroment which can be overridden either
|
7
|
+
# by passing and env into initialize or using request['HTTP_VAR'] = 'foo'
|
8
|
+
class FakeRequest < Request
|
9
|
+
|
10
|
+
# ==== Parameters
|
11
|
+
# env<Hash>:: Environment options that override the defaults.
|
12
|
+
# req<StringIO>:: The request to set as input for Rack.
|
13
|
+
def initialize(env = {}, req = StringIO.new)
|
14
|
+
env.environmentize_keys!
|
15
|
+
env['rack.input'] = req
|
16
|
+
super(DEFAULT_ENV.merge(env))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
DEFAULT_ENV = Mash.new({
|
21
|
+
'SERVER_NAME' => 'localhost',
|
22
|
+
'PATH_INFO' => '/',
|
23
|
+
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
|
24
|
+
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060214 Camino/1.0',
|
25
|
+
'SCRIPT_NAME' => '/',
|
26
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
27
|
+
'HTTP_CACHE_CONTROL' => 'max-age=0',
|
28
|
+
'HTTP_ACCEPT_LANGUAGE' => 'en,ja;q=0.9,fr;q=0.9,de;q=0.8,es;q=0.7,it;q=0.7,nl;q=0.6,sv;q=0.5,nb;q=0.5,da;q=0.4,fi;q=0.3,pt;q=0.3,zh-Hans;q=0.2,zh-Hant;q=0.1,ko;q=0.1',
|
29
|
+
'HTTP_HOST' => 'localhost',
|
30
|
+
'REMOTE_ADDR' => '127.0.0.1',
|
31
|
+
'SERVER_SOFTWARE' => 'Mongrel 1.1',
|
32
|
+
'HTTP_KEEP_ALIVE' => '300',
|
33
|
+
'HTTP_REFERER' => 'http://localhost/',
|
34
|
+
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
35
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
36
|
+
'REQUEST_URI' => '/',
|
37
|
+
'SERVER_PORT' => '80',
|
38
|
+
'GATEWAY_INTERFACE' => 'CGI/1.2',
|
39
|
+
'HTTP_ACCEPT' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
40
|
+
'HTTP_CONNECTION' => 'keep-alive',
|
41
|
+
'REQUEST_METHOD' => 'GET'
|
42
|
+
}) unless defined?(DEFAULT_ENV)
|
43
|
+
end
|
44
|
+
|
45
|
+
# CookieJar keeps track of cookies in a simple Mash.
|
46
|
+
class CookieJar < Mash
|
47
|
+
|
48
|
+
# ==== Parameters
|
49
|
+
# request<Merb::Request, Merb::FakeRequest>:: The controller request.
|
50
|
+
def update_from_request(request)
|
51
|
+
request.cookies.each do |key, value|
|
52
|
+
if value.blank?
|
53
|
+
self.delete(key)
|
54
|
+
else
|
55
|
+
self[key] = Merb::Request.unescape(value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# ==== Parameters
|
63
|
+
# env<Hash>:: A hash of environment keys to be merged into the default list.
|
64
|
+
# opt<Hash>:: A hash of options (see below).
|
65
|
+
#
|
66
|
+
# ==== Options (opt)
|
67
|
+
# :post_body<String>:: The post body for the request.
|
68
|
+
# :req<String>::
|
69
|
+
# The request string. This will only be used if :post_body is left out.
|
70
|
+
#
|
71
|
+
# ==== Returns
|
72
|
+
# FakeRequest:: A Request object that is built based on the parameters.
|
73
|
+
#
|
74
|
+
# ==== Notes
|
75
|
+
# If you pass a post body, the content-type will be set to URL-encoded.
|
76
|
+
#
|
77
|
+
#---
|
78
|
+
# @public
|
79
|
+
def fake_request(env = {}, opt = {})
|
80
|
+
if opt[:post_body]
|
81
|
+
req = opt[:post_body]
|
82
|
+
env[:content_type] ||= "application/x-www-form-urlencoded"
|
83
|
+
else
|
84
|
+
req = opt[:req]
|
85
|
+
end
|
86
|
+
FakeRequest.new(env, StringIO.new(req || ''))
|
87
|
+
end
|
88
|
+
|
89
|
+
# Dispatches an action to the given class. This bypasses the router and is
|
90
|
+
# suitable for unit testing of controllers.
|
91
|
+
#
|
92
|
+
# ==== Parameters
|
93
|
+
# controller_klass<Controller>::
|
94
|
+
# The controller class object that the action should be dispatched to.
|
95
|
+
# action<Symbol>:: The action name, as a symbol.
|
96
|
+
# params<Hash>::
|
97
|
+
# An optional hash that will end up as params in the controller instance.
|
98
|
+
# env<Hash>::
|
99
|
+
# An optional hash that is passed to the fake request. Any request options
|
100
|
+
# should go here (see +fake_request+), including :req or :post_body
|
101
|
+
# for setting the request body itself.
|
102
|
+
# &blk::
|
103
|
+
# The controller is yielded to the block provided for actions *prior* to
|
104
|
+
# the action being dispatched.
|
105
|
+
#
|
106
|
+
# ==== Example
|
107
|
+
# dispatch_to(MyController, :create, :name => 'Homer' ) do |controller|
|
108
|
+
# controller.stub!(:current_user).and_return(@user)
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# ==== Notes
|
112
|
+
# Does not use routes.
|
113
|
+
#
|
114
|
+
#---
|
115
|
+
# @public
|
116
|
+
def dispatch_to(controller_klass, action, params = {}, env = {}, &blk)
|
117
|
+
params = merge_controller_and_action(controller_klass, action, params)
|
118
|
+
dispatch_request(build_request(params, env), controller_klass, action.to_s, &blk)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Keep track of cookie values in CookieJar within the context of the
|
122
|
+
# block; you need to set this up for secific controllers.
|
123
|
+
#
|
124
|
+
# ==== Parameters
|
125
|
+
# *controller_classes:: Controller classes to operate on in the context of the block.
|
126
|
+
# &blk:: The context to operate on; optionally accepts the cookie jar as an argument.
|
127
|
+
def with_cookies(*controller_classes, &blk)
|
128
|
+
cookie_jar = CookieJar.new
|
129
|
+
before_cb = lambda { |c| c.cookies.update(cookie_jar) }
|
130
|
+
after_cb = lambda { |c| cookie_jar.update_from_request(c.request) }
|
131
|
+
controller_classes.each do |klass|
|
132
|
+
klass._before_dispatch_callbacks << before_cb
|
133
|
+
klass._after_dispatch_callbacks << after_cb
|
134
|
+
end
|
135
|
+
blk.arity == 1 ? blk.call(cookie_jar) : blk.call
|
136
|
+
controller_classes.each do |klass|
|
137
|
+
klass._before_dispatch_callbacks.delete before_cb
|
138
|
+
klass._after_dispatch_callbacks.delete after_cb
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Dispatches an action to the given class and using HTTP Basic Authentication
|
143
|
+
# This bypasses the router and is suitable for unit testing of controllers.
|
144
|
+
#
|
145
|
+
# ==== Parameters
|
146
|
+
# controller_klass<Controller>::
|
147
|
+
# The controller class object that the action should be dispatched to.
|
148
|
+
# action<Symbol>:: The action name, as a symbol.
|
149
|
+
# username<String>:: The username.
|
150
|
+
# password<String>:: The password.
|
151
|
+
# params<Hash>::
|
152
|
+
# An optional hash that will end up as params in the controller instance.
|
153
|
+
# env<Hash>::
|
154
|
+
# An optional hash that is passed to the fake request. Any request options
|
155
|
+
# should go here (see +fake_request+), including :req or :post_body
|
156
|
+
# for setting the request body itself.
|
157
|
+
# &blk::
|
158
|
+
# The controller is yielded to the block provided for actions *prior* to
|
159
|
+
# the action being dispatched.
|
160
|
+
#
|
161
|
+
# ==== Example
|
162
|
+
# dispatch_with_basic_authentication_to(MyController, :create, 'Fred', 'secret', :name => 'Homer' ) do |controller|
|
163
|
+
# controller.stub!(:current_user).and_return(@user)
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# ==== Notes
|
167
|
+
# Does not use routes.
|
168
|
+
#
|
169
|
+
#---
|
170
|
+
# @public
|
171
|
+
def dispatch_with_basic_authentication_to(controller_klass, action, username, password, params = {}, env = {}, &blk)
|
172
|
+
env["X_HTTP_AUTHORIZATION"] = "Basic #{Base64.encode64("#{username}:#{password}")}"
|
173
|
+
|
174
|
+
params = merge_controller_and_action(controller_klass, action, params)
|
175
|
+
dispatch_request(build_request(params, env), controller_klass, action.to_s, &blk)
|
176
|
+
end
|
177
|
+
|
178
|
+
def merge_controller_and_action(controller_klass, action, params)
|
179
|
+
params[:controller] = controller_klass.name.to_const_path
|
180
|
+
params[:action] = action.to_s
|
181
|
+
|
182
|
+
params
|
183
|
+
end
|
184
|
+
|
185
|
+
# Prepares and returns a request suitable for dispatching with
|
186
|
+
# dispatch_request. If you don't need to modify the request
|
187
|
+
# object before dispatching (e.g. to add cookies), you probably
|
188
|
+
# want to use dispatch_to instead.
|
189
|
+
#
|
190
|
+
# ==== Parameters
|
191
|
+
# params<Hash>::
|
192
|
+
# An optional hash that will end up as params in the controller instance.
|
193
|
+
# env<Hash>::
|
194
|
+
# An optional hash that is passed to the fake request. Any request options
|
195
|
+
# should go here (see +fake_request+), including :req or :post_body
|
196
|
+
# for setting the request body itself.
|
197
|
+
#
|
198
|
+
# ==== Example
|
199
|
+
# req = build_request(:id => 1)
|
200
|
+
# req.cookies['app_cookie'] = "testing"
|
201
|
+
# dispatch_request(req, MyController, :edit)
|
202
|
+
#
|
203
|
+
# ==== Notes
|
204
|
+
# Does not use routes.
|
205
|
+
#
|
206
|
+
#---
|
207
|
+
# @public
|
208
|
+
def build_request(params = {}, env = {})
|
209
|
+
params = Merb::Request.params_to_query_string(params)
|
210
|
+
|
211
|
+
query_string = env[:query_string] || env['QUERY_STRING']
|
212
|
+
env[:query_string] = query_string ? "#{query_string}&#{params}" : params
|
213
|
+
|
214
|
+
post_body = env[:post_body] || env['POST_BODY']
|
215
|
+
fake_request(env, { :post_body => post_body, :req => env[:req] })
|
216
|
+
end
|
217
|
+
|
218
|
+
# An HTTP GET request that operates through the router.
|
219
|
+
#
|
220
|
+
# ==== Parameters
|
221
|
+
# path<String>:: The path that should go to the router as the request uri.
|
222
|
+
# params<Hash>::
|
223
|
+
# An optional hash that will end up as params in the controller instance.
|
224
|
+
# env<Hash>::
|
225
|
+
# An optional hash that is passed to the fake request. Any request options
|
226
|
+
# should go here (see +fake_request+).
|
227
|
+
# &blk::
|
228
|
+
# The controller is yielded to the block provided for actions *prior* to
|
229
|
+
# the action being dispatched.
|
230
|
+
#---
|
231
|
+
# @public
|
232
|
+
def get(path, params = {}, env = {}, &block)
|
233
|
+
env[:request_method] = "GET"
|
234
|
+
request(path, params, env, &block)
|
235
|
+
end
|
236
|
+
|
237
|
+
# An HTTP POST request that operates through the router.
|
238
|
+
#
|
239
|
+
# ==== Parameters
|
240
|
+
# path<String>:: The path that should go to the router as the request uri.
|
241
|
+
# params<Hash>::
|
242
|
+
# An optional hash that will end up as params in the controller instance.
|
243
|
+
# env<Hash>::
|
244
|
+
# An optional hash that is passed to the fake request. Any request options
|
245
|
+
# should go here (see fake_request).
|
246
|
+
# &blk::
|
247
|
+
# The controller is yielded to the block provided for actions *prior* to
|
248
|
+
# the action being dispatched.
|
249
|
+
#---
|
250
|
+
# @public
|
251
|
+
def post(path, params = {}, env = {}, &block)
|
252
|
+
env[:request_method] = "POST"
|
253
|
+
request(path, params, env, &block)
|
254
|
+
end
|
255
|
+
|
256
|
+
# An HTTP PUT request that operates through the router.
|
257
|
+
#
|
258
|
+
# ==== Parameters
|
259
|
+
# path<String>:: The path that should go to the router as the request uri.
|
260
|
+
# params<Hash>::
|
261
|
+
# An optional hash that will end up as params in the controller instance.
|
262
|
+
# env<Hash>::
|
263
|
+
# An optional hash that is passed to the fake request. Any request options
|
264
|
+
# should go here (see fake_request).
|
265
|
+
# &blk::
|
266
|
+
# The controller is yielded to the block provided for actions *prior* to
|
267
|
+
# the action being dispatched.
|
268
|
+
#---
|
269
|
+
# @public
|
270
|
+
def put(path, params = {}, env = {}, &block)
|
271
|
+
env[:request_method] = "PUT"
|
272
|
+
request(path, params, env, &block)
|
273
|
+
end
|
274
|
+
|
275
|
+
# An HTTP DELETE request that operates through the router
|
276
|
+
#
|
277
|
+
# ==== Parameters
|
278
|
+
# path<String>:: The path that should go to the router as the request uri.
|
279
|
+
# params<Hash>::
|
280
|
+
# An optional hash that will end up as params in the controller instance.
|
281
|
+
# env<Hash>::
|
282
|
+
# An optional hash that is passed to the fake request. Any request options
|
283
|
+
# should go here (see fake_request).
|
284
|
+
# &blk::
|
285
|
+
# The controller is yielded to the block provided for actions *prior* to
|
286
|
+
# the action being dispatched.
|
287
|
+
#---
|
288
|
+
# @public
|
289
|
+
def delete(path, params = {}, env = {}, &block)
|
290
|
+
env[:request_method] = "DELETE"
|
291
|
+
request(path, params, env, &block)
|
292
|
+
end
|
293
|
+
|
294
|
+
# A generic request that checks the router for the controller and action.
|
295
|
+
# This request goes through the Merb::Router and finishes at the controller.
|
296
|
+
#
|
297
|
+
# ==== Parameters
|
298
|
+
# path<String>:: The path that should go to the router as the request uri.
|
299
|
+
# params<Hash>::
|
300
|
+
# An optional hash that will end up as params in the controller instance.
|
301
|
+
# env<Hash>::
|
302
|
+
# An optional hash that is passed to the fake request. Any request options
|
303
|
+
# should go here (see +fake_request+).
|
304
|
+
# &blk::
|
305
|
+
# The controller is yielded to the block provided for actions *prior* to
|
306
|
+
# the action being dispatched.
|
307
|
+
#
|
308
|
+
# ==== Example
|
309
|
+
# request(path, { :name => 'Homer' }, { :request_method => "PUT" }) do |controller|
|
310
|
+
# controller.stub!(:current_user).and_return(@user)
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# ==== Notes
|
314
|
+
# Uses Routes.
|
315
|
+
#
|
316
|
+
#---
|
317
|
+
# @semi-public
|
318
|
+
def request(path, params = {}, env= {}, &block)
|
319
|
+
env[:request_method] ||= "GET"
|
320
|
+
env[:request_uri], env[:query_string] = path.split('?')
|
321
|
+
|
322
|
+
multipart = env.delete(:test_with_multipart)
|
323
|
+
|
324
|
+
request = build_request(params, env)
|
325
|
+
|
326
|
+
opts = check_request_for_route(request) # Check that the request will be routed correctly
|
327
|
+
controller_name = (opts[:namespace] ? opts.delete(:namespace) + '/' : '') + opts.delete(:controller)
|
328
|
+
klass = Object.full_const_get(controller_name.snake_case.to_const_string)
|
329
|
+
|
330
|
+
action = opts.delete(:action).to_s
|
331
|
+
params.merge!(opts)
|
332
|
+
|
333
|
+
multipart.nil? ? dispatch_to(klass, action, params, env, &block) : dispatch_multipart_to(klass, action, params, env, &block)
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
# The workhorse for the dispatch*to helpers.
|
338
|
+
#
|
339
|
+
# ==== Parameters
|
340
|
+
# request<Merb::Test::RequestHelper::FakeRequest, Merb::Request>::
|
341
|
+
# A request object that has been setup for testing.
|
342
|
+
# controller_klass<Merb::Controller>::
|
343
|
+
# The class object off the controller to dispatch the action to.
|
344
|
+
# action<Symbol>:: The action to dispatch the request to.
|
345
|
+
# &blk::
|
346
|
+
# The controller is yielded to the block provided for actions *prior* to
|
347
|
+
# the action being dispatched.
|
348
|
+
#
|
349
|
+
# ==== Returns
|
350
|
+
# An instance of +controller_klass+ based on the parameters.
|
351
|
+
#
|
352
|
+
# ==== Notes
|
353
|
+
# Does not use routes.
|
354
|
+
#
|
355
|
+
#---
|
356
|
+
# @public
|
357
|
+
def dispatch_request(request, controller_klass, action, &blk)
|
358
|
+
controller = controller_klass.new(request)
|
359
|
+
yield controller if block_given?
|
360
|
+
controller._dispatch(action)
|
361
|
+
|
362
|
+
Merb.logger.info controller._benchmarks.inspect
|
363
|
+
Merb.logger.flush
|
364
|
+
|
365
|
+
controller
|
366
|
+
end
|
367
|
+
|
368
|
+
# Checks to see that a request is routable.
|
369
|
+
#
|
370
|
+
# ==== Parameters
|
371
|
+
# request<Merb::Test::RequestHelper::FakeRequest, Merb::Request>::
|
372
|
+
# The request object to inspect.
|
373
|
+
#
|
374
|
+
# ==== Raises
|
375
|
+
# Merb::ControllerExceptions::BadRequest::
|
376
|
+
# No matching route was found.
|
377
|
+
#
|
378
|
+
# ==== Returns
|
379
|
+
# Hash:: The parameters built based on the matching route.
|
380
|
+
#
|
381
|
+
#---
|
382
|
+
# @semi-public
|
383
|
+
def check_request_for_route(request)
|
384
|
+
match = ::Merb::Router.match(request)
|
385
|
+
if match[0].nil? && match[1].empty?
|
386
|
+
raise ::Merb::ControllerExceptions::BadRequest, "No routes match the request. Request uri: #{request.uri}"
|
387
|
+
else
|
388
|
+
match[1]
|
389
|
+
end
|
390
|
+
end # check_request_for_route
|
391
|
+
end # RequestHelper
|
392
|
+
end # Test
|
393
|
+
end # Merb
|