Search code examples
ruby-on-railscookiesintegration-testingruby-on-rails-2

Setting more than one cookie in integration test causes NoMethodError after upgrading rails 2.3


This all worked fine in rails 2.3.5, but when a contractor firm upgraded directly to 2.3.14 suddenly all the integration tests were saying:

NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]

I have a before_filter on my ApplicationController that sets a bunch of cookies for some javascript to use, and I found that if I comment out all but one of the lines that sets cookie values, it works fine, and it doesn't matter which line I leave in.

before_filter :set_cookies

def set_cookies
  cookies['logged_in'] = (logged_in ? 'y' : 'n')
  cookies['gets_premium'] = (gets_premium ? 'y' : 'n')
  cookies['is_admin'] = (is_admin ? 'y' : 'n')
end

If only one of these lines is active, everything is fine in the integration test, otherwise I get the error above. For example, consider the following test / response:

test "foo" do
  get '/'
end


$ ruby -I"lib:test" test/integration/foo_test.rb -n test_foo -v
Loaded suite test/integration/foo_test
Started
test_foo(FooTest): E

Finished in 5.112648 seconds.

  1) Error:
test_foo(FooTest):
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
    test/integration/foo_test.rb:269:in `test_foo'

1 tests, 0 assertions, 0 failures, 1 errors

But if any two of those cookie setting lines are commented out, I get:

$ ruby -I"lib:test" test/integration/foo_test.rb -n test_foo -v
Loaded suite test/integration/foo_test
Started
test_foo(FooTest): .

Finished in 1.780388 seconds.

1 tests, 0 assertions, 0 failures, 0 errors

The website running in development and production mode works fine - this is just a matter of getting the tests passing. Also, with debugging output I have verified that the error does not get thrown in the method where the cookies get set, that all executes fine, it's somewhere later that the error happens (but the backtrace doesn't tell me where)


Solution

  • This turned out to be a bug in how rack rack writes cookies to the client along with the session cookie. Rack was including double newlines and omitting semi-colons.

    Browsers like firefox can handle mildly malformed cookie data, but the integration test client couldn't.

    To fix this I had to rewrite the cookie header before sending it to the client.

    In environment.rb:

    require 'rack_rails_cookie_header_hack'
    

    And in lib/rack_rails_cookie_header_hack.rb:

    class RackRailsCookieHeaderHack
    
      def initialize(app)
        @app = app
      end
    
      def call(env)
        status, headers, body = @app.call(env)
        if headers['Set-Cookie']
          cookies = headers['Set-Cookie']
          cookies = cookies.split("\n") if is_str = cookies.is_a?(String)
          if cookies.respond_to?(:collect!)
            cookies.collect! { |h| h.strip }
            cookies.delete_if { |h| h.empty? }
            cookies.collect! { |h| h.include?(';') ? h : h + ';' }
          end
          headers['Set-Cookie'] = is_str ? cookies.join("\n").strip : cookies
        end
        [status, headers, body]
      end
    end
    

    Sorry the sources aren't articulated, I actually fixed this awhile ago and came across this questions and figured I'd post my patch.