diff --git a/.github/workflows/minitest.yml b/.github/workflows/minitest.yml index a7c8f71..31114ae 100644 --- a/.github/workflows/minitest.yml +++ b/.github/workflows/minitest.yml @@ -2,9 +2,9 @@ name: Minitest on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: test: diff --git a/Gemfile b/Gemfile index 1004d4c..2523fba 100644 --- a/Gemfile +++ b/Gemfile @@ -3,5 +3,11 @@ source 'https://rubygems.org' rails_version = ENV['RAILS_VERSION'] gem 'actionpack', rails_version +# Ruby 3.4+ removed these from stdlib, but older Rails versions need them +if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0') + gem 'mutex_m' + gem 'bigdecimal' +end + # Specify your gem's dependencies in auto-session-timeout.gemspec gemspec diff --git a/test/auto_session_timeout_test.rb b/test/auto_session_timeout_test.rb index 808a6f4..d60cbdd 100644 --- a/test/auto_session_timeout_test.rb +++ b/test/auto_session_timeout_test.rb @@ -32,6 +32,68 @@ def user_session_path end end + # Stub for testing session_expired? and signing_in? + class StubSession < Hash + end + + class StubRequest + attr_accessor :env, :original_url + + def initialize(path_info: "/", request_method: "GET", original_url: "http://example.com/") + @env = { + "PATH_INFO" => path_info, + "REQUEST_METHOD" => request_method + } + @original_url = original_url + end + end + + class StubControllerWithSession + include AutoSessionTimeout + + attr_accessor :session, :request, :session_was_reset + + def initialize + @session = StubSession.new + @request = StubRequest.new + @session_was_reset = false + end + + def main_app + MainApp.new + end + + def user_session_path + "/users/sign_in" + end + + def reset_session + @session_was_reset = true + @session.clear + end + end + + class StubUser + attr_accessor :auto_timeout + + def initialize(auto_timeout: nil) + @auto_timeout = auto_timeout + end + + def respond_to?(method) + method.to_sym == :auto_timeout || super + end + end + + class StubControllerWithCurrentUser < StubControllerWithSession + attr_accessor :current_user + + def initialize + super + @current_user = nil + end + end + describe "#active_url" do it "returns main_app.active_url when available" do controller = StubController.new @@ -56,4 +118,166 @@ def user_session_path end end + describe "#session_expired?" do + it "returns true when session has expired" do + controller = StubControllerWithSession.new + controller.session[:auto_session_expires_at] = Time.now - 60 + assert controller.send(:session_expired?, controller) + end + + it "returns false when session has not expired" do + controller = StubControllerWithSession.new + controller.session[:auto_session_expires_at] = Time.now + 60 + refute controller.send(:session_expired?, controller) + end + + it "returns falsy when no expiration is set" do + controller = StubControllerWithSession.new + refute controller.send(:session_expired?, controller) + end + end + + describe "#signing_in?" do + it "returns true for POST to sign_in_path" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/users/sign_in", request_method: "POST") + assert controller.send(:signing_in?, controller) + end + + it "returns false for GET to sign_in_path" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/users/sign_in", request_method: "GET") + refute controller.send(:signing_in?, controller) + end + + it "returns false for POST to other paths" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/other", request_method: "POST") + refute controller.send(:signing_in?, controller) + end + end + + describe "#handle_session_reset" do + it "calls reset_session on the controller" do + controller = StubControllerWithSession.new + controller.session[:auto_session_expires_at] = Time.now + 60 + controller.session[:user_id] = 123 + + refute controller.session_was_reset + controller.send(:handle_session_reset, controller) + + assert controller.session_was_reset + end + end + + describe "session timeout behavior" do + # Helper to simulate the before_action logic + def simulate_before_action(controller, seconds = nil) + if controller.send(:session_expired?, controller) && !controller.send(:signing_in?, controller) + controller.send(:handle_session_reset, controller) + else + unless controller.request.original_url.start_with?(controller.send(:active_url)) + current_user = controller.respond_to?(:current_user) ? controller.current_user : nil + offset = seconds || (current_user&.respond_to?(:auto_timeout) ? current_user.auto_timeout : nil) + controller.session[:auto_session_expires_at] = Time.now + offset if offset && offset > 0 + end + end + end + + it "resets session when expired and not signing in" do + controller = StubControllerWithSession.new + controller.session[:auto_session_expires_at] = Time.now - 60 + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET") + + simulate_before_action(controller) + + assert controller.session_was_reset + end + + it "does not reset session when signing in even if expired" do + controller = StubControllerWithSession.new + controller.session[:auto_session_expires_at] = Time.now - 60 + controller.request = StubRequest.new(path_info: "/users/sign_in", request_method: "POST") + + simulate_before_action(controller) + + refute controller.session_was_reset + end + + it "does not reset session when not expired" do + controller = StubControllerWithSession.new + controller.session[:auto_session_expires_at] = Time.now + 60 + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET") + + simulate_before_action(controller, 300) + + refute controller.session_was_reset + end + + it "sets session expiration time with given seconds" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET", original_url: "http://example.com/dashboard") + + before_time = Time.now + simulate_before_action(controller, 300) + after_time = Time.now + + assert controller.session[:auto_session_expires_at] >= before_time + 300 + assert controller.session[:auto_session_expires_at] <= after_time + 300 + end + + it "uses current_user.auto_timeout when available" do + controller = StubControllerWithCurrentUser.new + controller.current_user = StubUser.new(auto_timeout: 600) + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET", original_url: "http://example.com/dashboard") + + before_time = Time.now + simulate_before_action(controller) + after_time = Time.now + + assert controller.session[:auto_session_expires_at] >= before_time + 600 + assert controller.session[:auto_session_expires_at] <= after_time + 600 + end + + it "does not update expiration for active_url requests" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new( + path_info: "/active", + request_method: "GET", + original_url: "http://example.com/active" + ) + + simulate_before_action(controller, 300) + + assert_nil controller.session[:auto_session_expires_at] + end + + it "does not set expiration when seconds is nil and no user timeout" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET", original_url: "http://example.com/dashboard") + + simulate_before_action(controller, nil) + + assert_nil controller.session[:auto_session_expires_at] + end + + it "does not set expiration when seconds is zero" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET", original_url: "http://example.com/dashboard") + + simulate_before_action(controller, 0) + + assert_nil controller.session[:auto_session_expires_at] + end + + it "does not set expiration when seconds is negative" do + controller = StubControllerWithSession.new + controller.request = StubRequest.new(path_info: "/dashboard", request_method: "GET", original_url: "http://example.com/dashboard") + + simulate_before_action(controller, -10) + + assert_nil controller.session[:auto_session_expires_at] + end + end + end