L0ne
L0ne

Reputation: 590

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)

Upvotes: 2

Views: 618

Answers (3)

mastaBlasta
mastaBlasta

Reputation: 5850

Old forgotten issues...

I haven't tested Lone's fix but he has correctly identified the problem. If you catch the exception and print using exception.backtrace, you'll see that the problem is caused by gems/actionpack/lib/action_controller/integration.rb:329

The offending code is this:

cookies.each do |cookie|
  name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
  @cookies[name] = value
end

If you're like me, and you're only interested in some super quick integration tests, and don't care too much about future maintainability (since it's rails 2), then you can just add a conditional filter in that method

cookies.each do |cookie|
  unless cookie.blank?
    name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
    @cookies[name] = value
  end
end

and problem solved

Upvotes: 0

user1498852
user1498852

Reputation: 21

L0ne's answer works for me, but you also need to include this - it can go at the bottom of lib/rack_rails_cookie_header_hack.rb, providing you're requiring it at the bottom of your environment.rb file - ie after the Rails::Initializer has run:

ActionController::Dispatcher.middleware.insert_before(ActionController::Base.session_store, RackRailsCookieHeaderHack)

Upvotes: 2

L0ne
L0ne

Reputation: 590

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.

Upvotes: 2

Related Questions