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.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +458 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +319 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +340 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. 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,8 @@
1
+ module Merb
2
+ module Test
3
+ module ControllerHelper
4
+ include RequestHelper
5
+ include MultipartRequestHelper
6
+ end
7
+ end
8
+ end
@@ -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