Skip to content
This repository
Browse code

Improve testing of cookies in functional tests:

- cookies can be set using string or symbol keys
- cookies are preserved across calls to get, post, etc.
- cookie names and values are escaped
- cookies can be cleared using @request.cookies.clear

[#6272 state:resolved]
  • Loading branch information...
commit e2523ff68309f444cd052031c1fe8a3030d2865a 1 parent 2437c78
Andrew White authored March 06, 2011
11  actionpack/lib/action_controller/test_case.rb
@@ -171,6 +171,10 @@ def assign_parameters(routes, controller_path, action, parameters = {})
171 171
     end
172 172
 
173 173
     def recycle!
  174
+      write_cookies!
  175
+      @env.delete('HTTP_COOKIE') if @cookies.blank?
  176
+      @env.delete('action_dispatch.cookies')
  177
+      @cookies = nil
174 178
       @formats = nil
175 179
       @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
176 180
       @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
@@ -297,7 +301,11 @@ def exists?; true; end
297 301
   # and cookies, though. For sessions, you just do:
298 302
   #
299 303
   #   @request.session[:key] = "value"
300  
-  #   @request.cookies["key"] = "value"
  304
+  #   @request.cookies[:key] = "value"
  305
+  #
  306
+  # To clear the cookies for a test just clear the request's cookies hash:
  307
+  #
  308
+  #   @request.cookies.clear
301 309
   #
302 310
   # == Testing named routes
303 311
   #
@@ -411,6 +419,7 @@ def process(action, parameters = nil, session = nil, flash = nil, http_method =
411 419
         Base.class_eval { include Testing }
412 420
         @controller.process_with_new_base_test(@request, @response)
413 421
         @request.session.delete('flash') if @request.session['flash'].blank?
  422
+        @request.cookies.merge!(@response.cookies)
414 423
         @response
415 424
       end
416 425
 
2  actionpack/lib/action_dispatch/testing/test_process.rb
@@ -22,7 +22,7 @@ def flash
22 22
     end
23 23
 
24 24
     def cookies
25  
-      @request.cookies.merge(@response.cookies)
  25
+      @request.cookies.merge(@response.cookies).with_indifferent_access
26 26
     end
27 27
 
28 28
     def redirect_to_url
7  actionpack/lib/action_dispatch/testing/test_request.rb
... ...
@@ -1,5 +1,6 @@
1 1
 require 'active_support/core_ext/object/blank'
2 2
 require 'active_support/core_ext/hash/reverse_merge'
  3
+require 'rack/utils'
3 4
 
4 5
 module ActionDispatch
5 6
   class TestRequest < Request
@@ -76,10 +77,14 @@ def cookies
76 77
     private
77 78
       def write_cookies!
78 79
         unless @cookies.blank?
79  
-          @env['HTTP_COOKIE'] = @cookies.map { |name, value| "#{name}=#{value};" }.join(' ')
  80
+          @env['HTTP_COOKIE'] = @cookies.map { |name, value| escape_cookie(name, value) }.join('; ')
80 81
         end
81 82
       end
82 83
 
  84
+      def escape_cookie(name, value)
  85
+        "#{Rack::Utils.escape(name)}=#{Rack::Utils.escape(value)}"
  86
+      end
  87
+
83 88
       def delete_nil_values!
84 89
         @env.delete_if { |k, v| v.nil? }
85 90
       end
83  actionpack/test/dispatch/cookies_test.rb
@@ -94,6 +94,30 @@ def delete_cookie_with_domain
94 94
       cookies.delete(:user_name, :domain => :all)
95 95
       head :ok
96 96
     end
  97
+
  98
+    def symbol_key
  99
+      cookies[:user_name] = "david"
  100
+      head :ok
  101
+    end
  102
+
  103
+    def string_key
  104
+      cookies['user_name'] = "david"
  105
+      head :ok
  106
+    end
  107
+
  108
+    def symbol_key_mock
  109
+      cookies[:user_name] = "david" if cookies[:user_name] == "andrew"
  110
+      head :ok
  111
+    end
  112
+
  113
+    def string_key_mock
  114
+      cookies['user_name'] = "david" if cookies['user_name'] == "andrew"
  115
+      head :ok
  116
+    end
  117
+
  118
+    def noop
  119
+      head :ok
  120
+    end
97 121
   end
98 122
 
99 123
   tests TestController
@@ -291,6 +315,65 @@ def test_deleting_cookie_with_all_domain_option
291 315
     assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
292 316
   end
293 317
 
  318
+  def test_cookies_hash_is_indifferent_access
  319
+    [:symbol_key, :string_key].each do |cookie_key|
  320
+      get cookie_key
  321
+      assert_equal "david", cookies[:user_name]
  322
+      assert_equal "david", cookies['user_name']
  323
+    end
  324
+  end
  325
+
  326
+  def test_setting_request_cookies_is_indifferent_access
  327
+    @request.cookies.clear
  328
+    @request.cookies[:user_name] = "andrew"
  329
+    get :string_key_mock
  330
+    assert_equal "david", cookies[:user_name]
  331
+
  332
+    @request.cookies.clear
  333
+    @request.cookies['user_name'] = "andrew"
  334
+    get :symbol_key_mock
  335
+    assert_equal "david", cookies['user_name']
  336
+  end
  337
+
  338
+  def test_cookies_retained_across_requests
  339
+    get :symbol_key
  340
+    assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"]
  341
+    assert_equal "david", cookies[:user_name]
  342
+
  343
+    get :noop
  344
+    assert_nil @response.headers["Set-Cookie"]
  345
+    assert_equal "user_name=david", @request.env['HTTP_COOKIE']
  346
+    assert_equal "david", cookies[:user_name]
  347
+
  348
+    get :noop
  349
+    assert_nil @response.headers["Set-Cookie"]
  350
+    assert_equal "user_name=david", @request.env['HTTP_COOKIE']
  351
+    assert_equal "david", cookies[:user_name]
  352
+  end
  353
+
  354
+  def test_cookies_can_be_cleared
  355
+    get :symbol_key
  356
+    assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"]
  357
+    assert_equal "david", cookies[:user_name]
  358
+
  359
+    @request.cookies.clear
  360
+    get :noop
  361
+    assert_nil @response.headers["Set-Cookie"]
  362
+    assert_nil @request.env['HTTP_COOKIE']
  363
+    assert_nil cookies[:user_name]
  364
+
  365
+    get :symbol_key
  366
+    assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"]
  367
+    assert_equal "david", cookies[:user_name]
  368
+  end
  369
+
  370
+  def test_cookies_are_escaped
  371
+    @request.cookies[:user_ids] = '1;2'
  372
+    get :noop
  373
+    assert_equal "user_ids=1%3B2", @request.env['HTTP_COOKIE']
  374
+    assert_equal "1;2", cookies[:user_ids]
  375
+  end
  376
+
294 377
   private
295 378
     def assert_cookie_header(expected)
296 379
       header = @response.headers["Set-Cookie"]
4  actionpack/test/dispatch/test_request_test.rb
@@ -36,10 +36,10 @@ class TestRequestTest < ActiveSupport::TestCase
36 36
 
37 37
     req.cookies["user_name"] = "david"
38 38
     assert_equal({"user_name" => "david"}, req.cookies)
39  
-    assert_equal "user_name=david;", req.env["HTTP_COOKIE"]
  39
+    assert_equal "user_name=david", req.env["HTTP_COOKIE"]
40 40
 
41 41
     req.cookies["login"] = "XJ-122"
42 42
     assert_equal({"user_name" => "david", "login" => "XJ-122"}, req.cookies)
43  
-    assert_equal %w(login=XJ-122 user_name=david), req.env["HTTP_COOKIE"].split(/; ?/).sort
  43
+    assert_equal %w(login=XJ-122 user_name=david), req.env["HTTP_COOKIE"].split(/; /).sort
44 44
   end
45 45
 end

4 notes on commit e2523ff

James Tucker

Is this reliably backwards compatible? If not, this shouldn't go in a patchlevel release, it may break all user sessions.

Michael Koziarski
Owner

it only changes the test process stuff so it's not likely to break a production app at least.

Is it causing test failures for you guys?

Andrew White
Owner

It should make it more like a running the app for real since it's using the HTTP_COOKIE header rather than a cached hash between calls to process.

José Valim
Owner

I am also -1 on applying this on a patch level release. It may not break an application, but it may break tests on updating, or worse, tools that are built on top of Rails Testing structure (for instance Rspec). Patch level == bug fixes only.

Please sign in to comment.
Something went wrong with that request. Please try again.