diff --git a/Gemfile b/Gemfile index 21382e178..3867f8ac3 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.4.9" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' -gem "rails", "~> 7.2.2" +gem "rails", "~> 8.1.3" # Use postgresql as the database for Active Record gem "pg", "~> 1.1" # Use Puma as the app server @@ -44,7 +44,7 @@ gem "view_component", "~> 4.9" # Use the AWS S3 SDK as storage mechanism gem "aws-sdk-s3" # Track changes to models for auditing or versioning. -gem "paper_trail", "~> 15.2" +gem "paper_trail", "~> 17.0" # Store active record objects in version whodunnits gem "paper_trail-globalid" @@ -75,9 +75,6 @@ gem "unread" gem "nokogiri", "~> 1.19.1" gem "thor", "~> 1.4.0" -# Pinning until activesupport is updated to v8.1.2 -gem "connection_pool", "~> 2.5" - group :development, :test do # Check gems for known vulnerabilities gem "bundler-audit" diff --git a/Gemfile.lock b/Gemfile.lock index 4759f6ec7..18da10810 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,83 +1,84 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.3.1) - actionpack (= 7.2.3.1) - activesupport (= 7.2.3.1) + action_text-trix (2.1.19) + railties + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.3.1) - actionpack (= 7.2.3.1) - activejob (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (7.2.3.1) - actionpack (= 7.2.3.1) - actionview (= 7.2.3.1) - activejob (= 7.2.3.1) - activesupport (= 7.2.3.1) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.3.1) - actionview (= 7.2.3.1) - activesupport (= 7.2.3.1) - cgi + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.3.1) - actionpack (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + actiontext (8.1.3) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.3.1) - activesupport (= 7.2.3.1) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) - cgi erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.3.1) - activesupport (= 7.2.3.1) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (7.2.3.1) - activesupport (= 7.2.3.1) + activemodel (8.1.3) + activesupport (= 8.1.3) activemodel-serializers-xml (1.0.3) activemodel (>= 5.0.0.a) activesupport (>= 5.0.0.a) builder (~> 3.1) - activerecord (7.2.3.1) - activemodel (= 7.2.3.1) - activesupport (= 7.2.3.1) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) - activestorage (7.2.3.1) - actionpack (= 7.2.3.1) - activejob (= 7.2.3.1) - activerecord (= 7.2.3.1) - activesupport (= 7.2.3.1) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (7.2.3.1) + activesupport (8.1.3) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) - minitest (>= 5.1, < 6) + minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) @@ -115,7 +116,6 @@ GEM thread_safe (~> 0.3, >= 0.3.1) base64 (0.3.0) bcrypt (3.1.22) - benchmark (0.5.0) better_html (2.2.0) actionview (>= 7.0) activesupport (>= 7.0) @@ -149,13 +149,12 @@ GEM capybara-screenshot (1.0.26) capybara (>= 1.0, < 4) launchy - cgi (0.5.2) childprocess (5.0.0) coderay (1.1.3) coercible (1.0.0) descendants_tracker (~> 0.0.1) - concurrent-ruby (1.3.7) - connection_pool (2.5.5) + concurrent-ruby (1.3.8) + connection_pool (3.0.2) crack (1.0.0) bigdecimal rexml @@ -190,7 +189,7 @@ GEM drb (2.2.3) dumb_delegator (1.0.0) encryptor (3.0.0) - erb (6.0.4) + erb (6.0.6) erb_lint (0.9.0) activesupport better_html (>= 2.0.1) @@ -223,7 +222,7 @@ GEM fugit (1.12.2) et-orbi (~> 1.4) raabro (~> 1.4) - globalid (1.3.0) + globalid (1.4.0) activesupport (>= 6.1) govuk-components (6.4.0) html-attributes-utils (~> 1.0.0, >= 1.0.0) @@ -253,7 +252,7 @@ GEM jmespath (1.6.2) jsbundling-rails (1.3.0) railties (>= 6.0.0) - json (2.20.0) + json (2.21.1) json-schema (4.1.1) addressable (>= 2.8) jwt (3.2.0) @@ -281,22 +280,25 @@ GEM loofah (2.25.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.1) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.0.4) + marcel (1.2.1) matrix (0.4.2) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.27.0) + minitest (6.0.6) + drb (~> 2.0) + prism (~> 1.5) msgpack (1.8.3) multipart-post (2.4.1) nested_form (0.3.2) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.6.4.1) + net-imap (0.6.6) date net-protocol net-pop (0.1.2) @@ -322,8 +324,8 @@ GEM iniparse (~> 1.4) rexml (~> 3.2) pagy (9.4.0) - paper_trail (15.2.0) - activerecord (>= 6.1) + paper_trail (17.0.0) + activerecord (>= 7.1) request_store (~> 1.4) paper_trail-globalid (0.2.0) globalid @@ -336,7 +338,7 @@ GEM racc pg (1.5.5) possessive (1.0.1) - pp (0.6.3) + pp (0.6.4) prettyprint prettyprint (0.2.0) prism (1.9.0) @@ -351,9 +353,6 @@ GEM pry-byebug (3.10.1) byebug (~> 11.0) pry (>= 0.13, < 0.15) - psych (5.3.1) - date - stringio public_suffix (7.0.5) puma (7.2.1) nio4r (~> 2.0) @@ -373,20 +372,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (7.2.3.1) - actioncable (= 7.2.3.1) - actionmailbox (= 7.2.3.1) - actionmailer (= 7.2.3.1) - actionpack (= 7.2.3.1) - actiontext (= 7.2.3.1) - actionview (= 7.2.3.1) - activejob (= 7.2.3.1) - activemodel (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 7.2.3.1) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -401,10 +400,9 @@ GEM nested_form (~> 0.3) rails (>= 6.0, < 9) turbo-rails (>= 1.0, < 3) - railties (7.2.3.1) - actionpack (= 7.2.3.1) - activesupport (= 7.2.3.1) - cgi + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -417,9 +415,14 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (7.2.0) + rbs (4.1.0) + logger + prism (>= 1.6.0) + tsort + rdoc (8.0.0) erb - psych (>= 4.0.0) + prism (>= 1.6.0) + rbs (>= 4.0.0) tsort redcarpet (3.6.0) redis (4.8.1) @@ -531,7 +534,6 @@ GEM smart_properties (1.17.0) stimulus-rails (1.3.3) railties (>= 6.0.0) - stringio (3.2.0) thor (1.4.0) thread_safe (0.3.6) timecop (0.9.8) @@ -595,7 +597,6 @@ DEPENDENCIES capybara capybara-lockstep capybara-screenshot - connection_pool (~> 2.5) cssbundling-rails devise devise_two_factor_authentication @@ -615,7 +616,7 @@ DEPENDENCIES nokogiri (~> 1.19.1) notifications-ruby-client overcommit (>= 0.37.0) - paper_trail (~> 15.2) + paper_trail (~> 17.0) paper_trail-globalid parallel_tests pg (~> 1.1) @@ -627,7 +628,7 @@ DEPENDENCIES rack (~> 3.1.20) rack-attack rack-mini-profiler (~> 3.3.0) - rails (~> 7.2.2) + rails (~> 8.1.3) rails_admin (~> 3.1) redcarpet (~> 3.6) redis (~> 4.8) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c8ad4cb12..9efc48490 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -11,7 +11,11 @@ class UsersController < ApplicationController before_action -> { filter_manager.serialize_filters_to_session }, if: :current_user, only: %i[index] def index - redirect_to users_organisation_path(current_user.organisation) unless current_user.support? + # CSV requests fall through to the respond_to block below, which returns 401 + # for non-support users. Only HTML non-support users are redirected. The + # redirect must `return`: Rails 8 raises DoubleRenderError if the action then + # also renders/heads (previously the later render silently won). + return redirect_to users_organisation_path(current_user.organisation) unless current_user.support? || request.format.csv? all_users = User.visible.sorted_by_organisation_and_role filtered_users = filter_manager.filtered_users(all_users, search_term, session_filters) diff --git a/app/models/download_record.rb b/app/models/download_record.rb index 09c8ee851..497fa7062 100644 --- a/app/models/download_record.rb +++ b/app/models/download_record.rb @@ -11,8 +11,8 @@ class DownloadRecord < ApplicationRecord scheme_location: 3, }.freeze - enum download_type: DOWNLOAD_TYPE - enum user_role: User::ROLES + enum :download_type, DOWNLOAD_TYPE + enum :user_role, User::ROLES def self.build_from_user(user:, **attrs) new( diff --git a/app/models/location.rb b/app/models/location.rb index 4d9084f8c..19469a12e 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -172,7 +172,10 @@ class Location < ApplicationRecord LOCAL_AUTHORITIES = LocalAuthority.all.map { |la| [la.name, la.code] }.to_h attribute :local_authorities, :string - enum :local_authorities, LOCAL_AUTHORITIES + # LOCAL_AUTHORITIES is derived from a class-load-time DB query, so it can be + # empty on a fresh/unseeded database. Rails 8.1's `enum` raises on empty + # values (7.2 treated it as a no-op), so only declare it when populated. + enum :local_authorities, LOCAL_AUTHORITIES if LOCAL_AUTHORITIES.present? def self.local_authorities_for_current_year LocalAuthority.all.active(Time.zone.today).england.map { |la| [la.code, la.name] }.to_h end diff --git a/app/views/locations/deactivate_confirm.html.erb b/app/views/locations/deactivate_confirm.html.erb index 6748af918..d1966e0ec 100644 --- a/app/views/locations/deactivate_confirm.html.erb +++ b/app/views/locations/deactivate_confirm.html.erb @@ -1,4 +1,4 @@ -<%= form_with model: @location_deactivation_period, url: scheme_location_deactivate_path(@location), method: "patch", local: true do |f| %> +<%= form_with url: scheme_location_deactivate_path(@location), method: "patch", local: true do |f| %> <% content_for :before_content do %> <%= govuk_back_link(href: :back) %> <% end %> diff --git a/app/views/schemes/deactivate_confirm.html.erb b/app/views/schemes/deactivate_confirm.html.erb index a73208e75..af0ced18e 100644 --- a/app/views/schemes/deactivate_confirm.html.erb +++ b/app/views/schemes/deactivate_confirm.html.erb @@ -1,4 +1,4 @@ -<%= form_with model: @scheme_deactivation_period, url: scheme_deactivate_path(@scheme), method: "patch", local: true do |f| %> +<%= form_with url: scheme_deactivate_path(@scheme), method: "patch", local: true do |f| %> <% content_for :before_content do %> <%= govuk_back_link(href: :back) %> <% end %> diff --git a/config/application.rb b/config/application.rb index cb8979a78..a30333d43 100644 --- a/config/application.rb +++ b/config/application.rb @@ -21,7 +21,7 @@ Bundler.require(*Rails.groups) module DataCollector class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 7.2 + config.load_defaults 8.1 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. diff --git a/config/initializers/devise_two_factor_authentication_rails8_patch.rb b/config/initializers/devise_two_factor_authentication_rails8_patch.rb new file mode 100644 index 000000000..a3dc8d910 --- /dev/null +++ b/config/initializers/devise_two_factor_authentication_rails8_patch.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Compatibility patch for devise_two_factor_authentication 3.0.0 on Rails 8.1+. +# +# The gem's route mapper passes the non-standard :resend_code action into +# `resource ... only: [...]`. Rails 8.1 tightened `resource`/`resources` to raise +# an ArgumentError when :only/:except contain anything outside the standard REST +# actions, so route drawing blows up before the app can boot. +# +# The `resend_code` route is actually created by the `collection { ... }` block +# inside the resource, so listing :resend_code in :only was always a no-op. +# We redefine the mapper method to drop it, leaving behaviour identical. +# +# Remove this file if the gem publishes a Rails 8.1-compatible release. +module ActionDispatch::Routing + class Mapper + protected + + def devise_two_factor_authentication(mapping, controllers) + resource :two_factor_authentication, + only: %i[show update], + path: mapping.path_names[:two_factor_authentication], + controller: controllers[:two_factor_authentication] do + collection { get resend_code_path(mapping), as: "resend_code" } + end + end + end +end diff --git a/spec/features/accessibility_spec.rb b/spec/features/accessibility_spec.rb index d039a70b5..d0ef930df 100644 --- a/spec/features/accessibility_spec.rb +++ b/spec/features/accessibility_spec.rb @@ -69,6 +69,7 @@ RSpec.describe "Accessibility", :js do context "when viewing organisation pages" do let(:parent_relationship) { create(:organisation_relationship, parent_organisation: other_user.organisation) } let(:child_relationship) { create(:organisation_relationship, child_organisation: other_user.organisation) } + let(:name_change) { create(:organisation_name_change, organisation: other_user.organisation) } let(:organisation_paths) do routes = find_routes("organisation", other_user.organisation, other_user.organisation).reject do |route| route.match?(/\A\/organisations\/#{other_user.organisation_id}\z/) || @@ -81,12 +82,15 @@ RSpec.describe "Accessibility", :js do "filters/update" => "?codes_only=false", "stock-owners/remove" => "?target_organisation_id=#{child_relationship.parent_organisation.id}", "managing-agents/remove" => "?target_organisation_id=#{parent_relationship.child_organisation.id}", + "organisations/search" => "?query=test", } routes.map do |route| additional_params = route_mappings.find { |pattern, _| route.include?(pattern) }&.last route += additional_params if additional_params - route + # find_routes does not substitute :change_id, so provide a real record id + # to visit the actual cancel-name-change page instead of an error page. + route.gsub(":change_id", name_change.id.to_s) end end