diff --git a/lib/warden.rb b/lib/warden.rb index 777e0e2..5919fb7 100644 --- a/lib/warden.rb +++ b/lib/warden.rb @@ -39,6 +39,13 @@ def self.test_mode! while blk = Warden._on_next_request.shift blk.call(proxy) end + Warden._test_users.each_value do |(user, opts)| + opts = opts.dup + scope = opts[:scope] || proxy.config.default_scope + unless proxy.session_serializer.stored?(scope) + proxy.set_user(user, opts) + end + end end end end diff --git a/lib/warden/test/helpers.rb b/lib/warden/test/helpers.rb index cecbc1b..a14312e 100644 --- a/lib/warden/test/helpers.rb +++ b/lib/warden/test/helpers.rb @@ -16,10 +16,8 @@ def self.included(_base) # @see Warden::Proxy#set_user # @api public def login_as(user, opts = {}) - Warden.on_next_request do |proxy| - opts[:event] ||= :authentication - proxy.set_user(user, opts) - end + opts[:event] ||= :authentication + Warden._test_users[opts[:scope]] = [user, opts] end # Logs out a user from the session. @@ -28,6 +26,11 @@ def login_as(user, opts = {}) # @see Warden::Proxy#logout # @api public def logout(*scopes) + if scopes.empty? + Warden._test_users.clear + else + scopes.each { |s| Warden._test_users.delete(s) } + end Warden.on_next_request do |proxy| proxy.logout(*scopes) end diff --git a/lib/warden/test/warden_helpers.rb b/lib/warden/test/warden_helpers.rb index 9fa46ec..076376d 100644 --- a/lib/warden/test/warden_helpers.rb +++ b/lib/warden/test/warden_helpers.rb @@ -31,6 +31,7 @@ def on_next_request(&blk) # @api public def test_reset! _on_next_request.clear + _test_users.clear end # A container for the on_next_request items. @@ -39,6 +40,14 @@ def _on_next_request @_on_next_request ||= [] @_on_next_request end + + # A persistent store of test users keyed by scope. + # Unlike _on_next_request, these survive across multiple requests, + # preventing race conditions with background requests. + # @api private + def _test_users + @_test_users ||= {} + end end end end diff --git a/spec/warden/test/helpers_spec.rb b/spec/warden/test/helpers_spec.rb index 187bbd3..913027e 100644 --- a/spec/warden/test/helpers_spec.rb +++ b/spec/warden/test/helpers_spec.rb @@ -84,6 +84,79 @@ expect($captures).to eq([:run]) end + it "should persist authentication across multiple requests" do + user = "A User" + session = {} + login_as user + app = setup_rack(lambda { |e| + $captures << e['warden'].user + valid_response + }) + app.call(env_with_params("/", {}, "rack.session" => session)) + app.call(env_with_params("/", {}, "rack.session" => session)) + expect($captures).to eq(["A User", "A User"]) + end + + it "should not lose authentication when another session makes a concurrent request" do + alice = "Alice" + bob = "Bob" + + alice_session = {} + bob_session = {} + + app = setup_rack(lambda { |e| + $captures << e['warden'].user + valid_response + }) + + # 1. Alice logs in and visits a page — her session is established + login_as alice + app.call(env_with_params("/", {}, "rack.session" => alice_session)) + + # 2. Bob logs in + login_as bob + + # 3. A background request from Alice's session (e.g. Turbo Frame fetch) + app.call(env_with_params("/turbo-frame", {}, "rack.session" => alice_session)) + + # 4. Bob's session makes its first request + app.call(env_with_params("/", {}, "rack.session" => bob_session)) + + expect($captures).to eq(["Alice", "Alice", "Bob"]) + end + + it "should allow switching users with logout in between" do + session = {} + app = setup_rack(lambda { |e| + $captures << e['warden'].user + valid_response + }) + + login_as "Alice" + app.call(env_with_params("/", {}, "rack.session" => session)) + + logout + login_as "Bob" + app.call(env_with_params("/", {}, "rack.session" => session)) + + expect($captures).to eq(["Alice", "Bob"]) + end + + it "should clear persistent state on logout" do + user = "A User" + login_as user + expect(Warden._test_users).not_to be_empty + logout + expect(Warden._test_users).to be_empty + end + + it "should clear only the specified scope on logout" do + login_as "Default User" + login_as "Foo User", scope: :foo + logout :foo + expect(Warden._test_users.keys).to eq([nil]) + end + describe "#asset_paths" do it "should default asset_paths to anything asset path regex" do expect(Warden.asset_paths).to eq([/^\/assets\//] ) diff --git a/spec/warden/test/test_mode_spec.rb b/spec/warden/test/test_mode_spec.rb index ccb4471..3ebef37 100644 --- a/spec/warden/test/test_mode_spec.rb +++ b/spec/warden/test/test_mode_spec.rb @@ -63,6 +63,13 @@ expect($captures).to eq([]) end + it "should clear _test_users when test is reset" do + Warden._test_users[:default] = ["A User", { event: :authentication }] + expect(Warden._test_users).not_to be_empty + Warden.test_reset! + expect(Warden._test_users).to be_empty + end + context "asset requests" do it "should not execute on_next_request blocks if this is an asset request" do app = setup_rack(@app)