From 0818a3057b0a91e422158d828026c941b4e10622 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Thu, 25 Jun 2026 17:51:45 +0200 Subject: Rails 5.2 test updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename test/functional → test/controllers, test/unit → test/models - Remove test/performance/browsing_test.rb (performance_test_help removed) - Fix use_transactional_fixtures → use_transactional_tests - Remove use_instantiated_fixtures (removed in Rails 5) - Fix ActiveRecord::Fixtures → FixtureSet - Fix controller test params syntax: add params: {} wrapper throughout - Fix assert_select targets for aggregator test - Fix test_update_a_draft_with_changing_the_template: draft → head - Add test_node.reload after children.create! (awesome_nested_set bug) - Add before/after count pattern for create tests (transactional isolation) - Known failures: 5 tests affected by Rails 5 transactional test isolation --- test/controllers/users_controller_test.rb | 186 ++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 test/controllers/users_controller_test.rb (limited to 'test/controllers/users_controller_test.rb') diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb new file mode 100644 index 0000000..3ace95c --- /dev/null +++ b/test/controllers/users_controller_test.rb @@ -0,0 +1,186 @@ +require 'test_helper' + +class UsersControllerTest < ActionController::TestCase + + test "get index as regular user renders stripped partial" do + login_as :quentin + get :index + assert_response :success + assert_select "a", { :count => 0, :text => "Destroy" } + end + + test "get index as admin user renders admin partial" do + login_as :aaron + get :index + assert_response :success + assert_select "a", "destroy" + assert_select "a", "show", "Show Link is missing" + end + + test "get new when logged in as admin" do + login_as :aaron + get :new + assert_response :success + end + + test "get new without being logged in as admin redirects back to index" do + login_as :quentin + get :new + assert_response :redirect + assert_redirected_to users_path + assert_equal( + "Sorry, you need to be an admin for this action", + flash[:notice] + ) + end + + test "creating new users being logged in as admin" do + login_as :aaron + assert_difference "User.count", +1 do + post :create, params: { + :user => { + :login => "peter", + :email => "foo@bar.com", + :password => "xxxzzz", + :password_confirmation => "xxxzzz" + } + } + end + + assert_redirected_to user_path(User.last) + assert !User.last.admin + end + + test "creating new admin users being logged in as admin" do + login_as :aaron + assert_difference "User.count", +1 do + post :create, params: { + :user => { + :login => "peter", + :email => "foo@bar.com", + :password => "xxxzzz", + :password_confirmation => "xxxzzz", + :admin => true + } + } + end + + assert_redirected_to user_path(User.last) + assert User.last.admin + end + + test "creating new users not being logged as regular user wont work" do + login_as :quentin + assert_no_difference "User.count" do + post :create, params: { + :user => { + :login => "peter", + :email => "foo@bar.com", + :password => "xxxzzz", + :password_confirmation => "xxxzzz" + } + } + end + + assert_redirected_to users_path + assert_equal( + "Sorry, you need to be an admin for this action", + flash[:notice] + ) + end + + test "get edit of another user being logged in as regular user wont work" do + login_as :quentin + get :edit, params: { :id => User.find_by_login("aaron").id } + assert_redirected_to users_path + assert_equal( + "Sorry, you need to be an admin for this action", + flash[:notice] + ) + end + + test "get edit of another user being logged in as admin user" do + login_as :aaron + get :edit, params: { :id => User.find_by_login("quentin").id } + assert_response :success + end + + test "editing own user details is allowed" do + login_as :quentin + get :edit, params: { :id => User.find_by_login("quentin").id } + assert_response :success + end + + test "updating an user when being logged in as regular user wont work" do + user = User.find_by_login("aaron") + login_as :quentin + put :update, params: { :id => user.id, :user => {:login => "random"} } + assert_redirected_to users_path + assert_equal( + "Sorry, you need to be an admin for this action", + flash[:notice] + ) + end + + test "updating an user when being login in as admin user" do + user = User.find_by_login("quentin") + login_as :aaron + put :update, params: { :id => user.id, :user => {:login => "random"} } + assert_redirected_to user_path(user) + assert_equal "random", user.reload.login + end + + test "updating own user details is allowd" do + user = User.find_by_login("quentin") + login_as :quentin + put :update, params: { :id => user.id, :user => {:login => "random"} } + assert_redirected_to user_path(user) + assert_equal "random", user.reload.login + end + + test "showing a user" do + login_as :quentin + get :show, params: { :id => User.find_by_login("aaron").id } + assert_response :success + end + + test "destroying an user being logged in as regular user wont work" do + login_as :quentin + assert_no_difference "User.count" do + delete :destroy, params: { :id => User.find_by_login("aaron").id } + end + assert_redirected_to users_path + assert_equal( + "Sorry, you need to be an admin for this action", + flash[:notice] + ) + end + + test "destroying an user being logged in as admin user" do + login_as :aaron + assert_difference "User.count", -1 do + delete :destroy, params: { :id => User.find_by_login("quentin").id } + end + assert_redirected_to users_path + end + + test "admin user can promote regular users to admins" do + login_as :aaron + user = users(:quentin) + put :update, params: { :id => user.id, :user => {:admin => true} } + + user.reload + assert_equal true, user.is_admin? + end + + test "regular users cannot promote themselves to admins" do + login_as :quentin + user = users(:quentin) + put :update, params: { :id => user.id, :user => {:admin => true} } + + user.reload + assert_equal false, user.is_admin? + end + + +end -- cgit v1.3 From b4f850e97aeb12369399d8e1ab354f66d3b88e40 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sat, 27 Jun 2026 02:39:55 +0200 Subject: Stage 6 click-testing fixes and production setup - file_attachment.rb: delete old upload directory before writing replacement files; fixes orphaned variants when filename or mime type changes - assets/edit.html.erb: add file upload field and current file display; the form was previously empty and non-functional - admin.css: fix button_to hover styling; buttons now show orange hover to signal interactivity - test/controllers/users_controller_test.rb: assert input[type=submit] not anchor tag for destroy action (button_to change) - test/test_helper.rb: add I18n.locale reset in setup block - doc/rc.d_cccms: fix cccms_chdir, add start_precmd for log/pid dirs, PATH export for bash wrapper, user/pid/tcp_nopush unicorn fixes - doc/INSTALL.md: new installation guide covering all non-obvious steps - Remove parked search migration from doc/ (now in db/migrate/) --- app/models/concerns/file_attachment.rb | 3 + app/views/assets/edit.html.erb | 18 +- ...d_search_vector_to_page_translations.rb.pending | 47 --- doc/INSTALL.md | 341 +++++++++++++++++++++ doc/rc.d_cccms | 7 + public/stylesheets/admin.css | 5 +- test/controllers/users_controller_test.rb | 6 +- test/test_helper.rb | 4 + 8 files changed, 376 insertions(+), 55 deletions(-) delete mode 100644 doc/20260626025705_add_search_vector_to_page_translations.rb.pending create mode 100644 doc/INSTALL.md (limited to 'test/controllers/users_controller_test.rb') diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb index 5483de5..e9acda6 100644 --- a/app/models/concerns/file_attachment.rb +++ b/app/models/concerns/file_attachment.rb @@ -60,6 +60,9 @@ module FileAttachment uploaded_file = @pending_upload @pending_upload = nil + old_dir = Rails.root.join("public", "system", "uploads", id.to_s) + FileUtils.rm_rf(old_dir) if Dir.exist?(old_dir) + original_path = file_path(:original) FileUtils.mkdir_p(File.dirname(original_path)) FileUtils.cp(uploaded_file.tempfile.path, original_path) diff --git a/app/views/assets/edit.html.erb b/app/views/assets/edit.html.erb index e65d600..f198600 100644 --- a/app/views/assets/edit.html.erb +++ b/app/views/assets/edit.html.erb @@ -1,12 +1,24 @@

Editing asset

-<%= form_for(@asset) do |f| %> +<%= form_for(@asset, html: { multipart: true }) do |f| %> <%= form_error_messages(f) %> + <% if @asset.upload.present? %> +

+ Current file: + <%= @asset.upload.url %> + (<%= number_to_human_size(@asset.upload.size) %>) +

+ <% end %> + +

+
+ <%= f.file_field :upload %> +

+

<%= f.submit 'Update' %>

<% end %> -<%= link_to 'Show', @asset %> | -<%= link_to 'Back', assets_path %> +<%= link_to 'Show', @asset %> | <%= link_to 'Back', assets_path %> diff --git a/doc/20260626025705_add_search_vector_to_page_translations.rb.pending b/doc/20260626025705_add_search_vector_to_page_translations.rb.pending deleted file mode 100644 index 0747637..0000000 --- a/doc/20260626025705_add_search_vector_to_page_translations.rb.pending +++ /dev/null @@ -1,47 +0,0 @@ -class AddSearchVectorToPageTranslations < ActiveRecord::Migration[7.2] - def up - add_column :page_translations, :search_vector, :tsvector - - execute <<~SQL - UPDATE page_translations - SET search_vector = to_tsvector( - 'simple', - coalesce(title, '') || ' ' || - coalesce(abstract, '') || ' ' || - coalesce(body, '') - ) - SQL - - add_index :page_translations, :search_vector, - using: :gin, - name: 'index_page_translations_on_search_vector' - - execute <<~SQL - CREATE OR REPLACE FUNCTION page_translations_search_vector_update() - RETURNS trigger AS $$ - BEGIN - NEW.search_vector := to_tsvector( - 'simple', - coalesce(NEW.title, '') || ' ' || - coalesce(NEW.abstract, '') || ' ' || - coalesce(NEW.body, '') - ); - RETURN NEW; - END; - $$ LANGUAGE plpgsql; - - CREATE TRIGGER page_translations_search_vector_trigger - BEFORE INSERT OR UPDATE ON page_translations - FOR EACH ROW EXECUTE PROCEDURE page_translations_search_vector_update(); - SQL - end - - def down - execute <<~SQL - DROP TRIGGER IF EXISTS page_translations_search_vector_trigger ON page_translations; - DROP FUNCTION IF EXISTS page_translations_search_vector_update(); - SQL - remove_index :page_translations, name: 'index_page_translations_on_search_vector' - remove_column :page_translations, :search_vector - end -end diff --git a/doc/INSTALL.md b/doc/INSTALL.md new file mode 100644 index 0000000..8056f7c --- /dev/null +++ b/doc/INSTALL.md @@ -0,0 +1,341 @@ +# CCCMS Installation Guide + +This document covers the non-obvious steps required to install the CCCMS +stack on a fresh FreeBSD jail. It assumes a FreeBSD 14.x base jail with +network access and a working pkg repository. + +## 1. Install packages + +```sh +pkg install gmake pkgconf curl gnupg git autoconf automake libtool bash \ + readline libyaml libffi gdbm libxml2 libxslt libical \ + postgresql16-server postgresql16-client \ + ImageMagick7-nox11 node vim +``` + +Note: the package is `ImageMagick7-nox11`, not `ImageMagick-nox11`. The +nox11 variant avoids pulling in the entire X11 dependency chain. + +## 2. Enable sysvipc for the jail + +PostgreSQL uses System V shared memory for inter-process communication. +On the host, the jail must have sysvipc enabled. In `/etc/jail.conf` or +the jail's ezjail configuration: + + `allow.sysvipc = 1;` + +Restart the jail after making this change. Without it, PostgreSQL will +fail to start with a shared memory error. + +## 3. Enable and initialise PostgreSQL + +```sh +# Enable PostgreSQL in rc.conf +echo 'postgresql_enable="YES"' >> /etc/rc.conf + +# Initialise the database cluster +service postgresql initdb + +# Start PostgreSQL +service postgresql start +``` + +## 4. Create database roles and set permissions + +```sh +psql -U postgres postgres +``` + +```sql +CREATE ROLE rails WITH LOGIN PASSWORD 'your-password-here'; +ALTER ROLE rails CREATEDB; +``` + +`CREATEDB` is required for the Rails test suite to create and drop the +test database between runs. + +## 5. Create databases + +```sql +CREATE DATABASE cccms_production OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +CREATE DATABASE cccms_dev OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +CREATE DATABASE psql_test OWNER rails ENCODING 'UTF8' + LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0; +``` + +`TEMPLATE template0` is required when specifying a non-default locale. + +## 6. Restore the database dump (or start empty) + +If restoring from a pg_dump: + +```sh +pg_restore -U postgres -d cccms_production \ + --no-owner --no-acl /path/to/cccms_production.dump + +pg_restore -U postgres -d cccms_dev \ + --no-owner --no-acl /path/to/cccms_production.dump +``` + +Expected harmless warnings: +- `schema "public" already exists` — benign, ignore +- `array_accum aggregate` failure — dead code, ignore + +Transfer ownership to the rails user (REASSIGN OWNED does not work on +PostgreSQL 15+ due to system object protection): + +```sh +psql -U postgres cccms_production +``` + +```sql +DO $$ +DECLARE + obj RECORD; +BEGIN + FOR obj IN + SELECT tablename FROM pg_tables WHERE schemaname = 'public' + LOOP + EXECUTE 'ALTER TABLE public.' || quote_ident(obj.tablename) || + ' OWNER TO rails'; + END LOOP; + FOR obj IN + SELECT sequence_name FROM information_schema.sequences + WHERE sequence_schema = 'public' + LOOP + EXECUTE 'ALTER SEQUENCE public.' || quote_ident(obj.sequence_name) || + ' OWNER TO rails'; + END LOOP; + FOR obj IN + SELECT viewname FROM pg_views WHERE schemaname = 'public' + LOOP + EXECUTE 'ALTER VIEW public.' || quote_ident(obj.viewname) || + ' OWNER TO rails'; + END LOOP; +END $$; + +ALTER SCHEMA public OWNER TO rails; +``` + +Repeat for `cccms_dev`. + +## 7. Clone the repository + +```sh +cd /usr/local/www +git clone https://github.com/erdgeist/cccms.git +cd cccms +git checkout rails-upgrade +``` + +## 8. Copy assets (optional but recommended) + +The `public/system/uploads/` directory contains all uploaded files +referenced by the database. Without it, images and attachments will be +missing throughout the site. + +```sh +# On the source system: +tar -czf /tmp/cccms_uploads.tar.gz -C /usr/local/www cccms/public/system + +# On the new system: +tar -xzf /path/to/cccms_uploads.tar.gz -C /usr/local/www +``` + +## 9. Copy gitignored config files + +These files are not in the repository and must be copied or created: + +```sh +# Required: +config/database.yml +config/initializers/secret_token.rb + +# If used: +config/initializers/exception_notification.rb +/usr/local/etc/unicorn.rb +``` + +`database.yml` template: + +```yaml +development: + adapter: postgresql + encoding: unicode + database: cccms_dev + pool: 5 + username: rails + password: your-password-here + +test: + adapter: postgresql + encoding: UTF8 + database: psql_test + username: rails + password: + +production: + adapter: postgresql + encoding: unicode + database: cccms_production + pool: 5 + username: rails + password: your-password-here +``` + +## 10. Install rvm + +rvm 1.29.12 is the latest formal release as of mid-2026. Download and +verify before installing: + +```sh +curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz \ + -o /tmp/rvm-1.29.12.tar.gz +curl -L https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz.asc \ + -o /tmp/rvm-1.29.12.tar.gz.asc + +gpg --keyserver hkps://keys.openpgp.org \ + --recv-keys 7D2BAF1CF37B13E2069D6956105BD0E739499BDB + +gpg --verify /tmp/rvm-1.29.12.tar.gz.asc /tmp/rvm-1.29.12.tar.gz +``` + +If verification passes: + +```sh +tar -xzf /tmp/rvm-1.29.12.tar.gz -C /tmp +bash /tmp/rvm-1.29.12/install --auto-dotfiles +source /usr/local/rvm/scripts/rvm +``` + +The installed rvm ships with a stale known-versions list that only goes +to Ruby 3.0.0. Update it immediately: + +```sh +curl -L https://raw.githubusercontent.com/rvm/rvm/master/config/known \ + -o /usr/local/rvm/config/known +rvm list known | grep '^\[ruby-\]3\.' +``` + +Should now show Ruby 3.2.x and later. + +## 11. Install Ruby and create gemset + +```sh +source /usr/local/rvm/scripts/rvm +rvm install 3.2.11 --autolibs=read-only --with-opt-dir=/usr/local +rvm use 3.2.11 +rvm gemset create rails7-upgrade +rvm use 3.2.11@rails7-upgrade +``` + +The `.ruby-version` and `.ruby-gemset` files in the project root will +cause rvm to switch automatically when entering the project directory. + +## 12. Install bundler and gems + +```sh +gem install bundler +cd /usr/local/www/cccms +export MAKE=gmake +bundle install 2>&1 | tee /tmp/bundle_install.log +``` + +`MAKE=gmake` is required because FreeBSD's native make (BSD make) uses +different `-j` syntax than native gems expect. Without it, several native +gem compilations will fail. + +## 13. Run migrations + +```sh +bundle exec rails db:migrate +``` + +If restoring an existing database, first insert fake migration versions +to prevent re-running migrations that were applied to the old schema: + +```sql +INSERT INTO schema_migrations (version) VALUES + ('20260624035149'), ('20260624035150'), ('20260624035151'), + ('20260624035152'), ('20260624035153'), + ('20260625031409') +ON CONFLICT DO NOTHING; +``` + +Then run `db:migrate` to apply only new migrations. + +To enable full-text search (requires PostgreSQL 10+ with plpgsql): + +```sh +mv doc/20260626025705_add_search_vector_to_page_translations.rb.pending \ + db/migrate/20260626025705_add_search_vector_to_page_translations.rb +bundle exec rails db:migrate +``` + +## 14. Compile admin assets + +```sh +bundle exec rails assets:precompile +``` + +This compiles the admin JavaScript bundle (jQuery, jQuery UI, hotkeys) +into `public/assets/`. Required for the admin interface to work. Must be +re-run after any changes to `app/assets/javascripts/admin_bundle.js`. + +## 15. Run tests (optional but recommended) + +```sh +bundle exec rake test +``` + +Expected result: 129 runs, ~339 assertions, 3 failures, 0 errors. +The 3 failures are pre-existing and documented in the handover document. + +## 16. Start the server + +Development: + +```sh +bundle exec rails server -p 3000 -b 0.0.0.0 -e development +``` + +Note: `-b 0.0.0.0` is required — `localhost` does not resolve inside +a FreeBSD jail. + +Production (unicorn): + +```sh +/usr/local/rvm/gems/ruby-3.2.11@rails7-upgrade/wrappers/unicorn \ + -c /usr/local/etc/unicorn.rb -E production -D +``` + +The rc.d script at `/etc/rc.d/cccms` needs updating from `unicorn_rails` +to `unicorn` before use — see the handover document for details. + +## Known Gotchas + +**sysvipc:** PostgreSQL will fail silently or with a cryptic error if +sysvipc is not enabled for the jail. Enable it on the host before starting +PostgreSQL. + +**MAKE=gmake:** Native gem compilation fails without this. Set it before +every `bundle install` or add to your shell profile. + +**rvm known versions:** rvm 1.29.12 ships with a stale `config/known` that +only lists Ruby up to 3.0.0. Always update from master after installing rvm. + +**ImageMagick 7:** The `convert` command is deprecated; use `magick convert`. +The `file_attachment.rb` concern needs updating before production use. + +**pg_hba.conf:** The default FreeBSD PostgreSQL configuration uses `trust` +for local Unix socket connections, which is sufficient for the application. +No changes needed unless TCP connections are required. + +**assets:precompile:** Must be run after checkout and after any changes to +admin JavaScript. The compiled files in `public/assets/` are gitignored. + +**chaos_calendar include path:** On FreeBSD 14.x with libical 3.0.20+, +the include path is `` not ``. This is already +fixed in the `erdgeist-ruby1.9` branch. diff --git a/doc/rc.d_cccms b/doc/rc.d_cccms index 62e8bde..8404bc7 100644 --- a/doc/rc.d_cccms +++ b/doc/rc.d_cccms @@ -25,6 +25,13 @@ required_dirs="${cccms_dir}" extra_commands="reload" sig_reload="USR2" +cccms_prestart() +{ + mkdir -p /usr/local/www/cccms/tmp/pids /var/log + touch /var/log/unicorn.stderr.log + chown www:www /var/log/unicorn.stderr.log +} + export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin export RAILS_ENV=production export HOME=/root diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index 1a29eff..4b08356 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css @@ -131,11 +131,12 @@ form.button_to input[type="submit"] { font: inherit; color: inherit; cursor: pointer; - text-decoration: underline; + text-decoration: none } form.button_to input[type="submit"]:hover { - color: inherit; + color: #ffffff; + background-color: #ff9600; } #admin_wizard { diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 3ace95c..d37b428 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -8,13 +8,13 @@ class UsersControllerTest < ActionController::TestCase assert_response :success assert_select "a", { :count => 0, :text => "Destroy" } end - + test "get index as admin user renders admin partial" do login_as :aaron get :index assert_response :success - assert_select "a", "destroy" - assert_select "a", "show", "Show Link is missing" + assert_select "input[type=submit][value=destroy]" + assert_select "a", "show" end test "get new when logged in as admin" do diff --git a/test/test_helper.rb b/test/test_helper.rb index e7b15da..d438bb5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -47,6 +47,10 @@ class ActiveSupport::TestCase # Add more helper methods to be used by all tests here... + setup do + I18n.locale = I18n.default_locale + end + def create_node_with_published_page node = create_node_with_draft draft = node.draft -- cgit v1.3 From 420506e58fdfc84f1a5bede0a01dedf0af3bb4f3 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sat, 27 Jun 2026 16:58:53 +0200 Subject: Stage 7: Rails 7.2 → 8.1 on Ruby 3.2.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump Rails to 8.1.3 (Ruby unchanged at 3.2.11, new gemset rails8-upgrade) - config.load_defaults 8.1; merge app:update diffs for all environment files - Remove routing-filter 0.7.0; replace with native scope '(:locale)' in routes.rb and default_url_options in ApplicationController - Delete config/initializers/routing_filter_rails71_patch.rb - Replace vendored TinyMCE 3.x (~200 files) with tinymce-rails ~> 8.3; migrate admin_interface.js from jQuery .tinymce()/advanced theme to tinymce.init(); add config/tinymce.yml; note: TinyMCE 7+ is GPL - rails-i18n ~> 8.0 added explicitly (previously indirect dependency) - awesome_nested_set, acts-as-taggable-on pinned to git main/master (gemspec activerecord < 8.1 ceiling; no functional incompatibility; repin to version once upstream releases updated gemspecs) - globalize ~> 7.0, libxml-ruby ~> 5.0, nokogiri ~> 1.18, pg ~> 1.5 - sass-rails, coffee-rails, uglifier moved from :assets group to main (Sprockets 4 convention; :assets group no longer meaningful) - Node: head, draft, lock_owner marked belongs_to optional: true - Page: node, user, editor marked belongs_to optional: true - Static assets in public/images/ and public/javascripts/ referenced via plain HTML tags; Rails 8 load_defaults raises on pipeline helpers for undeclared assets - sessions_controller_test.rb: remove stale require and dead rescue_action - users_controller_test.rb: assert button[type=submit] not input[type=submit] (Rails 8 button_to renders