diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4c6630b..076a49b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,3 +39,26 @@ jobs:
bundler-cache: true
- name: Run RuboCop
run: bundle exec rubocop
+
+ generator-test:
+ runs-on: ubuntu-latest
+ needs: [test]
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.4'
+ bundler-cache: true
+
+ - name: Install Rails
+ run: gem install rails
+
+ - name: Install Playwright
+ run: npx playwright install --with-deps chromium
+
+ - name: Run generator test
+ env:
+ HEADLESS: 'true'
+ run: bin/test_generator
diff --git a/.gitignore b/.gitignore
index 5f8ce36..6c87ccd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,7 @@
Gemfile.lock
.playwright-mcp/
log/
+
+# Local development files
+plans/
+todos/
diff --git a/.rubocop.yml b/.rubocop.yml
index 63567a7..ba51bf2 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -58,6 +58,7 @@ Metrics/ClassLength:
Metrics/CyclomaticComplexity:
Exclude:
- 'lib/rails_simple_auth/models/concerns/**/*'
+ - 'lib/rails_simple_auth/controllers/concerns/**/*'
Metrics/PerceivedComplexity:
Exclude:
@@ -82,3 +83,17 @@ Rails/ReflectionClassName:
Style/SafeNavigationChainLength:
Enabled: false
+
+# Generator test files use Capybara naming convention
+Naming/PredicatePrefix:
+ Exclude:
+ - 'test/generator_test_files/**/*'
+
+# Generator test files are templates, not part of gem test suite
+Minitest/MultipleAssertions:
+ Max: 5
+
+# Allow generated test files to have longer files
+Metrics/ModuleLength:
+ Exclude:
+ - 'test/generator_test_files/**/*'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e80b8e9..e082b62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+
+- **Generator E2E test infrastructure** - CI pipeline now validates that generators produce working views
+ - `bin/test_generator` script creates fresh Rails app, runs generators, and executes E2E tests
+ - Reusable Page Object test templates in `test/generator_test_files/`
+ - GitHub Actions `generator-test` job runs after unit tests pass
+
+### Fixed
+
+- **Session model autoloading** - Moved `RailsSimpleAuth::Session` from `lib/` to `app/models/` for proper Rails engine autoloading
+
## [1.0.14] - 2026-01-20
### Fixed
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f7a6642..f27d4f9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,10 +21,32 @@ Thank you for your interest in contributing!
2. Fork the repository
3. Create a feature branch (`git checkout -b feature/my-feature`)
4. Write tests for your changes
-5. Ensure all tests pass (`bundle exec rake test`)
-6. Run linter (`bundle exec rubocop`)
-7. Update CHANGELOG.md under `[Unreleased]`
-8. Submit a pull request
+5. **Run local CI before submitting:**
+ ```bash
+ bin/ci
+ ```
+ This runs RuboCop and all unit tests.
+6. Update CHANGELOG.md under `[Unreleased]`
+7. Submit a pull request
+
+### Local Development
+
+```bash
+# Install dependencies
+bundle install
+
+# Run local CI (linter + tests)
+bin/ci
+
+# Run only tests
+bundle exec rake test
+
+# Run only linter
+bundle exec rubocop
+
+# Run generator E2E tests (optional, requires Rails + Playwright)
+bin/test_generator
+```
### Code Style
diff --git a/Rakefile b/Rakefile
index 4caa98e..2fee9c4 100644
--- a/Rakefile
+++ b/Rakefile
@@ -6,7 +6,8 @@ require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
t.libs << 'test'
t.libs << 'lib'
- t.test_files = FileList['test/**/*_test.rb']
+ # Exclude generator_test_files - those are template tests for generated apps
+ t.test_files = FileList['test/**/*_test.rb'].exclude('test/generator_test_files/**/*')
end
task default: :test
diff --git a/lib/rails_simple_auth/models/session.rb b/app/models/rails_simple_auth/session.rb
similarity index 65%
rename from lib/rails_simple_auth/models/session.rb
rename to app/models/rails_simple_auth/session.rb
index a3a9066..080e2ca 100644
--- a/lib/rails_simple_auth/models/session.rb
+++ b/app/models/rails_simple_auth/session.rb
@@ -4,8 +4,10 @@ module RailsSimpleAuth
class Session < ::ApplicationRecord
self.table_name = 'sessions'
- # Use lambda to defer class resolution until runtime
- belongs_to :user, class_name: -> { RailsSimpleAuth.configuration.user_class_name }
+ # NOTE: class_name is evaluated at class load time. Users customizing
+ # user_class_name must configure it before this model loads (e.g., in
+ # config/application.rb or an early-loading initializer).
+ belongs_to :user, class_name: RailsSimpleAuth.configuration.user_class_name
scope :recent, -> { order(created_at: :desc) }
scope :active, -> { where(created_at: RailsSimpleAuth.configuration.session_expiry.ago..) }
diff --git a/bin/ci b/bin/ci
new file mode 100755
index 0000000..2e39b1c
--- /dev/null
+++ b/bin/ci
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+set -e
+
+# Local CI script for rails_simple_auth
+# Run this before submitting a pull request
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+GEM_DIR="$(dirname "$SCRIPT_DIR")"
+
+cd "$GEM_DIR"
+
+echo "=== rails_simple_auth Local CI ==="
+echo ""
+
+# Step 1: Install dependencies
+echo "๐ฆ Installing dependencies..."
+bundle install --quiet
+
+# Step 2: Run RuboCop
+echo ""
+echo "๐ Running RuboCop..."
+bundle exec rubocop
+echo " โ
RuboCop passed"
+
+# Step 3: Run unit tests
+echo ""
+echo "๐งช Running unit tests..."
+bundle exec rake test
+echo " โ
Unit tests passed"
+
+echo ""
+echo "=== All checks passed! โ
==="
+echo ""
+echo "Your code is ready for a pull request."
+echo ""
+echo "Optional: Run generator E2E tests (requires Rails and Playwright):"
+echo " bin/test_generator"
diff --git a/bin/test_generator b/bin/test_generator
new file mode 100755
index 0000000..123bfbb
--- /dev/null
+++ b/bin/test_generator
@@ -0,0 +1,259 @@
+#!/usr/bin/env bash
+set -e
+
+# Test rails_simple_auth generator by creating a fresh Rails app and running tests
+# This script validates that the gem's generators and default views work correctly
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+GEM_DIR="$(dirname "$SCRIPT_DIR")"
+TEST_APP_DIR="${TEST_APP_DIR:-/tmp/rails_simple_auth_test_app}"
+HEADLESS="${HEADLESS:-true}"
+
+echo "=== rails_simple_auth Generator Test ==="
+echo "Gem directory: $GEM_DIR"
+echo "Test app directory: $TEST_APP_DIR"
+echo ""
+
+# Clean up previous test app
+if [ -d "$TEST_APP_DIR" ]; then
+ echo "Removing existing test app..."
+ rm -rf "$TEST_APP_DIR"
+fi
+
+# Create new Rails app
+echo "Creating new Rails app..."
+rails new "$TEST_APP_DIR" --skip-test --skip-git --skip-docker --skip-action-mailbox --skip-action-text --skip-active-storage
+
+cd "$TEST_APP_DIR"
+
+# Add gems to Gemfile
+echo "Adding gems to Gemfile..."
+cat >> Gemfile << 'EOF'
+
+# Authentication gem (local development)
+gem "rails_simple_auth", path: ENV.fetch("RSA_GEM_PATH", "../rails_simple_auth")
+
+group :test do
+ gem "capybara"
+ gem "capybara-playwright-driver"
+end
+EOF
+
+# Install dependencies
+# Export RSA_GEM_PATH for the entire script (used by Gemfile)
+# NOTE: Using RSA_GEM_PATH instead of GEM_PATH to avoid conflict with Ruby's standard env var
+export RSA_GEM_PATH="$GEM_DIR"
+
+echo "Installing dependencies..."
+bundle install
+
+# Create User model with authenticates_with
+echo "Creating User model..."
+cat > app/models/user.rb << 'EOF'
+class User < ApplicationRecord
+ authenticates_with :confirmable, :magic_linkable
+end
+EOF
+
+# Create users migration BEFORE install generator (users table must exist before sessions)
+echo "Creating users migration..."
+mkdir -p db/migrate
+TIMESTAMP=$(date +%Y%m%d%H%M%S)
+cat > "db/migrate/${TIMESTAMP}_create_users.rb" << 'EOF'
+class CreateUsers < ActiveRecord::Migration[8.0]
+ def change
+ create_table :users do |t|
+ t.string :email, null: false
+ t.string :password_digest
+ t.datetime :confirmed_at
+ t.string :unconfirmed_email
+
+ t.timestamps
+ end
+
+ add_index :users, :email, unique: true
+ end
+end
+EOF
+
+# Small delay to ensure sessions migration gets a later timestamp
+sleep 1
+
+# Run the install generator
+echo "Running rails_simple_auth:install generator..."
+bin/rails generate rails_simple_auth:install
+
+# Run CSS generator
+echo "Running CSS generator..."
+bin/rails generate rails_simple_auth:css
+
+# Run migrations (for both dev and test environments)
+echo "Running migrations..."
+bin/rails db:migrate
+RAILS_ENV=test bin/rails db:migrate
+
+# Create dashboard controller
+echo "Creating DashboardController..."
+mkdir -p app/controllers
+cat > app/controllers/dashboard_controller.rb << 'EOF'
+class DashboardController < ApplicationController
+ before_action :require_authentication
+
+ def show
+ end
+end
+EOF
+
+# Create dashboard view
+echo "Creating dashboard view..."
+mkdir -p app/views/dashboard
+cat > app/views/dashboard/show.html.erb << 'EOF'
+
+EOF
+
+# Create home controller and view
+echo "Creating HomeController..."
+cat > app/controllers/home_controller.rb << 'EOF'
+class HomeController < ApplicationController
+ def index
+ redirect_to dashboard_path if user_signed_in?
+ end
+end
+EOF
+
+mkdir -p app/views/home
+cat > app/views/home/index.html.erb << 'EOF'
+
+EOF
+
+# Add routes
+echo "Adding routes..."
+cat > config/routes.rb << 'EOF'
+Rails.application.routes.draw do
+ rails_simple_auth_routes
+
+ get "dashboard", to: "dashboard#show"
+ root "home#index"
+end
+EOF
+
+# Update application layout for flash messages
+echo "Updating application layout..."
+cat > app/views/layouts/application.html.erb << 'EOF'
+
+
+
+ rails_simple_auth Test App
+
+ <%= csrf_meta_tags %>
+ <%= csp_meta_tag %>
+ <%= stylesheet_link_tag "rails_simple_auth", "data-turbo-track": "reload" %>
+ <%= javascript_importmap_tags %>
+
+
+
+ <% flash.each do |type, message| %>
+
+ <%= message %>
+
+ <% end %>
+ <%= yield %>
+
+
+
+EOF
+
+# Copy test files from gem
+echo "Copying test infrastructure..."
+mkdir -p test/support/pages test/system test/models test/controllers
+
+# Copy base page
+cat > test/support/pages/base_page.rb << 'EOF'
+# frozen_string_literal: true
+
+module Pages
+ class BasePage
+ include Capybara::DSL
+ include Capybara::Minitest::Assertions
+
+ attr_reader :test_context
+
+ def initialize(test_context)
+ @test_context = test_context
+ end
+ end
+end
+EOF
+
+# Copy page objects from the gem's test directory
+cp "$GEM_DIR/test/generator_test_files/pages/"*.rb test/support/pages/ 2>/dev/null || true
+cp "$GEM_DIR/test/generator_test_files/system/"*.rb test/system/ 2>/dev/null || true
+cp "$GEM_DIR/test/generator_test_files/models/"*.rb test/models/ 2>/dev/null || true
+cp "$GEM_DIR/test/generator_test_files/controllers/"*.rb test/controllers/ 2>/dev/null || true
+
+# Create test helper
+cat > test/test_helper.rb << 'EOF'
+ENV["RAILS_ENV"] ||= "test"
+require_relative "../config/environment"
+require "rails/test_help"
+require "capybara/rails"
+require "capybara/minitest"
+
+Capybara.register_driver(:playwright) do |app|
+ Capybara::Playwright::Driver.new(app,
+ browser_type: :chromium,
+ headless: ENV["HEADLESS"] != "false")
+end
+
+Capybara.default_driver = :playwright
+Capybara.javascript_driver = :playwright
+
+Dir[Rails.root.join("test/support/**/*.rb")].each { |f| require f }
+
+class ActiveSupport::TestCase
+ parallelize(workers: :number_of_processors)
+end
+
+class ActionDispatch::IntegrationTest
+ include Capybara::DSL
+ include Capybara::Minitest::Assertions
+
+ def teardown
+ Capybara.reset_sessions!
+ end
+end
+EOF
+
+# Install Playwright browser
+echo "Installing Playwright browser..."
+npx playwright install chromium 2>/dev/null || echo "Playwright browser installation skipped"
+
+# Run tests
+echo ""
+echo "=== Running Tests ==="
+HEADLESS="$HEADLESS" bin/rails test:all
+
+echo ""
+echo "=== All tests passed! ==="
diff --git a/lib/generators/rails_simple_auth/css/templates/rails_simple_auth.css b/lib/generators/rails_simple_auth/css/templates/rails_simple_auth.css
index 8ef4a3c..eacc417 100644
--- a/lib/generators/rails_simple_auth/css/templates/rails_simple_auth.css
+++ b/lib/generators/rails_simple_auth/css/templates/rails_simple_auth.css
@@ -11,37 +11,49 @@
* }
*/
+/* ============================================================================
+ Base Reset
+ ============================================================================ */
+
+.rsa-auth-form *,
+.rsa-auth-form *::before,
+.rsa-auth-form *::after {
+ box-sizing: border-box;
+}
+
/* ============================================================================
CSS Variables (Override these to customize)
============================================================================ */
:root {
/* Primary Colors */
- --rsa-color-primary: #3b82f6;
- --rsa-color-primary-hover: #2563eb;
+ --rsa-color-primary: #4f46e5;
+ --rsa-color-primary-hover: #4338ca;
+ --rsa-color-primary-light: #eef2ff;
/* Background Colors */
- --rsa-color-background: #ffffff;
+ --rsa-color-background: #f8fafc;
--rsa-color-background-form: #ffffff;
- --rsa-color-surface: #f9fafb;
- --rsa-color-surface-hover: #f3f4f6;
+ --rsa-color-surface: #f1f5f9;
+ --rsa-color-surface-hover: #e2e8f0;
/* Text Colors */
- --rsa-color-text: #374151;
- --rsa-color-text-muted: #6b7280;
- --rsa-color-text-dark: #1f2937;
+ --rsa-color-text: #475569;
+ --rsa-color-text-muted: #64748b;
+ --rsa-color-text-dark: #0f172a;
/* Border Colors */
- --rsa-color-border: #e5e7eb;
- --rsa-color-border-form: #d1d5db;
+ --rsa-color-border: #e2e8f0;
+ --rsa-color-border-form: #cbd5e1;
/* Semantic Colors */
--rsa-color-danger: #dc2626;
+ --rsa-color-danger-hover: #b91c1c;
--rsa-color-success: #16a34a;
--rsa-color-white: #ffffff;
/* Focus Ring */
- --rsa-color-focus-ring: rgba(59, 130, 246, 0.5);
+ --rsa-color-focus-ring: rgba(79, 70, 229, 0.4);
/* OAuth Provider Colors */
--rsa-color-google: #4285f4;
@@ -57,15 +69,26 @@
--rsa-space-5: 1.25rem;
--rsa-space-6: 1.5rem;
--rsa-space-8: 2rem;
+ --rsa-space-10: 2.5rem;
/* Border Radius */
- --rsa-radius-sm: 0.25rem;
- --rsa-radius-md: 0.375rem;
- --rsa-radius-lg: 0.5rem;
- --rsa-radius-xl: 0.75rem;
+ --rsa-radius-sm: 0.375rem;
+ --rsa-radius-md: 0.5rem;
+ --rsa-radius-lg: 0.75rem;
+ --rsa-radius-xl: 1rem;
+ --rsa-radius-full: 9999px;
+
+ /* Shadows */
+ --rsa-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --rsa-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --rsa-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --rsa-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
/* Border Width */
--rsa-border-thin: 1px;
+
+ /* Font */
+ --rsa-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* ============================================================================
@@ -93,6 +116,10 @@
background-color: var(--rsa-color-background-form);
}
+.rsa-auth-form > * {
+ width: 100%;
+}
+
.rsa-auth-form__title {
margin-bottom: var(--rsa-space-2);
font-size: 1.5rem;
@@ -109,6 +136,11 @@
}
.rsa-auth-form__form {
+ display: block;
+ width: 100%;
+}
+
+.rsa-auth-form__form > * {
width: 100%;
}
@@ -174,8 +206,9 @@
.rsa-auth-form__actions {
display: flex;
flex-direction: column;
- align-items: center;
+ align-items: stretch;
margin-top: var(--rsa-space-6);
+ width: 100%;
}
.rsa-auth-form__submit {
diff --git a/test/generator_test_files/controllers/dashboard_controller_test.rb b/test/generator_test_files/controllers/dashboard_controller_test.rb
new file mode 100644
index 0000000..bef6f8c
--- /dev/null
+++ b/test/generator_test_files/controllers/dashboard_controller_test.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class DashboardControllerTest < ActionDispatch::IntegrationTest
+ test 'should redirect unauthenticated user to sign in' do
+ get dashboard_path
+
+ assert_redirected_to new_session_path
+ end
+
+ test 'should get show for authenticated user' do
+ user = User.create!(
+ email: 'dashboard@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ # Sign in the user
+ post session_path, params: { email: user.email, password: 'password123' }
+
+ get dashboard_path
+
+ assert_response :success
+ assert_match 'Dashboard', response.body
+ assert_match user.email, response.body
+ end
+
+ test 'should sign out user' do
+ user = User.create!(
+ email: 'signout@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ # Sign in the user
+ post session_path, params: { email: user.email, password: 'password123' }
+
+ # Sign out
+ delete session_path
+
+ # Should be redirected
+ assert_response :redirect
+
+ # Dashboard should now redirect to sign in
+ get dashboard_path
+
+ assert_redirected_to new_session_path
+ end
+end
diff --git a/test/generator_test_files/controllers/home_controller_test.rb b/test/generator_test_files/controllers/home_controller_test.rb
new file mode 100644
index 0000000..35411e8
--- /dev/null
+++ b/test/generator_test_files/controllers/home_controller_test.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class HomeControllerTest < ActionDispatch::IntegrationTest
+ test 'should get index for unauthenticated user' do
+ get root_path
+
+ assert_response :success
+ assert_match 'rails_simple_auth Demo', response.body
+ end
+
+ test 'should redirect authenticated user to dashboard' do
+ user = User.create!(
+ email: 'home_redirect@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ # Sign in the user
+ post session_path, params: { email: user.email, password: 'password123' }
+
+ get root_path
+
+ assert_redirected_to dashboard_path
+ end
+end
diff --git a/test/generator_test_files/models/user_test.rb b/test/generator_test_files/models/user_test.rb
new file mode 100644
index 0000000..7fd0db6
--- /dev/null
+++ b/test/generator_test_files/models/user_test.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class UserTest < ActiveSupport::TestCase
+ test 'user has authenticates_with modules included' do
+ user = User.new
+
+ assert_respond_to user, :authenticate, 'User should respond to authenticate'
+ assert_respond_to user, :confirmed?, 'User should respond to confirmed?'
+ assert_respond_to user, :generate_magic_link_token, 'User should respond to generate_magic_link_token'
+ end
+
+ test 'user can be created with valid attributes' do
+ user = User.new(
+ email: 'valid@example.com',
+ password: 'password123',
+ password_confirmation: 'password123'
+ )
+
+ assert_predicate user, :valid?, "User should be valid: #{user.errors.full_messages.join(', ')}"
+ end
+
+ test 'user requires email' do
+ user = User.new(
+ password: 'password123',
+ password_confirmation: 'password123'
+ )
+
+ assert_not user.valid?, 'User should not be valid without email'
+ assert_includes user.errors[:email], "can't be blank"
+ end
+
+ test 'user requires password' do
+ user = User.new(email: 'test@example.com')
+
+ assert_not user.valid?, 'User should not be valid without password'
+ assert_predicate user.errors[:password], :any?, 'Should have password error'
+ end
+
+ test 'user email must be unique' do
+ User.create!(
+ email: 'duplicate@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ user = User.new(
+ email: 'duplicate@example.com',
+ password: 'password123',
+ password_confirmation: 'password123'
+ )
+
+ assert_not user.valid?, 'User should not be valid with duplicate email'
+ assert_includes user.errors[:email], 'has already been taken'
+ end
+
+ test 'user password must meet minimum length' do
+ user = User.new(
+ email: 'short@example.com',
+ password: 'short',
+ password_confirmation: 'short'
+ )
+
+ assert_not user.valid?, 'User should not be valid with short password'
+ assert user.errors[:password].any? { |e| e.include?('at least') },
+ 'Should have password length error'
+ end
+
+ test 'user can authenticate with correct password' do
+ user = User.create!(
+ email: 'auth@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ assert user.authenticate('password123'), 'Should authenticate with correct password'
+ end
+
+ test 'user cannot authenticate with incorrect password' do
+ user = User.create!(
+ email: 'auth_fail@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ assert_not user.authenticate('wrongpassword'), 'Should not authenticate with wrong password'
+ end
+
+ test 'user confirmed? returns true when confirmed_at is set' do
+ user = User.new(confirmed_at: Time.current)
+
+ assert_predicate user, :confirmed?, 'User should be confirmed when confirmed_at is set'
+ end
+
+ test 'user confirmed? returns false when confirmed_at is nil' do
+ user = User.new(confirmed_at: nil)
+
+ assert_not user.confirmed?, 'User should not be confirmed when confirmed_at is nil'
+ end
+
+ test 'user can generate magic link token' do
+ user = User.create!(
+ email: 'magic@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ token = user.generate_magic_link_token
+
+ assert_predicate token, :present?, 'Should generate magic link token'
+ assert_kind_of String, token
+ end
+
+ test 'user can generate password reset token' do
+ user = User.create!(
+ email: 'reset@example.com',
+ password: 'password123',
+ password_confirmation: 'password123',
+ confirmed_at: Time.current
+ )
+
+ token = user.generate_password_reset_token
+
+ assert_predicate token, :present?, 'Should generate password reset token'
+ assert_kind_of String, token
+ end
+
+ test 'user can generate confirmation token' do
+ user = User.create!(
+ email: 'confirm_token@example.com',
+ password: 'password123',
+ password_confirmation: 'password123'
+ )
+
+ token = user.generate_confirmation_token
+
+ assert_predicate token, :present?, 'Should generate confirmation token'
+ assert_kind_of String, token
+ end
+end
diff --git a/test/generator_test_files/pages/base_page.rb b/test/generator_test_files/pages/base_page.rb
new file mode 100644
index 0000000..266a8e7
--- /dev/null
+++ b/test/generator_test_files/pages/base_page.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Pages
+ class BasePage
+ include Capybara::DSL
+ include Capybara::Minitest::Assertions
+
+ def initialize(test_context)
+ @test_context = test_context
+ end
+
+ def has_flash_notice?(message)
+ has_selector?('.rsa-flash--notice', text: message, wait: 5) ||
+ has_selector?('[data-flash="notice"]', text: message, wait: 5) ||
+ has_text?(message)
+ end
+
+ def has_flash_alert?(message)
+ has_selector?('.rsa-flash--alert', text: message, wait: 5) ||
+ has_selector?('[data-flash="alert"]', text: message, wait: 5) ||
+ has_text?(message)
+ end
+
+ def current_path
+ URI.parse(page.current_url).path
+ end
+
+ private
+
+ attr_reader :test_context
+ end
+end
diff --git a/test/generator_test_files/pages/confirmation_page.rb b/test/generator_test_files/pages/confirmation_page.rb
new file mode 100644
index 0000000..5cdcf79
--- /dev/null
+++ b/test/generator_test_files/pages/confirmation_page.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Pages
+ class ConfirmationPage < BasePage
+ def visit_page
+ visit '/confirmations/new'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Resend Confirmation', wait: 5)
+ end
+
+ def request_confirmation(email:)
+ fill_in 'Email', with: email
+ click_button 'Resend Confirmation'
+ self
+ end
+
+ def confirm_with_token(token:)
+ visit "/confirmations/#{token}"
+ self
+ end
+
+ def has_confirmation_sent_message?
+ # After submission, redirects to sign in page with flash notice
+ has_text?('confirmation instructions have been sent')
+ end
+
+ def has_confirmed_message?
+ has_text?('Email confirmed') || has_text?('You can now sign in')
+ end
+
+ def has_invalid_token_error?
+ has_text?('Invalid or expired confirmation link')
+ end
+ end
+end
diff --git a/test/generator_test_files/pages/dashboard_page.rb b/test/generator_test_files/pages/dashboard_page.rb
new file mode 100644
index 0000000..046e72f
--- /dev/null
+++ b/test/generator_test_files/pages/dashboard_page.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Pages
+ class DashboardPage < BasePage
+ def visit_page
+ visit '/dashboard'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Dashboard', wait: 5)
+ end
+
+ def has_welcome_message_for?(email)
+ has_text?(email)
+ end
+
+ def sign_out
+ click_button 'Sign Out'
+ Pages::SignInPage.new(test_context)
+ end
+ end
+end
diff --git a/test/generator_test_files/pages/landing_page.rb b/test/generator_test_files/pages/landing_page.rb
new file mode 100644
index 0000000..e06d726
--- /dev/null
+++ b/test/generator_test_files/pages/landing_page.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Pages
+ class LandingPage < BasePage
+ def visit_page
+ visit '/'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'rails_simple_auth Demo', wait: 5)
+ end
+
+ def click_sign_in
+ click_link 'Sign In'
+ Pages::SignInPage.new(test_context)
+ end
+
+ def click_sign_up
+ click_link 'Sign Up'
+ Pages::SignUpPage.new(test_context)
+ end
+ end
+end
diff --git a/test/generator_test_files/pages/magic_link_page.rb b/test/generator_test_files/pages/magic_link_page.rb
new file mode 100644
index 0000000..c4f5701
--- /dev/null
+++ b/test/generator_test_files/pages/magic_link_page.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Pages
+ class MagicLinkPage < BasePage
+ def visit_page
+ visit '/magic_link_form'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Sign In with Magic Link', wait: 5)
+ end
+
+ def request_magic_link(email:)
+ fill_in 'Email', with: email
+ click_button 'Send Magic Link'
+ self
+ end
+
+ def has_magic_link_sent_message?
+ # After submission, redirects to sign in page with flash notice
+ has_text?('If an account exists') || has_text?('magic link has been sent')
+ end
+ end
+end
diff --git a/test/generator_test_files/pages/password_reset_page.rb b/test/generator_test_files/pages/password_reset_page.rb
new file mode 100644
index 0000000..3954364
--- /dev/null
+++ b/test/generator_test_files/pages/password_reset_page.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Pages
+ class PasswordResetPage < BasePage
+ def visit_page
+ visit '/passwords/new'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Reset Password', wait: 5)
+ end
+
+ def request_reset(email:)
+ fill_in 'Email', with: email
+ click_button 'Send Reset Instructions'
+ self
+ end
+
+ def has_reset_email_sent_message?
+ # After submission, redirects to sign in page with flash notice
+ has_text?('If an account exists') || has_text?('instructions have been sent')
+ end
+ end
+
+ class PasswordEditPage < BasePage
+ def visit_page(token:)
+ visit "/passwords/#{token}/edit"
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Set New Password', wait: 5)
+ end
+
+ def reset_password(password:, password_confirmation: nil)
+ fill_in 'New Password', with: password
+ fill_in 'Confirm Password', with: password_confirmation || password
+ click_button 'Update Password'
+ self
+ end
+
+ def has_invalid_token_error?
+ has_text?('Invalid or expired password reset link')
+ end
+ end
+end
diff --git a/test/generator_test_files/pages/sign_in_page.rb b/test/generator_test_files/pages/sign_in_page.rb
new file mode 100644
index 0000000..dad35b1
--- /dev/null
+++ b/test/generator_test_files/pages/sign_in_page.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Pages
+ class SignInPage < BasePage
+ def visit_page
+ visit '/session/new'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Sign In', wait: 5)
+ end
+
+ def sign_in(email:, password:)
+ fill_in 'Email', with: email
+ fill_in 'Password', with: password
+ click_button 'Sign In'
+ self
+ end
+
+ def click_forgot_password
+ click_link 'Forgot password?'
+ Pages::PasswordResetPage.new(test_context)
+ end
+
+ def click_magic_link
+ click_link 'Sign in with Magic Link'
+ Pages::MagicLinkPage.new(test_context)
+ end
+
+ def click_sign_up
+ click_link 'Sign Up'
+ Pages::SignUpPage.new(test_context)
+ end
+
+ def has_invalid_credentials_error?
+ has_text?('Invalid email or password')
+ end
+
+ def has_unconfirmed_error?
+ has_text?('confirm') || has_text?('Confirm')
+ end
+ end
+end
diff --git a/test/generator_test_files/pages/sign_up_page.rb b/test/generator_test_files/pages/sign_up_page.rb
new file mode 100644
index 0000000..b511f62
--- /dev/null
+++ b/test/generator_test_files/pages/sign_up_page.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Pages
+ class SignUpPage < BasePage
+ def visit_page
+ visit '/sign_up'
+ self
+ end
+
+ def displayed?
+ has_selector?('.rsa-auth-form__title', text: 'Sign Up', wait: 5)
+ end
+
+ def sign_up(email:, password:)
+ fill_in 'Email', with: email
+ fill_in 'Password', with: password
+ click_button 'Sign Up'
+ self
+ end
+
+ def click_sign_in
+ click_link 'Already have an account? Sign In'
+ Pages::SignInPage.new(test_context)
+ end
+
+ def has_email_taken_error?
+ has_text?('has already been taken') || has_text?('Email has already been taken')
+ end
+
+ def has_password_too_short_error?
+ has_text?('at least') || has_text?('must be at least')
+ end
+ end
+end
diff --git a/test/generator_test_files/system/authentication_test.rb b/test/generator_test_files/system/authentication_test.rb
new file mode 100644
index 0000000..f4e7155
--- /dev/null
+++ b/test/generator_test_files/system/authentication_test.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class AuthenticationTest < ActionDispatch::IntegrationTest
+ def setup
+ super
+ @landing_page = Pages::LandingPage.new(self)
+ @sign_in_page = Pages::SignInPage.new(self)
+ @sign_up_page = Pages::SignUpPage.new(self)
+ @dashboard_page = Pages::DashboardPage.new(self)
+ end
+
+ test 'landing page displays correctly' do
+ @landing_page.visit_page
+
+ assert_predicate @landing_page, :displayed?, 'Landing page should be displayed'
+ end
+
+ test 'unauthenticated user can navigate from landing to sign in' do
+ @landing_page.visit_page
+ sign_in_page = @landing_page.click_sign_in
+
+ assert_predicate sign_in_page, :displayed?, 'Sign in page should be displayed'
+ end
+
+ test 'unauthenticated user can navigate from landing to sign up' do
+ @landing_page.visit_page
+ sign_up_page = @landing_page.click_sign_up
+
+ assert_predicate sign_up_page, :displayed?, 'Sign up page should be displayed'
+ end
+
+ test 'unauthenticated user accessing dashboard is redirected to sign in' do
+ @dashboard_page.visit_page
+
+ assert_predicate @sign_in_page, :displayed?, 'User should be redirected to sign in page'
+ end
+
+ test 'user can sign up with valid credentials' do
+ email = "test_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ @sign_up_page.visit_page
+ @sign_up_page.sign_up(email: email, password: password)
+
+ # After sign up, user should see confirmation message
+ assert has_text?('Account created') || has_text?('check your email'),
+ 'User should see confirmation instructions after sign up'
+ end
+
+ test 'user cannot sign up with existing email' do
+ email = "existing_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ # Create user first
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @sign_up_page.visit_page
+ @sign_up_page.sign_up(email: email, password: password)
+
+ assert_predicate @sign_up_page, :has_email_taken_error?, 'Should show email taken error'
+ end
+
+ test 'user cannot sign up with short password' do
+ email = "short_pwd_#{SecureRandom.hex(4)}@example.com"
+
+ @sign_up_page.visit_page
+ @sign_up_page.sign_up(email: email, password: 'short')
+
+ assert_predicate @sign_up_page, :has_password_too_short_error?, 'Should show password too short error'
+ end
+
+ test 'confirmed user can sign in with valid credentials' do
+ email = "signin_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: email, password: password)
+
+ assert_predicate @dashboard_page, :displayed?, 'User should be redirected to dashboard after sign in'
+ assert @dashboard_page.has_welcome_message_for?(email), 'Dashboard should show user email'
+ end
+
+ test 'user cannot sign in with invalid password' do
+ email = "invalid_pwd_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: email, password: 'wrongpassword')
+
+ assert_predicate @sign_in_page, :has_invalid_credentials_error?, 'Should show invalid credentials error'
+ end
+
+ test 'user cannot sign in with non-existent email' do
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: 'nonexistent@example.com', password: 'password123')
+
+ assert_predicate @sign_in_page, :has_invalid_credentials_error?, 'Should show invalid credentials error'
+ end
+
+ test 'authenticated user can sign out' do
+ email = "signout_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: email, password: password)
+
+ assert_predicate @dashboard_page, :displayed?, 'User should be on dashboard'
+
+ @dashboard_page.sign_out
+
+ # After sign out, user should be on sign in page or landing
+ assert @sign_in_page.displayed? || @landing_page.displayed?,
+ 'User should be redirected after sign out'
+ end
+
+ test 'authenticated user visiting landing page is redirected to dashboard' do
+ email = "redirect_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: email, password: password)
+
+ assert_predicate @dashboard_page, :displayed?, 'User should be on dashboard'
+
+ @landing_page.visit_page
+
+ assert_predicate @dashboard_page, :displayed?, 'Authenticated user should be redirected to dashboard from landing'
+ end
+end
diff --git a/test/generator_test_files/system/email_confirmation_test.rb b/test/generator_test_files/system/email_confirmation_test.rb
new file mode 100644
index 0000000..183eee2
--- /dev/null
+++ b/test/generator_test_files/system/email_confirmation_test.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class EmailConfirmationTest < ActionDispatch::IntegrationTest
+ def setup
+ super
+ @sign_in_page = Pages::SignInPage.new(self)
+ @sign_up_page = Pages::SignUpPage.new(self)
+ @confirmation_page = Pages::ConfirmationPage.new(self)
+ @dashboard_page = Pages::DashboardPage.new(self)
+ end
+
+ test 'unconfirmed user cannot sign in' do
+ email = "unconfirmed_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: nil)
+
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: email, password: password)
+
+ # User should either see error or be asked to confirm
+ assert @sign_in_page.has_unconfirmed_error? || has_text?('confirm') || @sign_in_page.displayed?,
+ 'Unconfirmed user should not be able to sign in'
+ end
+
+ test 'user can confirm email with valid token' do
+ email = "confirm_valid_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ user = User.create!(email: email, password: password, password_confirmation: password, confirmed_at: nil)
+
+ # Generate a valid confirmation token
+ token = user.generate_confirmation_token
+
+ @confirmation_page.confirm_with_token(token: token)
+
+ # After confirmation, user should see success or be redirected to sign in
+ assert @confirmation_page.has_confirmed_message? || @sign_in_page.displayed?,
+ 'User should see confirmation success or be on sign in page'
+
+ # Verify user can now sign in
+ @sign_in_page.visit_page
+ @sign_in_page.sign_in(email: email, password: password)
+
+ assert_predicate @dashboard_page, :displayed?, 'Confirmed user should be able to sign in'
+ end
+
+ test 'user cannot confirm email with invalid token' do
+ visit '/confirmations/invalid_token_123'
+
+ # Should show error
+ assert @confirmation_page.has_invalid_token_error? || has_text?('invalid') || has_text?('expired'),
+ 'Should show invalid or expired token error'
+ end
+
+ test 'user can request new confirmation email' do
+ email = "resend_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: nil)
+
+ @confirmation_page.visit_page
+ @confirmation_page.request_confirmation(email: email)
+
+ assert_predicate @confirmation_page, :has_confirmation_sent_message?,
+ 'Should show confirmation email sent message'
+ end
+end
diff --git a/test/generator_test_files/system/magic_link_test.rb b/test/generator_test_files/system/magic_link_test.rb
new file mode 100644
index 0000000..faf6249
--- /dev/null
+++ b/test/generator_test_files/system/magic_link_test.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class MagicLinkTest < ActionDispatch::IntegrationTest
+ def setup
+ super
+ @sign_in_page = Pages::SignInPage.new(self)
+ @magic_link_page = Pages::MagicLinkPage.new(self)
+ @dashboard_page = Pages::DashboardPage.new(self)
+ end
+
+ test 'magic link page displays correctly' do
+ @magic_link_page.visit_page
+
+ assert_predicate @magic_link_page, :displayed?, 'Magic link page should be displayed'
+ end
+
+ test 'user can navigate to magic link from sign in page' do
+ @sign_in_page.visit_page
+ magic_link_page = @sign_in_page.click_magic_link
+
+ assert_predicate magic_link_page, :displayed?, 'Magic link page should be displayed'
+ end
+
+ test 'user can request magic link for existing email' do
+ email = "magic_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @magic_link_page.visit_page
+ @magic_link_page.request_magic_link(email: email)
+
+ assert_predicate @magic_link_page, :has_magic_link_sent_message?,
+ 'Should show magic link sent message'
+ end
+
+ test 'user can sign in with valid magic link token' do
+ email = "magic_valid_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ user = User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ # Generate a valid magic link token
+ token = user.generate_magic_link_token
+
+ visit "/magic_link?token=#{token}"
+
+ assert_predicate @dashboard_page, :displayed?, 'User should be signed in and on dashboard after using magic link'
+ assert @dashboard_page.has_welcome_message_for?(email), 'Dashboard should show user email'
+ end
+
+ test 'user cannot sign in with invalid magic link token' do
+ visit '/magic_link?token=invalid_token_123'
+
+ # Should show error or be redirected to sign in
+ assert @sign_in_page.displayed? || has_text?('invalid') || has_text?('expired'),
+ 'Should show error or redirect to sign in for invalid token'
+ end
+
+ test 'requesting magic link for non-existent email does not reveal information' do
+ @magic_link_page.visit_page
+ @magic_link_page.request_magic_link(email: 'nonexistent@example.com')
+
+ # Should show same message to prevent email enumeration
+ assert_predicate @magic_link_page, :has_magic_link_sent_message?,
+ 'Should show magic link sent message even for non-existent email'
+ end
+end
diff --git a/test/generator_test_files/system/password_reset_test.rb b/test/generator_test_files/system/password_reset_test.rb
new file mode 100644
index 0000000..6e76516
--- /dev/null
+++ b/test/generator_test_files/system/password_reset_test.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class PasswordResetTest < ActionDispatch::IntegrationTest
+ def setup
+ super
+ @sign_in_page = Pages::SignInPage.new(self)
+ @password_reset_page = Pages::PasswordResetPage.new(self)
+ @dashboard_page = Pages::DashboardPage.new(self)
+ end
+
+ test 'password reset page displays correctly' do
+ @password_reset_page.visit_page
+
+ assert_predicate @password_reset_page, :displayed?, 'Password reset page should be displayed'
+ end
+
+ test 'user can navigate to password reset from sign in page' do
+ @sign_in_page.visit_page
+ password_reset_page = @sign_in_page.click_forgot_password
+
+ assert_predicate password_reset_page, :displayed?, 'Password reset page should be displayed'
+ end
+
+ test 'user can request password reset for existing email' do
+ email = "reset_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+
+ User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ @password_reset_page.visit_page
+ @password_reset_page.request_reset(email: email)
+
+ assert_predicate @password_reset_page, :has_reset_email_sent_message?,
+ 'Should show reset email sent message'
+ end
+
+ test 'user can request password reset for non-existent email without revealing info' do
+ @password_reset_page.visit_page
+ @password_reset_page.request_reset(email: 'nonexistent@example.com')
+
+ # Should show same message to prevent email enumeration
+ assert_predicate @password_reset_page, :has_reset_email_sent_message?,
+ 'Should show reset email sent message even for non-existent email'
+ end
+
+ test 'user can reset password with valid token' do
+ email = "reset_valid_#{SecureRandom.hex(4)}@example.com"
+ password = 'password123'
+ new_password = 'newpassword456'
+
+ user = User.create!(email: email, password: password, password_confirmation: password, confirmed_at: Time.current)
+
+ # Generate a valid password reset token
+ token = user.generate_password_reset_token
+
+ password_edit_page = Pages::PasswordEditPage.new(self)
+ password_edit_page.visit_page(token: token)
+ password_edit_page.reset_password(password: new_password)
+
+ # After successful reset, user should see success message
+ assert has_text?('Password has been reset') || has_text?('new password'),
+ 'User should see password reset success message'
+
+ # Reload user to verify password changed
+ user.reload
+
+ # Verify new password works by authenticating directly (not through browser)
+ assert user.authenticate(new_password), 'New password should authenticate the user'
+ end
+
+ test 'user cannot reset password with invalid token' do
+ password_edit_page = Pages::PasswordEditPage.new(self)
+ password_edit_page.visit_page(token: 'invalid_token_123')
+
+ # Should show error or be on error page
+ assert password_edit_page.has_invalid_token_error? || has_text?('invalid') || has_text?('expired'),
+ 'Should show invalid or expired token error'
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index a0f7fc7..8b5e4ff 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -63,9 +63,17 @@ class Application < Rails::Application
Rails.application.initialize!
# Configure signed IDs for ActiveRecord (required for Rails 8+)
+# Note: This method is deprecated in Rails 8.2, but the replacement API
+# (ActiveRecord.message_verifiers) requires full Rails app initialization
ActiveRecord::Base.signed_id_verifier_secret = Rails.application.secret_key_base
require 'rails_simple_auth'
+
+# Configure RailsSimpleAuth to use User class before anything uses Session
+RailsSimpleAuth.configure do |config|
+ config.user_class_name = 'User'
+end
+
require 'minitest/autorun'
# Define User model for testing
@@ -88,24 +96,6 @@ def self.find_by_oauth(provider, uid)
end
end
-# Monkey-patch Session for testing to use static class name
-module RailsSimpleAuth
- class Session < ApplicationRecord
- self.table_name = 'sessions'
- belongs_to :user, class_name: 'User'
-
- scope :recent, -> { order(created_at: :desc) }
- scope :active, -> { where(created_at: RailsSimpleAuth.configuration.session_expiry.ago..) }
- scope :expired, -> { where(created_at: ...RailsSimpleAuth.configuration.session_expiry.ago) }
-
- def self.cleanup_expired!
- count = expired.delete_all
- Rails.logger.info("[RailsSimpleAuth] Cleaned up #{count} expired sessions")
- count
- end
- end
-end
-
# Add Rails-style assertion helpers to Minitest
module Minitest
class Test