actionpack 3.2.13 → 3.2.14.rc1

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.

@@ -98,8 +98,8 @@ module ActionDispatch
98
98
  BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
99
99
 
100
100
  def valid_accept_header
101
- (xhr? && (accept || content_mime_type)) ||
102
- (accept && accept !~ BROWSER_LIKE_ACCEPTS)
101
+ (xhr? && (accept.present? || content_mime_type)) ||
102
+ (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
103
103
  end
104
104
 
105
105
  def use_accept_header
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/object/blank'
3
3
  require 'active_support/core_ext/object/inclusion'
4
+ require 'active_support/core_ext/enumerable'
4
5
  require 'active_support/inflector'
5
6
  require 'action_dispatch/routing/redirection'
6
7
 
@@ -67,8 +68,6 @@ module ActionDispatch
67
68
  private
68
69
 
69
70
  def normalize_options!
70
- path_without_format = @path.sub(/\(\.:format\)$/, '')
71
-
72
71
  @options.merge!(default_controller_and_action)
73
72
 
74
73
  requirements.each do |name, requirement|
@@ -781,6 +780,10 @@ module ActionDispatch
781
780
  child
782
781
  end
783
782
 
783
+ def merge_action_scope(parent, child) #:nodoc:
784
+ child
785
+ end
786
+
784
787
  def merge_path_names_scope(parent, child) #:nodoc:
785
788
  merge_options_scope(parent, child)
786
789
  end
@@ -1255,6 +1258,10 @@ module ActionDispatch
1255
1258
  paths = [path] + rest
1256
1259
  end
1257
1260
 
1261
+ if @scope[:controller] && @scope[:action]
1262
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1263
+ end
1264
+
1258
1265
  path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
1259
1266
  if using_match_shorthand?(path_without_format, options)
1260
1267
  options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
@@ -97,9 +97,7 @@ module ActionDispatch
97
97
  @routes = {}
98
98
  @helpers = []
99
99
 
100
- @module = Module.new do
101
- instance_methods.each { |selector| remove_method(selector) }
102
- end
100
+ @module = Module.new
103
101
  end
104
102
 
105
103
  def helper_names
@@ -108,13 +106,11 @@ module ActionDispatch
108
106
 
109
107
  def clear!
110
108
  @helpers.each do |helper|
111
- @module.module_eval do
112
- remove_possible_method helper
113
- end
109
+ @module.remove_possible_method helper
114
110
  end
115
111
 
116
- @routes = {}
117
- @helpers = []
112
+ @routes.clear
113
+ @helpers.clear
118
114
  end
119
115
 
120
116
  def add(name, route)
@@ -0,0 +1,525 @@
1
+ require 'stringio'
2
+ require 'uri'
3
+ require 'active_support/core_ext/kernel/singleton_class'
4
+ require 'active_support/core_ext/object/try'
5
+ require 'rack/test'
6
+ require 'minitest/unit'
7
+
8
+ module ActionDispatch
9
+ module Integration #:nodoc:
10
+ module RequestHelpers
11
+ # Performs a GET request with the given parameters.
12
+ #
13
+ # - +path+: The URI (as a String) on which you want to perform a GET
14
+ # request.
15
+ # - +parameters+: The HTTP parameters that you want to pass. This may
16
+ # be +nil+,
17
+ # a Hash, or a String that is appropriately encoded
18
+ # (<tt>application/x-www-form-urlencoded</tt> or
19
+ # <tt>multipart/form-data</tt>).
20
+ # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
21
+ # merged into the Rack env hash.
22
+ #
23
+ # This method returns a Response object, which one can use to
24
+ # inspect the details of the response. Furthermore, if this method was
25
+ # called from an ActionDispatch::IntegrationTest object, then that
26
+ # object's <tt>@response</tt> instance variable will point to the same
27
+ # response object.
28
+ #
29
+ # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
30
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
31
+ def get(path, parameters = nil, headers_or_env = nil)
32
+ process :get, path, parameters, headers_or_env
33
+ end
34
+
35
+ # Performs a POST request with the given parameters. See +#get+ for more
36
+ # details.
37
+ def post(path, parameters = nil, headers_or_env = nil)
38
+ process :post, path, parameters, headers_or_env
39
+ end
40
+
41
+ # Performs a PATCH request with the given parameters. See +#get+ for more
42
+ # details.
43
+ def patch(path, parameters = nil, headers_or_env = nil)
44
+ process :patch, path, parameters, headers_or_env
45
+ end
46
+
47
+ # Performs a PUT request with the given parameters. See +#get+ for more
48
+ # details.
49
+ def put(path, parameters = nil, headers_or_env = nil)
50
+ process :put, path, parameters, headers_or_env
51
+ end
52
+
53
+ # Performs a DELETE request with the given parameters. See +#get+ for
54
+ # more details.
55
+ def delete(path, parameters = nil, headers_or_env = nil)
56
+ process :delete, path, parameters, headers_or_env
57
+ end
58
+
59
+ # Performs a HEAD request with the given parameters. See +#get+ for more
60
+ # details.
61
+ def head(path, parameters = nil, headers_or_env = nil)
62
+ process :head, path, parameters, headers_or_env
63
+ end
64
+
65
+ <<<<<<< HEAD
66
+ # Performs a OPTIONS request with the given parameters. See +#get+ for
67
+ # more details.
68
+ def options(path, parameters = nil, headers_or_env = nil)
69
+ process :options, path, parameters, headers_or_env
70
+ end
71
+
72
+ =======
73
+ >>>>>>> parent of ad46884... Integration tests support the OPTIONS http method
74
+ # Performs an XMLHttpRequest request with the given parameters, mirroring
75
+ # a request from the Prototype library.
76
+ #
77
+ # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
78
+ # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
79
+ # string; the headers are a hash.
80
+ def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
81
+ headers_or_env ||= {}
82
+ headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
83
+ headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
84
+ process(request_method, path, parameters, headers_or_env)
85
+ end
86
+ alias xhr :xml_http_request
87
+
88
+ # Follow a single redirect response. If the last response was not a
89
+ # redirect, an exception will be raised. Otherwise, the redirect is
90
+ # performed on the location header.
91
+ def follow_redirect!
92
+ raise "not a redirect! #{status} #{status_message}" unless redirect?
93
+ get(response.location)
94
+ status
95
+ end
96
+
97
+ # Performs a request using the specified method, following any subsequent
98
+ # redirect. Note that the redirects are followed until the response is
99
+ # not a redirect--this means you may run into an infinite loop if your
100
+ # redirect loops back to itself.
101
+ def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
102
+ process(http_method, path, parameters, headers_or_env)
103
+ follow_redirect! while redirect?
104
+ status
105
+ end
106
+
107
+ # Performs a GET request, following any subsequent redirect.
108
+ # See +request_via_redirect+ for more information.
109
+ def get_via_redirect(path, parameters = nil, headers_or_env = nil)
110
+ request_via_redirect(:get, path, parameters, headers_or_env)
111
+ end
112
+
113
+ # Performs a POST request, following any subsequent redirect.
114
+ # See +request_via_redirect+ for more information.
115
+ def post_via_redirect(path, parameters = nil, headers_or_env = nil)
116
+ request_via_redirect(:post, path, parameters, headers_or_env)
117
+ end
118
+
119
+ # Performs a PATCH request, following any subsequent redirect.
120
+ # See +request_via_redirect+ for more information.
121
+ def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
122
+ request_via_redirect(:patch, path, parameters, headers_or_env)
123
+ end
124
+
125
+ # Performs a PUT request, following any subsequent redirect.
126
+ # See +request_via_redirect+ for more information.
127
+ def put_via_redirect(path, parameters = nil, headers_or_env = nil)
128
+ request_via_redirect(:put, path, parameters, headers_or_env)
129
+ end
130
+
131
+ # Performs a DELETE request, following any subsequent redirect.
132
+ # See +request_via_redirect+ for more information.
133
+ def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
134
+ request_via_redirect(:delete, path, parameters, headers_or_env)
135
+ end
136
+ end
137
+
138
+ # An instance of this class represents a set of requests and responses
139
+ # performed sequentially by a test process. Because you can instantiate
140
+ # multiple sessions and run them side-by-side, you can also mimic (to some
141
+ # limited extent) multiple simultaneous users interacting with your system.
142
+ #
143
+ # Typically, you will instantiate a new session using
144
+ # IntegrationTest#open_session, rather than instantiating
145
+ # Integration::Session directly.
146
+ class Session
147
+ DEFAULT_HOST = "www.example.com"
148
+
149
+ include MiniTest::Assertions
150
+ include TestProcess, RequestHelpers, Assertions
151
+
152
+ %w( status status_message headers body redirect? ).each do |method|
153
+ delegate method, :to => :response, :allow_nil => true
154
+ end
155
+
156
+ %w( path ).each do |method|
157
+ delegate method, :to => :request, :allow_nil => true
158
+ end
159
+
160
+ # The hostname used in the last request.
161
+ def host
162
+ @host || DEFAULT_HOST
163
+ end
164
+ attr_writer :host
165
+
166
+ # The remote_addr used in the last request.
167
+ attr_accessor :remote_addr
168
+
169
+ # The Accept header to send.
170
+ attr_accessor :accept
171
+
172
+ # A map of the cookies returned by the last response, and which will be
173
+ # sent with the next request.
174
+ def cookies
175
+ _mock_session.cookie_jar
176
+ end
177
+
178
+ # A reference to the controller instance used by the last request.
179
+ attr_reader :controller
180
+
181
+ # A reference to the request instance used by the last request.
182
+ attr_reader :request
183
+
184
+ # A reference to the response instance used by the last request.
185
+ attr_reader :response
186
+
187
+ # A running counter of the number of requests processed.
188
+ attr_accessor :request_count
189
+
190
+ include ActionDispatch::Routing::UrlFor
191
+
192
+ # Create and initialize a new Session instance.
193
+ def initialize(app)
194
+ super()
195
+ @app = app
196
+
197
+ # If the app is a Rails app, make url_helpers available on the session
198
+ # This makes app.url_for and app.foo_path available in the console
199
+ if app.respond_to?(:routes)
200
+ singleton_class.class_eval do
201
+ include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
202
+ include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
203
+ end
204
+ end
205
+
206
+ reset!
207
+ end
208
+
209
+ def url_options
210
+ @url_options ||= default_url_options.dup.tap do |url_options|
211
+ url_options.reverse_merge!(controller.url_options) if controller
212
+
213
+ if @app.respond_to?(:routes) && @app.routes.respond_to?(:default_url_options)
214
+ url_options.reverse_merge!(@app.routes.default_url_options)
215
+ end
216
+
217
+ url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
218
+ end
219
+ end
220
+
221
+ # Resets the instance. This can be used to reset the state information
222
+ # in an existing session instance, so it can be used from a clean-slate
223
+ # condition.
224
+ #
225
+ # session.reset!
226
+ def reset!
227
+ @https = false
228
+ @controller = @request = @response = nil
229
+ @_mock_session = nil
230
+ @request_count = 0
231
+ @url_options = nil
232
+
233
+ self.host = DEFAULT_HOST
234
+ self.remote_addr = "127.0.0.1"
235
+ self.accept = "text/xml,application/xml,application/xhtml+xml," +
236
+ "text/html;q=0.9,text/plain;q=0.8,image/png," +
237
+ "*/*;q=0.5"
238
+
239
+ unless defined? @named_routes_configured
240
+ # the helpers are made protected by default--we make them public for
241
+ # easier access during testing and troubleshooting.
242
+ @named_routes_configured = true
243
+ end
244
+ end
245
+
246
+ # Specify whether or not the session should mimic a secure HTTPS request.
247
+ #
248
+ # session.https!
249
+ # session.https!(false)
250
+ def https!(flag = true)
251
+ @https = flag
252
+ end
253
+
254
+ # Return +true+ if the session is mimicking a secure HTTPS request.
255
+ #
256
+ # if session.https?
257
+ # ...
258
+ # end
259
+ def https?
260
+ @https
261
+ end
262
+
263
+ # Set the host name to use in the next request.
264
+ #
265
+ # session.host! "www.example.com"
266
+ alias :host! :host=
267
+
268
+ private
269
+ def _mock_session
270
+ @_mock_session ||= Rack::MockSession.new(@app, host)
271
+ end
272
+
273
+ # Performs the actual request.
274
+ def process(method, path, parameters = nil, headers_or_env = nil)
275
+ if path =~ %r{://}
276
+ location = URI.parse(path)
277
+ https! URI::HTTPS === location if location.scheme
278
+ host! "#{location.host}:#{location.port}" if location.host
279
+ path = location.query ? "#{location.path}?#{location.query}" : location.path
280
+ end
281
+
282
+ unless ActionController::Base < ActionController::Testing
283
+ ActionController::Base.class_eval do
284
+ include ActionController::Testing
285
+ end
286
+ end
287
+
288
+ hostname, port = host.split(':')
289
+
290
+ env = {
291
+ :method => method,
292
+ :params => parameters,
293
+
294
+ "SERVER_NAME" => hostname,
295
+ "SERVER_PORT" => port || (https? ? "443" : "80"),
296
+ "HTTPS" => https? ? "on" : "off",
297
+ "rack.url_scheme" => https? ? "https" : "http",
298
+
299
+ "REQUEST_URI" => path,
300
+ "HTTP_HOST" => host,
301
+ "REMOTE_ADDR" => remote_addr,
302
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
303
+ "HTTP_ACCEPT" => accept
304
+ }
305
+ # this modifies the passed env directly
306
+ Http::Headers.new(env).merge!(headers_or_env || {})
307
+
308
+ session = Rack::Test::Session.new(_mock_session)
309
+
310
+ env.merge!(env)
311
+
312
+ # NOTE: rack-test v0.5 doesn't build a default uri correctly
313
+ # Make sure requested path is always a full uri
314
+ uri = URI.parse('/')
315
+ uri.scheme ||= env['rack.url_scheme']
316
+ uri.host ||= env['SERVER_NAME']
317
+ uri.port ||= env['SERVER_PORT'].try(:to_i)
318
+ uri += path
319
+
320
+ session.request(uri.to_s, env)
321
+
322
+ @request_count += 1
323
+ @request = ActionDispatch::Request.new(session.last_request.env)
324
+ response = _mock_session.last_response
325
+ @response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body)
326
+ @html_document = nil
327
+ @url_options = nil
328
+
329
+ @controller = session.last_request.env['action_controller.instance']
330
+
331
+ return response.status
332
+ end
333
+ end
334
+
335
+ module Runner
336
+ include ActionDispatch::Assertions
337
+
338
+ def app
339
+ @app ||= nil
340
+ end
341
+
342
+ # Reset the current session. This is useful for testing multiple sessions
343
+ # in a single test case.
344
+ def reset!
345
+ @integration_session = Integration::Session.new(app)
346
+ end
347
+
348
+ <<<<<<< HEAD
349
+ %w(get post patch put head delete options cookies assigns
350
+ =======
351
+ %w(get post put head delete cookies assigns
352
+ >>>>>>> parent of ad46884... Integration tests support the OPTIONS http method
353
+ xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
354
+ define_method(method) do |*args|
355
+ reset! unless integration_session
356
+ # reset the html_document variable, but only for new get/post calls
357
+ @html_document = nil unless method == 'cookies' || method == 'assigns'
358
+ integration_session.__send__(method, *args).tap do
359
+ copy_session_variables!
360
+ end
361
+ end
362
+ end
363
+
364
+ # Open a new session instance. If a block is given, the new session is
365
+ # yielded to the block before being returned.
366
+ #
367
+ # session = open_session do |sess|
368
+ # sess.extend(CustomAssertions)
369
+ # end
370
+ #
371
+ # By default, a single session is automatically created for you, but you
372
+ # can use this method to open multiple sessions that ought to be tested
373
+ # simultaneously.
374
+ def open_session(app = nil)
375
+ dup.tap do |session|
376
+ yield session if block_given?
377
+ end
378
+ end
379
+
380
+ # Copy the instance variables from the current session instance into the
381
+ # test instance.
382
+ def copy_session_variables! #:nodoc:
383
+ return unless integration_session
384
+ %w(controller response request).each do |var|
385
+ instance_variable_set("@#{var}", @integration_session.__send__(var))
386
+ end
387
+ end
388
+
389
+ def default_url_options
390
+ reset! unless integration_session
391
+ integration_session.default_url_options
392
+ end
393
+
394
+ def default_url_options=(options)
395
+ reset! unless integration_session
396
+ integration_session.default_url_options = options
397
+ end
398
+
399
+ def respond_to?(method, include_private = false)
400
+ integration_session.respond_to?(method, include_private) || super
401
+ end
402
+
403
+ # Delegate unhandled messages to the current session instance.
404
+ def method_missing(sym, *args, &block)
405
+ reset! unless integration_session
406
+ if integration_session.respond_to?(sym)
407
+ integration_session.__send__(sym, *args, &block).tap do
408
+ copy_session_variables!
409
+ end
410
+ else
411
+ super
412
+ end
413
+ end
414
+
415
+ private
416
+ def integration_session
417
+ @integration_session ||= nil
418
+ end
419
+ end
420
+ end
421
+
422
+ # An integration test spans multiple controllers and actions,
423
+ # tying them all together to ensure they work together as expected. It tests
424
+ # more completely than either unit or functional tests do, exercising the
425
+ # entire stack, from the dispatcher to the database.
426
+ #
427
+ # At its simplest, you simply extend <tt>IntegrationTest</tt> and write your tests
428
+ # using the get/post methods:
429
+ #
430
+ # require "test_helper"
431
+ #
432
+ # class ExampleTest < ActionDispatch::IntegrationTest
433
+ # fixtures :people
434
+ #
435
+ # def test_login
436
+ # # get the login page
437
+ # get "/login"
438
+ # assert_equal 200, status
439
+ #
440
+ # # post the login and follow through to the home page
441
+ # post "/login", username: people(:jamis).username,
442
+ # password: people(:jamis).password
443
+ # follow_redirect!
444
+ # assert_equal 200, status
445
+ # assert_equal "/home", path
446
+ # end
447
+ # end
448
+ #
449
+ # However, you can also have multiple session instances open per test, and
450
+ # even extend those instances with assertions and methods to create a very
451
+ # powerful testing DSL that is specific for your application. You can even
452
+ # reference any named routes you happen to have defined.
453
+ #
454
+ # require "test_helper"
455
+ #
456
+ # class AdvancedTest < ActionDispatch::IntegrationTest
457
+ # fixtures :people, :rooms
458
+ #
459
+ # def test_login_and_speak
460
+ # jamis, david = login(:jamis), login(:david)
461
+ # room = rooms(:office)
462
+ #
463
+ # jamis.enter(room)
464
+ # jamis.speak(room, "anybody home?")
465
+ #
466
+ # david.enter(room)
467
+ # david.speak(room, "hello!")
468
+ # end
469
+ #
470
+ # private
471
+ #
472
+ # module CustomAssertions
473
+ # def enter(room)
474
+ # # reference a named route, for maximum internal consistency!
475
+ # get(room_url(id: room.id))
476
+ # assert(...)
477
+ # ...
478
+ # end
479
+ #
480
+ # def speak(room, message)
481
+ # xml_http_request "/say/#{room.id}", message: message
482
+ # assert(...)
483
+ # ...
484
+ # end
485
+ # end
486
+ #
487
+ # def login(who)
488
+ # open_session do |sess|
489
+ # sess.extend(CustomAssertions)
490
+ # who = people(who)
491
+ # sess.post "/login", username: who.username,
492
+ # password: who.password
493
+ # assert(...)
494
+ # end
495
+ # end
496
+ # end
497
+ class IntegrationTest < ActiveSupport::TestCase
498
+ include Integration::Runner
499
+ include ActionController::TemplateAssertions
500
+ include ActionDispatch::Routing::UrlFor
501
+
502
+ @@app = nil
503
+
504
+ def self.app
505
+ if !@@app && !ActionDispatch.test_app
506
+ ActiveSupport::Deprecation.warn "Rails application fallback is deprecated and no longer works, please set ActionDispatch.test_app"
507
+ end
508
+
509
+ @@app || ActionDispatch.test_app
510
+ end
511
+
512
+ def self.app=(app)
513
+ @@app = app
514
+ end
515
+
516
+ def app
517
+ super || self.class.app
518
+ end
519
+
520
+ def url_options
521
+ reset! unless integration_session
522
+ integration_session.url_options
523
+ end
524
+ end
525
+ end