actionpack 1.11.2 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (149) hide show
  1. data/CHANGELOG +392 -5
  2. data/lib/action_controller.rb +8 -4
  3. data/lib/action_controller/assertions.rb +9 -10
  4. data/lib/action_controller/base.rb +177 -88
  5. data/lib/action_controller/benchmarking.rb +5 -5
  6. data/lib/action_controller/caching.rb +44 -36
  7. data/lib/action_controller/cgi_ext/cgi_methods.rb +71 -6
  8. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +1 -1
  9. data/lib/action_controller/cgi_process.rb +36 -24
  10. data/lib/action_controller/components.rb +152 -52
  11. data/lib/action_controller/dependencies.rb +1 -1
  12. data/lib/action_controller/deprecated_redirects.rb +2 -2
  13. data/lib/action_controller/deprecated_request_methods.rb +34 -0
  14. data/lib/action_controller/filters.rb +59 -19
  15. data/lib/action_controller/flash.rb +53 -47
  16. data/lib/action_controller/helpers.rb +2 -2
  17. data/lib/action_controller/integration.rb +524 -0
  18. data/lib/action_controller/layout.rb +58 -23
  19. data/lib/action_controller/mime_responds.rb +163 -0
  20. data/lib/action_controller/mime_type.rb +142 -0
  21. data/lib/action_controller/pagination.rb +13 -7
  22. data/lib/action_controller/request.rb +59 -56
  23. data/lib/action_controller/rescue.rb +1 -1
  24. data/lib/action_controller/routing.rb +29 -10
  25. data/lib/action_controller/scaffolding.rb +8 -0
  26. data/lib/action_controller/session/active_record_store.rb +21 -10
  27. data/lib/action_controller/session/mem_cache_store.rb +18 -12
  28. data/lib/action_controller/session_management.rb +30 -11
  29. data/lib/action_controller/templates/rescues/_trace.rhtml +1 -1
  30. data/lib/action_controller/templates/scaffolds/layout.rhtml +4 -4
  31. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  32. data/lib/action_controller/test_process.rb +189 -118
  33. data/lib/action_controller/vendor/html-scanner/html/node.rb +20 -1
  34. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +3 -0
  35. data/lib/action_controller/vendor/html-scanner/html/version.rb +1 -1
  36. data/lib/action_controller/vendor/xml_node.rb +97 -0
  37. data/lib/action_controller/verification.rb +2 -0
  38. data/lib/action_pack/version.rb +3 -3
  39. data/lib/action_view.rb +0 -2
  40. data/lib/action_view/base.rb +109 -36
  41. data/lib/action_view/compiled_templates.rb +1 -1
  42. data/lib/action_view/helpers/active_record_helper.rb +4 -2
  43. data/lib/action_view/helpers/asset_tag_helper.rb +6 -7
  44. data/lib/action_view/helpers/capture_helper.rb +49 -12
  45. data/lib/action_view/helpers/date_helper.rb +14 -4
  46. data/lib/action_view/helpers/form_helper.rb +136 -20
  47. data/lib/action_view/helpers/form_options_helper.rb +29 -7
  48. data/lib/action_view/helpers/form_tag_helper.rb +22 -20
  49. data/lib/action_view/helpers/java_script_macros_helper.rb +29 -9
  50. data/lib/action_view/helpers/javascript_helper.rb +50 -446
  51. data/lib/action_view/helpers/javascripts/controls.js +95 -30
  52. data/lib/action_view/helpers/javascripts/dragdrop.js +161 -21
  53. data/lib/action_view/helpers/javascripts/effects.js +310 -211
  54. data/lib/action_view/helpers/javascripts/prototype.js +228 -28
  55. data/lib/action_view/helpers/number_helper.rb +9 -9
  56. data/lib/action_view/helpers/pagination_helper.rb +1 -1
  57. data/lib/action_view/helpers/prototype_helper.rb +900 -0
  58. data/lib/action_view/helpers/scriptaculous_helper.rb +135 -0
  59. data/lib/action_view/helpers/text_helper.rb +7 -6
  60. data/lib/action_view/helpers/url_helper.rb +23 -14
  61. data/lib/action_view/partials.rb +12 -4
  62. data/rakefile +13 -5
  63. data/test/abstract_unit.rb +4 -3
  64. data/test/active_record_unit.rb +88 -0
  65. data/test/{controller → activerecord}/active_record_assertions_test.rb +7 -50
  66. data/test/{controller → activerecord}/active_record_store_test.rb +27 -4
  67. data/test/activerecord/pagination_test.rb +161 -0
  68. data/test/controller/action_pack_assertions_test.rb +18 -15
  69. data/test/controller/base_test.rb +31 -42
  70. data/test/controller/benchmark_test.rb +8 -11
  71. data/test/controller/capture_test.rb +33 -1
  72. data/test/controller/cgi_test.rb +33 -0
  73. data/test/controller/custom_handler_test.rb +8 -0
  74. data/test/controller/fake_controllers.rb +9 -17
  75. data/test/controller/filters_test.rb +32 -3
  76. data/test/controller/flash_test.rb +26 -41
  77. data/test/controller/fragment_store_setting_test.rb +1 -1
  78. data/test/controller/layout_test.rb +73 -0
  79. data/test/controller/mime_responds_test.rb +257 -0
  80. data/test/controller/mime_type_test.rb +24 -0
  81. data/test/controller/new_render_test.rb +157 -1
  82. data/test/controller/redirect_test.rb +23 -0
  83. data/test/controller/render_test.rb +54 -56
  84. data/test/controller/request_test.rb +25 -0
  85. data/test/controller/routing_test.rb +74 -66
  86. data/test/controller/test_test.rb +66 -1
  87. data/test/controller/verification_test.rb +3 -1
  88. data/test/controller/webservice_test.rb +255 -0
  89. data/test/fixtures/companies.yml +24 -0
  90. data/test/fixtures/company.rb +9 -0
  91. data/test/fixtures/db_definitions/sqlite.sql +42 -0
  92. data/test/fixtures/developer.rb +7 -0
  93. data/test/fixtures/developers.yml +21 -0
  94. data/test/fixtures/developers_projects.yml +13 -0
  95. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  96. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  97. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  98. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  99. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  100. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  101. data/test/fixtures/project.rb +3 -0
  102. data/test/fixtures/projects.yml +7 -0
  103. data/test/fixtures/replies.yml +13 -0
  104. data/test/fixtures/reply.rb +5 -0
  105. data/test/fixtures/respond_to/all_types_with_layout.rhtml +1 -0
  106. data/test/fixtures/respond_to/all_types_with_layout.rjs +1 -0
  107. data/test/fixtures/respond_to/layouts/standard.rhtml +1 -0
  108. data/test/fixtures/respond_to/using_defaults.rhtml +1 -0
  109. data/test/fixtures/respond_to/using_defaults.rjs +1 -0
  110. data/test/fixtures/respond_to/using_defaults.rxml +1 -0
  111. data/test/fixtures/respond_to/using_defaults_with_type_list.rhtml +1 -0
  112. data/test/fixtures/respond_to/using_defaults_with_type_list.rjs +1 -0
  113. data/test/fixtures/respond_to/using_defaults_with_type_list.rxml +1 -0
  114. data/test/fixtures/test/block_content_for.rhtml +2 -0
  115. data/test/fixtures/test/delete_with_js.rjs +2 -0
  116. data/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml +1 -0
  117. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  118. data/test/fixtures/test/erb_content_for.rhtml +2 -0
  119. data/test/fixtures/test/hello_world.rxml +3 -0
  120. data/test/fixtures/test/hello_world_with_layout_false.rhtml +1 -0
  121. data/test/fixtures/test/non_erb_block_content_for.rxml +4 -0
  122. data/test/fixtures/topic.rb +3 -0
  123. data/test/fixtures/topics.yml +22 -0
  124. data/test/template/active_record_helper_test.rb +4 -0
  125. data/test/template/asset_tag_helper_test.rb +7 -2
  126. data/test/template/date_helper_test.rb +39 -2
  127. data/test/template/form_helper_test.rb +238 -5
  128. data/test/template/form_options_helper_test.rb +78 -0
  129. data/test/template/form_tag_helper_test.rb +11 -0
  130. data/test/template/java_script_macros_helper_test.rb +51 -6
  131. data/test/template/javascript_helper_test.rb +7 -153
  132. data/test/template/number_helper_test.rb +14 -13
  133. data/test/template/prototype_helper_test.rb +423 -0
  134. data/test/template/scriptaculous_helper_test.rb +90 -0
  135. data/test/template/text_helper_test.rb +12 -9
  136. data/test/template/url_helper_test.rb +31 -15
  137. metadata +291 -246
  138. data/lib/action_controller/cgi_ext/multipart_progress.rb +0 -169
  139. data/lib/action_controller/upload_progress.rb +0 -473
  140. data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +0 -17
  141. data/lib/action_view/helpers/upload_progress_helper.rb +0 -433
  142. data/lib/action_view/vendor/builder.rb +0 -13
  143. data/lib/action_view/vendor/builder/blankslate.rb +0 -53
  144. data/lib/action_view/vendor/builder/xmlbase.rb +0 -143
  145. data/lib/action_view/vendor/builder/xmlevents.rb +0 -63
  146. data/lib/action_view/vendor/builder/xmlmarkup.rb +0 -308
  147. data/test/controller/multipart_progress_testx.rb +0 -365
  148. data/test/controller/upload_progress_testx.rb +0 -89
  149. data/test/template/upload_progress_helper_testx.rb +0 -136
@@ -24,14 +24,21 @@ module ActionController #:nodoc:
24
24
  #
25
25
  # See docs on the FlashHash class for more details about the flash.
26
26
  module Flash
27
- def self.append_features(base) #:nodoc:
28
- super
29
- base.before_filter(:fire_flash)
30
- base.after_filter(:sweep_flash)
31
- end
27
+ def self.included(base)
28
+ base.send :include, InstanceMethods
29
+
30
+ base.class_eval do
31
+ alias_method :assign_shortcuts_without_flash, :assign_shortcuts
32
+ alias_method :assign_shortcuts, :assign_shortcuts_with_flash
32
33
 
34
+ alias_method :process_cleanup_without_flash, :process_cleanup
35
+ alias_method :process_cleanup, :process_cleanup_with_flash
36
+ end
37
+ end
38
+
39
+
33
40
  class FlashNow #:nodoc:
34
- def initialize flash
41
+ def initialize(flash)
35
42
  @flash = flash
36
43
  end
37
44
 
@@ -47,9 +54,6 @@ module ActionController #:nodoc:
47
54
  end
48
55
 
49
56
  class FlashHash < Hash
50
- @@avoid_sweep = false
51
- cattr_accessor :avoid_sweep
52
-
53
57
  def initialize #:nodoc:
54
58
  super
55
59
  @used = {}
@@ -60,14 +64,14 @@ module ActionController #:nodoc:
60
64
  super
61
65
  end
62
66
 
63
- def update h #:nodoc:
64
- h.keys.each{|k| discard k }
67
+ def update(h) #:nodoc:
68
+ h.keys.each{ |k| discard(k) }
65
69
  super
66
70
  end
67
71
 
68
- alias merge! update
72
+ alias :merge! :update
69
73
 
70
- def replace h #:nodoc:
74
+ def replace(h) #:nodoc:
71
75
  @used = {}
72
76
  super
73
77
  end
@@ -106,7 +110,6 @@ module ActionController #:nodoc:
106
110
  #
107
111
  # This method is called automatically by filters, so you generally don't need to care about it.
108
112
  def sweep #:nodoc:
109
- return if @@avoid_sweep
110
113
  keys.each do |k|
111
114
  unless @used[k]
112
115
  use(k)
@@ -133,40 +136,43 @@ module ActionController #:nodoc:
133
136
  end
134
137
  end
135
138
 
136
-
137
- protected
138
- # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
139
- # <tt>flash["notice"] = "hello"</tt> to put a new one.
140
- # Note that if sessions are disabled only flash.now will work.
141
- def flash #:doc:
142
- # @session = Hash.new if sessions are disabled
143
- if @session.is_a?(Hash)
144
- @__flash ||= FlashHash.new
145
-
146
- # otherwise, @session is a CGI::Session or a TestSession
147
- else
148
- @session['flash'] ||= FlashHash.new
149
- end
139
+ module InstanceMethods #:nodoc:
140
+ def assign_shortcuts_with_flash(request, response) #:nodoc:
141
+ assign_shortcuts_without_flash(request, response)
142
+ flash(:refresh)
150
143
  end
151
-
152
- # deprecated. use <tt>flash.keep</tt> instead
153
- def keep_flash #:doc:
154
- warn 'keep_flash is deprecated; use flash.keep instead.'
155
- flash.keep
144
+
145
+ def process_cleanup_with_flash
146
+ flash.sweep if @session
147
+ process_cleanup_without_flash
156
148
  end
149
+
150
+ protected
151
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
152
+ # <tt>flash["notice"] = "hello"</tt> to put a new one.
153
+ # Note that if sessions are disabled only flash.now will work.
154
+ def flash(refresh = false) #:doc:
155
+ if @flash.nil? || refresh
156
+ @flash =
157
+ if @session.is_a?(Hash)
158
+ # @session is a Hash, if sessions are disabled
159
+ # we don't put the flash in the session in this case
160
+ FlashHash.new
161
+ else
162
+ # otherwise, @session is a CGI::Session or a TestSession
163
+ # so make sure it gets retrieved from/saved to session storage after request processing
164
+ @session["flash"] ||= FlashHash.new
165
+ end
166
+ end
167
+
168
+ @flash
169
+ end
157
170
 
158
-
159
- private
160
-
161
- # marks flash entries as used and expose the flash to the view
162
- def fire_flash
163
- flash.discard
164
- @assigns["flash"] = flash
165
- end
166
-
167
- # deletes the flash entries that were not marked for keeping
168
- def sweep_flash
169
- flash.sweep
170
- end
171
+ # deprecated. use <tt>flash.keep</tt> instead
172
+ def keep_flash #:doc:
173
+ warn 'keep_flash is deprecated; use flash.keep instead.'
174
+ flash.keep
175
+ end
176
+ end
171
177
  end
172
- end
178
+ end
@@ -109,7 +109,7 @@ module ActionController #:nodoc:
109
109
 
110
110
  private
111
111
  def default_helper_module!
112
- module_name = name.sub(/^Controllers::/, '').sub(/Controller$|$/, 'Helper')
112
+ module_name = name.sub(/Controller$|$/, 'Helper')
113
113
  module_path = module_name.split('::').map { |m| m.underscore }.join('/')
114
114
  require_dependency module_path
115
115
  helper module_name.constantize
@@ -128,7 +128,7 @@ module ActionController #:nodoc:
128
128
  rescue MissingSourceFile => e
129
129
  raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
130
130
  end
131
- end
131
+ end
132
132
  end
133
133
  end
134
134
  end
@@ -0,0 +1,524 @@
1
+ require 'dispatcher'
2
+ require 'stringio'
3
+ require 'uri'
4
+
5
+ module ActionController
6
+ module Integration #:nodoc:
7
+ # An integration Session instance represents a set of requests and responses
8
+ # performed sequentially by some virtual user. Becase you can instantiate
9
+ # multiple sessions and run them side-by-side, you can also mimic (to some
10
+ # limited extent) multiple simultaneous users interacting with your system.
11
+ #
12
+ # Typically, you will instantiate a new session using IntegrationTest#open_session,
13
+ # rather than instantiating Integration::Session directly.
14
+ class Session
15
+ include Test::Unit::Assertions
16
+ include ActionController::TestProcess
17
+
18
+ # The integer HTTP status code of the last request.
19
+ attr_reader :status
20
+
21
+ # The status message that accompanied the status code of the last request.
22
+ attr_reader :status_message
23
+
24
+ # The URI of the last request.
25
+ attr_reader :path
26
+
27
+ # The hostname used in the last request.
28
+ attr_accessor :host
29
+
30
+ # The remote_addr used in the last request.
31
+ attr_accessor :remote_addr
32
+
33
+ # The Accept header to send.
34
+ attr_accessor :accept
35
+
36
+ # A map of the cookies returned by the last response, and which will be
37
+ # sent with the next request.
38
+ attr_reader :cookies
39
+
40
+ # A map of the headers returned by the last response.
41
+ attr_reader :headers
42
+
43
+ # A reference to the controller instance used by the last request.
44
+ attr_reader :controller
45
+
46
+ # A reference to the request instance used by the last request.
47
+ attr_reader :request
48
+
49
+ # A reference to the response instance used by the last request.
50
+ attr_reader :response
51
+
52
+ # Create an initialize a new Session instance.
53
+ def initialize
54
+ reset!
55
+ end
56
+
57
+ # Resets the instance. This can be used to reset the state information
58
+ # in an existing session instance, so it can be used from a clean-slate
59
+ # condition.
60
+ #
61
+ # session.reset!
62
+ def reset!
63
+ @status = @path = @headers = nil
64
+ @result = @status_message = nil
65
+ @https = false
66
+ @cookies = {}
67
+ @controller = @request = @response = nil
68
+
69
+ self.host = "www.example.com"
70
+ self.remote_addr = "127.0.0.1"
71
+ self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
72
+
73
+ unless @named_routes_configured
74
+ # install the named routes in this session instance.
75
+ klass = class<<self; self; end
76
+ Routing::NamedRoutes.install(klass)
77
+
78
+ # the helpers are made protected by default--we make them public for
79
+ # easier access during testing and troubleshooting.
80
+ klass.send(:public, *Routing::NamedRoutes::Helpers)
81
+ @named_routes_configured = true
82
+ end
83
+ end
84
+
85
+ # Specify whether or not the session should mimic a secure HTTPS request.
86
+ #
87
+ # session.https!
88
+ # session.https!(false)
89
+ def https!(flag=true)
90
+ @https = flag
91
+ end
92
+
93
+ # Return +true+ if the session is mimicing a secure HTTPS request.
94
+ #
95
+ # if session.https?
96
+ # ...
97
+ # end
98
+ def https?
99
+ @https
100
+ end
101
+
102
+ # Set the host name to use in the next request.
103
+ #
104
+ # session.host! "www.example.com"
105
+ def host!(name)
106
+ @host = name
107
+ end
108
+
109
+ # Follow a single redirect response. If the last response was not a
110
+ # redirect, an exception will be raised. Otherwise, the redirect is
111
+ # performed on the location header.
112
+ def follow_redirect!
113
+ raise "not a redirect! #{@status} #{@status_message}" unless redirect?
114
+ get(interpret_uri(headers["location"].first))
115
+ status
116
+ end
117
+
118
+ # Performs a GET request, following any subsequent redirect. Note that
119
+ # the redirects are followed until the response is not a redirect--this
120
+ # means you may run into an infinite loop if your redirect loops back to
121
+ # itself.
122
+ def get_via_redirect(path, args={})
123
+ get path, args
124
+ follow_redirect! while redirect?
125
+ status
126
+ end
127
+
128
+ # Performs a POST request, following any subsequent redirect. This is
129
+ # vulnerable to infinite loops, the same as #get_via_redirect.
130
+ def post_via_redirect(path, args={})
131
+ post path, args
132
+ follow_redirect! while redirect?
133
+ status
134
+ end
135
+
136
+ # Returns +true+ if the last response was a redirect.
137
+ def redirect?
138
+ status/100 == 3
139
+ end
140
+
141
+ # Performs a GET request with the given parameters. The parameters may
142
+ # be +nil+, a Hash, or a string that is appropriately encoded
143
+ # (application/x-www-form-urlencoded or multipart/form-data).
144
+ def get(path, parameters=nil, headers=nil)
145
+ process :get, path, parameters, headers
146
+ end
147
+
148
+ # Performs a POST request with the given parameters. The parameters may
149
+ # be +nil+, a Hash, or a string that is appropriately encoded
150
+ # (application/x-www-form-urlencoded or multipart/form-data).
151
+ def post(path, parameters=nil, headers=nil)
152
+ process :post, path, parameters, headers
153
+ end
154
+
155
+ # Performs an XMLHttpRequest request with the given parameters, mimicing
156
+ # the request environment created by the Prototype library. The parameters
157
+ # may be +nil+, a Hash, or a string that is appropriately encoded
158
+ # (application/x-www-form-urlencoded or multipart/form-data).
159
+ def xml_http_request(path, parameters=nil, headers=nil)
160
+ headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
161
+ post(path, parameters, headers)
162
+ end
163
+
164
+ # Returns the URL for the given options, according to the rules specified
165
+ # in the application's routes.
166
+ def url_for(options)
167
+ controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
168
+ end
169
+
170
+ private
171
+
172
+ class MockCGI < CGI #:nodoc:
173
+ attr_accessor :stdinput, :stdoutput, :env_table
174
+
175
+ def initialize(env, input=nil)
176
+ self.env_table = env
177
+ self.stdinput = StringIO.new(input || "")
178
+ self.stdoutput = StringIO.new
179
+
180
+ super()
181
+ end
182
+ end
183
+
184
+ # Tailors the session based on the given URI, setting the HTTPS value
185
+ # and the hostname.
186
+ def interpret_uri(path)
187
+ location = URI.parse(path)
188
+ https! URI::HTTPS === location if location.scheme
189
+ host! location.host if location.host
190
+ location.query ? "#{location.path}?#{location.query}" : location.path
191
+ end
192
+
193
+ # Performs the actual request.
194
+ def process(method, path, parameters=nil, headers=nil)
195
+ data = requestify(parameters)
196
+ path = interpret_uri(path) if path =~ %r{://}
197
+ path = "/#{path}" unless path[0] == ?/
198
+ @path = path
199
+ env = {}
200
+
201
+ if method == :get
202
+ env["QUERY_STRING"] = data
203
+ data = nil
204
+ end
205
+
206
+ env.update(
207
+ "REQUEST_METHOD" => method.to_s.upcase,
208
+ "REQUEST_URI" => path,
209
+ "HTTP_HOST" => host,
210
+ "REMOTE_ADDR" => remote_addr,
211
+ "SERVER_PORT" => (https? ? "443" : "80"),
212
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
213
+ "CONTENT_LENGTH" => data ? data.length.to_s : nil,
214
+ "HTTP_COOKIE" => encode_cookies,
215
+ "HTTPS" => https? ? "on" : "off",
216
+ "HTTP_ACCEPT" => accept
217
+ )
218
+
219
+ (headers || {}).each do |key, value|
220
+ key = key.to_s.upcase.gsub(/-/, "_")
221
+ key = "HTTP_#{key}" unless env.has_key?(key)
222
+ env[key] = value
223
+ end
224
+
225
+ unless ActionController::Base.respond_to?(:clear_last_instantiation!)
226
+ ActionController::Base.send(:include, ControllerCapture)
227
+ end
228
+
229
+ ActionController::Base.clear_last_instantiation!
230
+
231
+ cgi = MockCGI.new(env, data)
232
+ Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
233
+ @result = cgi.stdoutput.string
234
+
235
+ @controller = ActionController::Base.last_instantiation
236
+ @request = @controller.request
237
+ @response = @controller.response
238
+
239
+ # Decorate the response with the standard behavior of the TestResponse
240
+ # so that things like assert_response can be used in integration
241
+ # tests.
242
+ @response.extend(TestResponseBehavior)
243
+
244
+ parse_result
245
+ return status
246
+ end
247
+
248
+ # Parses the result of the response and extracts the various values,
249
+ # like cookies, status, headers, etc.
250
+ def parse_result
251
+ headers, result_body = @result.split(/\r\n\r\n/, 2)
252
+
253
+ @headers = Hash.new { |h,k| h[k] = [] }
254
+ headers.each_line do |line|
255
+ key, value = line.strip.split(/:\s*/, 2)
256
+ @headers[key.downcase] << value
257
+ end
258
+
259
+ (@headers['set-cookie'] || [] ).each do |string|
260
+ name, value = string.match(/^(.*?)=(.*?);/)[1,2]
261
+ @cookies[name] = value
262
+ end
263
+
264
+ @status, @status_message = @headers["status"].first.split(/ /)
265
+ @status = @status.to_i
266
+ end
267
+
268
+ # Encode the cookies hash in a format suitable for passing to a
269
+ # request.
270
+ def encode_cookies
271
+ cookies.inject("") do |string, (name, value)|
272
+ string << "#{name}=#{value}; "
273
+ end
274
+ end
275
+
276
+ # Get a temporarly URL writer object
277
+ def generic_url_rewriter
278
+ cgi = MockCGI.new('REQUEST_METHOD' => "GET",
279
+ 'QUERY_STRING' => "",
280
+ "REQUEST_URI" => "/",
281
+ "HTTP_HOST" => host,
282
+ "SERVER_PORT" => https? ? "443" : "80",
283
+ "HTTPS" => https? ? "on" : "off")
284
+ ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
285
+ end
286
+
287
+ def name_with_prefix(prefix, name)
288
+ prefix ? "#{prefix}[#{name}]" : name.to_s
289
+ end
290
+
291
+ # Convert the given parameters to a request string. The parameters may
292
+ # be a string, +nil+, or a Hash.
293
+ def requestify(parameters, prefix=nil)
294
+ if Hash === parameters
295
+ return nil if parameters.empty?
296
+ parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
297
+ elsif Array === parameters
298
+ parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
299
+ elsif prefix.nil?
300
+ parameters
301
+ else
302
+ "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
303
+ end
304
+ end
305
+
306
+ end
307
+
308
+ # A module used to extend ActionController::Base, so that integration tests
309
+ # can capture the controller used to satisfy a request.
310
+ module ControllerCapture #:nodoc:
311
+ def self.included(base)
312
+ base.extend(ClassMethods)
313
+ base.class_eval do
314
+ class <<self
315
+ alias_method :new_without_capture, :new
316
+ alias_method :new, :new_with_capture
317
+ end
318
+ end
319
+ end
320
+
321
+ module ClassMethods #:nodoc:
322
+ mattr_accessor :last_instantiation
323
+
324
+ def clear_last_instantiation!
325
+ self.last_instantiation = nil
326
+ end
327
+
328
+ def new_with_capture(*args)
329
+ self.last_instantiation ||= new_without_capture(*args)
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ # An IntegrationTest is one that spans multiple controllers and actions,
336
+ # tying them all together to ensure they work together as expected. It tests
337
+ # more completely than either unit or functional tests do, exercising the
338
+ # entire stack, from the dispatcher to the database.
339
+ #
340
+ # At its simplest, you simply extend IntegrationTest and write your tests
341
+ # using the get/post methods:
342
+ #
343
+ # require "#{File.dirname(__FILE__)}/test_helper"
344
+ # require "integration_test"
345
+ #
346
+ # class ExampleTest < ActionController::IntegrationTest
347
+ # fixtures :people
348
+ #
349
+ # def test_login
350
+ # # get the login page
351
+ # get "/login"
352
+ # assert_equal 200, status
353
+ #
354
+ # # post the login and follow through to the home page
355
+ # post "/login", :username => people(:jamis).username,
356
+ # :password => people(:jamis).password
357
+ # follow_redirect!
358
+ # assert_equal 200, status
359
+ # assert_equal "/home", path
360
+ # end
361
+ # end
362
+ #
363
+ # However, you can also have multiple session instances open per test, and
364
+ # even extend those instances with assertions and methods to create a very
365
+ # powerful testing DSL that is specific for your application. You can even
366
+ # reference any named routes you happen to have defined!
367
+ #
368
+ # require "#{File.dirname(__FILE__)}/test_helper"
369
+ # require "integration_test"
370
+ #
371
+ # class AdvancedTest < ActionController::IntegrationTest
372
+ # fixtures :people, :rooms
373
+ #
374
+ # def test_login_and_speak
375
+ # jamis, david = login(:jamis), login(:david)
376
+ # room = rooms(:office)
377
+ #
378
+ # jamis.enter(room)
379
+ # jamis.speak(room, "anybody home?")
380
+ #
381
+ # david.enter(room)
382
+ # david.speak(room, "hello!")
383
+ # end
384
+ #
385
+ # private
386
+ #
387
+ # module CustomAssertions
388
+ # def enter(room)
389
+ # # reference a named route, for maximum internal consistency!
390
+ # get(room_url(:id => room.id))
391
+ # assert(...)
392
+ # ...
393
+ # end
394
+ #
395
+ # def speak(room, message)
396
+ # xml_http_request "/say/#{room.id}", :message => message
397
+ # assert(...)
398
+ # ...
399
+ # end
400
+ # end
401
+ #
402
+ # def login(who)
403
+ # open_session do |sess|
404
+ # sess.extend(CustomAssertions)
405
+ # who = people(who)
406
+ # sess.post "/login", :username => who.username,
407
+ # :password => who.password
408
+ # assert(...)
409
+ # end
410
+ # end
411
+ # end
412
+ class IntegrationTest < Test::Unit::TestCase
413
+ # Work around a bug in test/unit caused by the default test being named
414
+ # as a symbol (:default_test), which causes regex test filters
415
+ # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
416
+ # symbols.
417
+ def initialize(name) #:nodoc:
418
+ super(name.to_s)
419
+ end
420
+
421
+ # Work around test/unit's requirement that every subclass of TestCase have
422
+ # at least one test method. Note that this implementation extends to all
423
+ # subclasses, as well, so subclasses of IntegrationTest may also exist
424
+ # without any test methods.
425
+ def run(*args) #:nodoc:
426
+ return if @method_name == "default_test"
427
+ super
428
+ end
429
+
430
+ # Because of how use_instantiated_fixtures and use_transactional_fixtures
431
+ # are defined, we need to treat them as special cases. Otherwise, users
432
+ # would potentially have to set their values for both Test::Unit::TestCase
433
+ # ActionController::IntegrationTest, since by the time the value is set on
434
+ # TestCase, IntegrationTest has already been defined and cannot inherit
435
+ # changes to those variables. So, we make those two attributes copy-on-write.
436
+
437
+ class << self
438
+ def use_transactional_fixtures=(flag) #:nodoc:
439
+ @_use_transactional_fixtures = true
440
+ @use_transactional_fixtures = flag
441
+ end
442
+
443
+ def use_instantiated_fixtures=(flag) #:nodoc:
444
+ @_use_instantiated_fixtures = true
445
+ @use_instantiated_fixtures = flag
446
+ end
447
+
448
+ def use_transactional_fixtures #:nodoc:
449
+ @_use_transactional_fixtures ?
450
+ @use_transactional_fixtures :
451
+ superclass.use_transactional_fixtures
452
+ end
453
+
454
+ def use_instantiated_fixtures #:nodoc:
455
+ @_use_instantiated_fixtures ?
456
+ @use_instantiated_fixtures :
457
+ superclass.use_instantiated_fixtures
458
+ end
459
+ end
460
+
461
+ # Reset the current session. This is useful for testing multiple sessions
462
+ # in a single test case.
463
+ def reset!
464
+ @integration_session = open_session
465
+ end
466
+
467
+ %w(get post cookies assigns).each do |method|
468
+ define_method(method) do |*args|
469
+ reset! unless @integration_session
470
+ returning @integration_session.send(method, *args) do
471
+ copy_session_variables!
472
+ end
473
+ end
474
+ end
475
+
476
+ # Open a new session instance. If a block is given, the new session is
477
+ # yielded to the block before being returned.
478
+ #
479
+ # session = open_session do |sess|
480
+ # sess.extend(CustomAssertions)
481
+ # end
482
+ #
483
+ # By default, a single session is automatically created for you, but you
484
+ # can use this method to open multiple sessions that ought to be tested
485
+ # simultaneously.
486
+ def open_session
487
+ session = Integration::Session.new
488
+
489
+ # delegate the fixture accessors back to the test instance
490
+ extras = Module.new { attr_accessor :delegate, :test_result }
491
+ self.class.fixture_table_names.each do |table_name|
492
+ name = table_name.tr(".", "_")
493
+ next unless respond_to?(name)
494
+ extras.send(:define_method, name) { |*args| delegate.send(name, *args) }
495
+ end
496
+
497
+ # delegate add_assertion to the test case
498
+ extras.send(:define_method, :add_assertion) { test_result.add_assertion }
499
+ session.extend(extras)
500
+ session.delegate = self
501
+ session.test_result = @_result
502
+
503
+ yield session if block_given?
504
+ session
505
+ end
506
+
507
+ # Copy the instance variables from the current session instance into the
508
+ # test instance.
509
+ def copy_session_variables! #:nodoc:
510
+ return unless @integration_session
511
+ %w(controller response request).each do |var|
512
+ instance_variable_set("@#{var}", @integration_session.send(var))
513
+ end
514
+ end
515
+
516
+ # Delegate unhandled messages to the current session instance.
517
+ def method_missing(sym, *args, &block)
518
+ reset! unless @integration_session
519
+ returning @integration_session.send(sym, *args, &block) do
520
+ copy_session_variables!
521
+ end
522
+ end
523
+ end
524
+ end