Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting rack.session to hash by default when using to_rack #985

Open
garytaylor opened this issue Jul 28, 2022 · 5 comments
Open

Setting rack.session to hash by default when using to_rack #985

garytaylor opened this issue Jul 28, 2022 · 5 comments

Comments

@garytaylor
Copy link

In rails 7, if there is no session in the request, it now defaults to an object that responds to enabled?

However, because rack.session is set to {} in webmock (WebMock::RackResponse#session), when webmock us used in a way that mounts this rails app (using to_rack), this causes a failure in the following line in the rails app

ActionDispatch::Flash::RequestMethods#commit_flash

return unless session.enabled?

where 'session' is defined as

      def session
        fetch_header(RACK_SESSION) do |k|
          set_header RACK_SESSION, default_session
        end
      end

and fetch_header will return the session if already defined in env['rack.session'] therefore 'default_session' is not being called. The return value is therefore an empty hash which does not respond to enabled?

I am not sure if this is really a webmock issue or if rails are being a bit naughty setting rack.session to anything but a hash

@bblimke
Copy link
Owner

bblimke commented Aug 2, 2022

perhaps WebMock::RackResponse#session should return that default_session instead of returning {} ?

@jgraichen
Copy link

I have a single file test case here:

# frozen_string_literal: true

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'

  gem 'json'
  gem 'rails', '~> 7.0'
  gem 'rspec'
  gem 'webmock'
end

ENV['RAILS_ENV'] ||= 'test'

require 'action_controller/railtie'

class Application < Rails::Application
  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger

  config.action_dispatch.show_exceptions = :none

  routes.draw do
    get '/' => 'test#index'
  end
end

class TestController < ActionController::Base
  include Rails.application.routes.url_helpers

  def index
    render plain: 'Hello World'
  end
end

require 'net/http'
require 'rspec/autorun'
require 'webmock/rspec'

WebMock.enable!

RSpec.configure do |config|
  config.full_backtrace = true
end

RSpec.describe do
  it do
    stub_request(:any, /example\.org/)
      .to_rack(Rails.application)

    response = Net::HTTP.get('example.org', '/')
    expect(response).to eq 'Hello World'
  end
end

Currently, this fails:

Failures:

  1) 
     Failure/Error: response = Net::HTTP.get('example.org', '/')
     
     NoMethodError:
       undefined method `enabled?' for an instance of Hash
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/flash.rb:72:in `commit_flash'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_controller/metal.rb:253:in `dispatch'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_controller/metal.rb:335:in `dispatch'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/routing/route_set.rb:67:in `dispatch'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/routing/route_set.rb:50:in `serve'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/journey/router.rb:53:in `block in serve'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/journey/router.rb:133:in `block in find_routes'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/journey/router.rb:126:in `each'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/journey/router.rb:126:in `find_routes'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/journey/router.rb:34:in `serve'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/routing/route_set.rb:896:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/tempfile_reaper.rb:20:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/etag.rb:29:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/conditional_get.rb:31:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/head.rb:15:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/http/permissions_policy.rb:38:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/http/content_security_policy.rb:36:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/cookies.rb:704:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/callbacks.rb:31:in `block in call'
     # ruby/gems/3.3.0/gems/activesupport-7.2.1.1/lib/active_support/callbacks.rb:101:in `run_callbacks'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/callbacks.rb:30:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/executor.rb:16:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/debug_exceptions.rb:31:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/show_exceptions.rb:32:in `call'
     # ruby/gems/3.3.0/gems/railties-7.2.1.1/lib/rails/rack/logger.rb:41:in `call_app'
     # ruby/gems/3.3.0/gems/railties-7.2.1.1/lib/rails/rack/logger.rb:29:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/remote_ip.rb:96:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/request_id.rb:33:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/method_override.rb:28:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/runtime.rb:24:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/executor.rb:16:in `call'
     # ruby/gems/3.3.0/gems/actionpack-7.2.1.1/lib/action_dispatch/middleware/static.rb:27:in `call'
     # ruby/gems/3.3.0/gems/rack-3.1.8/lib/rack/sendfile.rb:114:in `call'
     # ruby/gems/3.3.0/gems/railties-7.2.1.1/lib/rails/engine.rb:535:in `call'
     # ruby/gems/3.3.0/gems/webmock-3.24.0/lib/webmock/rack_response.rb:12:in `evaluate'
     # ruby/gems/3.3.0/gems/webmock-3.24.0/lib/webmock/stub_registry.rb:80:in `evaluate_response_for_request'
     # ruby/gems/3.3.0/gems/webmock-3.24.0/lib/webmock/stub_registry.rb:67:in `response_for_request'
     # ruby/gems/3.3.0/gems/webmock-3.24.0/lib/webmock/http_lib_adapters/net_http.rb:76:in `request'
     # ruby/3.3.0/net/http.rb:2177:in `request_get'
     # ruby/3.3.0/net/http.rb:817:in `block in get_response'
     # ruby/gems/3.3.0/gems/webmock-3.24.0/lib/webmock/http_lib_adapters/net_http.rb:116:in `start_without_connect'
     # ruby/gems/3.3.0/gems/webmock-3.24.0/lib/webmock/http_lib_adapters/net_http.rb:143:in `start'
     # ruby/3.3.0/net/http.rb:816:in `get_response'
     # ruby/3.3.0/net/http.rb:803:in `get'
     # test.rb:52:in `block (2 levels) in <main>'

@jgraichen
Copy link

perhaps WebMock::RackResponse#session should return that default_session instead of returning {} ?

Does WebMock has to set a "rack.session" at all?

@bblimke
Copy link
Owner

bblimke commented Oct 25, 2024

@jgraichen possibly not. I guess the intention was to have a complete Rack app with session, in case session is not set by the app like Rails does.

@jgraichen
Copy link

jgraichen commented Oct 28, 2024

@bblimke Thanks! I wasn't sure if that was intentional, e.g. to inspect the session later on.

Would it be possible not to set 'rack.session' at all? As far as I know, a rack server (such as puma) wouldn't set that either. So, if no middleware defines it (such as rails or rack-session) it should be present.

That would help with another error I got on a Rails API-only app. It doesn't really have any sessions, but tests fail completely because any present 'rack.session' break some code, or the test instrumentation does not initialize a stub session.

I had to change the patch from above to completely strip 'rack.session' to get specs on the app passing again:

module WebmockSessionPatch
  def build_rack_env(request)
    super.tap do |env|
      env.delete('rack.session')
      env.delete('rack.session.options')
    end
  end

  WebMock::RackResponse.prepend(WebmockSessionPatch)
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants