From 7db25aa1597dd62d580c86aceff23913ee85d842 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:47:42 +0100
Subject: [PATCH 01/30] Bump Rails to 8 and update gem dependencies
ENG-1743
---
Gemfile | 10 +-
Gemfile.lock | 256 +++++++++++++++++++++++++--------------------------
2 files changed, 132 insertions(+), 134 deletions(-)
diff --git a/Gemfile b/Gemfile
index e68bc19756..2a42ca1728 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,10 +4,9 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.3.11"
gem "activestorage-validator"
-gem "administrate", "~> 0.20.0"
+gem "administrate"
gem "administrate-field-active_storage"
gem "administrate-field-jsonb"
-gem "administrate-field-nested_has_many"
gem "attr_encrypted"
gem "audited"
gem "aws-sdk-s3", require: false
@@ -43,7 +42,10 @@ gem "prawn"
gem "puma"
gem "rack-attack"
gem "rack-cors", require: "rack/cors"
-gem "rails", "~> 7.1.0"
+gem "ostruct"
+gem "rails", "~> 8.0"
+gem "sprockets-rails"
+gem "sassc-rails"
gem "rails-healthcheck"
gem "rest-client"
gem "scout_apm"
@@ -96,7 +98,7 @@ group :development do
gem "spring"
gem "spring-watcher-listen"
gem "web-console", ">= 3.3.0"
- gem "bullet", "~> 7.1.0"
+ gem "bullet"
gem "standard"
gem "standard-rails"
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 3b0b5c6361..7aee0b5ba1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,104 +1,95 @@
GEM
remote: https://rubygems.org/
specs:
- actioncable (7.1.5.2)
- actionpack (= 7.1.5.2)
- activesupport (= 7.1.5.2)
+ 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.1.5.2)
- actionpack (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activestorage (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- mail (>= 2.7.1)
- net-imap
- net-pop
- net-smtp
- actionmailer (7.1.5.2)
- actionpack (= 7.1.5.2)
- actionview (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- mail (~> 2.5, >= 2.5.4)
- net-imap
- net-pop
- net-smtp
+ 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 (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.1.5.2)
- actionview (= 7.1.5.2)
- activesupport (= 7.1.5.2)
+ actionpack (8.1.3)
+ actionview (= 8.1.3)
+ activesupport (= 8.1.3)
nokogiri (>= 1.8.5)
- racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
- actiontext (7.1.5.2)
- actionpack (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activestorage (= 7.1.5.2)
- activesupport (= 7.1.5.2)
+ useragent (~> 0.16)
+ 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.1.5.2)
- activesupport (= 7.1.5.2)
+ actionview (8.1.3)
+ activesupport (= 8.1.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
- activejob (7.1.5.2)
- activesupport (= 7.1.5.2)
+ activejob (8.1.3)
+ activesupport (= 8.1.3)
globalid (>= 0.3.6)
- activemodel (7.1.5.2)
- activesupport (= 7.1.5.2)
- activerecord (7.1.5.2)
- activemodel (= 7.1.5.2)
- activesupport (= 7.1.5.2)
+ activemodel (8.1.3)
+ activesupport (= 8.1.3)
+ activerecord (8.1.3)
+ activemodel (= 8.1.3)
+ activesupport (= 8.1.3)
timeout (>= 0.4.0)
- activestorage (7.1.5.2)
- actionpack (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activesupport (= 7.1.5.2)
+ activestorage (8.1.3)
+ actionpack (= 8.1.3)
+ activejob (= 8.1.3)
+ activerecord (= 8.1.3)
+ activesupport (= 8.1.3)
marcel (~> 1.0)
activestorage-validator (0.4.0)
rails (>= 6.1.0)
- activesupport (7.1.5.2)
+ activesupport (8.1.3)
base64
- benchmark (>= 0.3)
bigdecimal
- concurrent-ruby (~> 1.0, >= 1.0.2)
+ 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)
- mutex_m
securerandom (>= 0.3)
- tzinfo (~> 2.0)
+ tzinfo (~> 2.0, >= 2.0.5)
+ uri (>= 0.13.1)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
- administrate (0.20.1)
- actionpack (>= 6.0, < 8.0)
- actionview (>= 6.0, < 8.0)
- activerecord (>= 6.0, < 8.0)
- jquery-rails (~> 4.6.0)
+ administrate (1.0.0)
+ actionpack (>= 6.0, < 9.0)
+ actionview (>= 6.0, < 9.0)
+ activerecord (>= 6.0, < 9.0)
kaminari (~> 1.2.2)
- sassc-rails (~> 2.1)
- selectize-rails (~> 0.6)
administrate-field-active_storage (1.0.6)
administrate (>= 0.2.2)
rails (>= 7.0)
- administrate-field-jsonb (0.4.6)
- administrate (< 1.0.0)
+ administrate-field-jsonb (0.4.8)
+ administrate (< 2.0)
rails (>= 4.2)
- administrate-field-nested_has_many (2.1.0)
- administrate (>= 0.19, < 1)
- cocoon (~> 1.2, >= 1.2.11)
ast (2.4.3)
attr_encrypted (4.2.0)
encryptor (~> 3.0.0)
@@ -146,7 +137,7 @@ GEM
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
- bigdecimal (3.2.2)
+ bigdecimal (3.3.1)
bindex (0.8.1)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
@@ -154,7 +145,7 @@ GEM
msgpack (~> 1.2)
brakeman (6.0.1)
builder (3.3.0)
- bullet (7.1.6)
+ bullet (8.1.3)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (12.0.0)
@@ -169,20 +160,19 @@ GEM
xpath (~> 3.2)
choice (0.2.0)
climate_control (1.2.0)
- cloudflare-rails (6.2.0)
- actionpack (>= 7.1.0, < 8.1.0)
- activesupport (>= 7.1.0, < 8.1.0)
- railties (>= 7.1.0, < 8.1.0)
+ cloudflare-rails (7.0.0)
+ actionpack (>= 7.2.0, < 8.2.0)
+ activesupport (>= 7.2.0, < 8.2.0)
+ railties (>= 7.2.0, < 8.2.0)
zeitwerk (>= 2.5.0)
- cocoon (1.2.15)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
combine_pdf (1.0.23)
matrix
ruby-rc4 (>= 0.1.5)
- concurrent-ruby (1.3.5)
- connection_pool (2.5.4)
+ concurrent-ruby (1.3.6)
+ connection_pool (3.0.2)
crack (0.4.5)
rexml
crass (1.0.6)
@@ -237,7 +227,7 @@ GEM
enumerize (2.7.0)
activesupport (>= 3.2)
equalizer (0.0.11)
- erb (5.0.2)
+ erb (6.0.4)
erb_lint (0.5.0)
activesupport
better_html (>= 2.0.1)
@@ -262,7 +252,7 @@ GEM
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
- ffi (1.17.2)
+ ffi (1.17.4)
fiber-storage (1.0.1)
fog-aws (3.30.0)
base64 (~> 0.2.0)
@@ -287,7 +277,7 @@ GEM
geocoder (1.8.5)
base64 (>= 0.1.0)
csv (>= 3.0.0)
- globalid (1.2.1)
+ globalid (1.3.0)
activesupport (>= 6.1)
graphlient (0.8.0)
faraday (~> 2.0)
@@ -320,22 +310,19 @@ GEM
http-cookie (1.0.5)
domain_name (~> 0.5)
humanize (2.5.1)
- i18n (1.14.7)
+ i18n (1.14.8)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
interception (0.5)
- io-console (0.8.1)
- irb (1.15.2)
+ io-console (0.8.2)
+ irb (1.18.0)
pp (>= 0.6.0)
+ prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jaro_winkler (1.5.6)
jmespath (1.6.2)
- jquery-rails (4.6.0)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
- json (2.7.1)
+ json (2.19.8)
jwt (2.7.1)
kaminari (1.2.2)
activesupport (>= 4.1.0)
@@ -364,16 +351,17 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
- loofah (2.24.1)
+ loofah (2.25.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
lumberjack (1.2.9)
- mail (2.8.1)
+ mail (2.9.0)
+ logger
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
- marcel (1.0.4)
+ marcel (1.2.1)
matrix (0.4.3)
memcachier (0.0.2)
method_source (1.1.0)
@@ -386,15 +374,16 @@ GEM
rake
mini_mime (1.1.5)
mini_portile2 (2.8.9)
- minitest (5.25.5)
+ minitest (6.0.6)
+ drb (~> 2.0)
+ prism (~> 1.5)
msgpack (1.8.0)
multi_json (1.15.0)
multi_xml (0.6.0)
- mutex_m (0.3.0)
nenv (0.3.0)
net-http (0.6.0)
uri
- net-imap (0.5.14)
+ net-imap (0.6.4.1)
date
net-protocol
net-pop (0.1.2)
@@ -404,7 +393,7 @@ GEM
net-smtp (0.5.1)
net-protocol
netrc (0.11.0)
- nio4r (2.7.4)
+ nio4r (2.7.5)
nokogiri (1.19.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
@@ -431,6 +420,7 @@ GEM
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
+ ostruct (0.6.3)
pagy (6.4.3)
parallel (1.24.0)
parser (3.3.11.1)
@@ -441,12 +431,13 @@ GEM
pg_search (2.3.6)
activerecord (>= 5.2)
activesupport (>= 5.2)
- pp (0.6.2)
+ pp (0.6.3)
prettyprint
prawn (2.4.0)
pdf-core (~> 0.9.0)
ttfunk (~> 1.7)
prettyprint (0.2.0)
+ prism (1.9.0)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -458,7 +449,7 @@ GEM
pry-rescue (1.6.0)
interception (>= 0.5)
pry (>= 0.12.0)
- psych (5.2.6)
+ psych (5.4.0)
date
stringio
public_suffix (7.0.5)
@@ -483,22 +474,22 @@ GEM
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
- rackup (2.2.1)
+ rackup (2.3.1)
rack (>= 3)
- rails (7.1.5.2)
- actioncable (= 7.1.5.2)
- actionmailbox (= 7.1.5.2)
- actionmailer (= 7.1.5.2)
- actionpack (= 7.1.5.2)
- actiontext (= 7.1.5.2)
- actionview (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activemodel (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activestorage (= 7.1.5.2)
- activesupport (= 7.1.5.2)
+ 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.1.5.2)
+ railties (= 8.1.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -515,26 +506,28 @@ GEM
rails-healthcheck (1.4.0)
actionpack
railties
- rails-html-sanitizer (1.6.2)
- loofah (~> 2.21)
+ rails-html-sanitizer (1.7.0)
+ loofah (~> 2.25)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
- railties (7.1.5.2)
- actionpack (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- irb
+ railties (8.1.3)
+ actionpack (= 8.1.3)
+ activesupport (= 8.1.3)
+ irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
+ tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
- rake (13.3.0)
+ rake (13.4.2)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rbs (2.8.4)
- rdoc (6.14.2)
+ rdoc (7.2.0)
erb
psych (>= 4.0.0)
+ tsort
redis-client (0.24.0)
connection_pool
reek (6.3.0)
@@ -543,7 +536,7 @@ GEM
rainbow (>= 2.0, < 4.0)
rexml (~> 3.1)
regexp_parser (2.11.2)
- reline (0.6.2)
+ reline (0.6.3)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
@@ -618,7 +611,6 @@ GEM
scout_apm (6.2.0)
parser
securerandom (0.4.1)
- selectize-rails (0.12.6)
selenium-webdriver (4.35.0)
base64 (~> 0.2)
logger (~> 1.4)
@@ -703,14 +695,15 @@ GEM
lint_roller (~> 1.0)
rubocop-rails (~> 2.20.2)
statesman (13.1.0)
- stringio (3.1.7)
+ stringio (3.2.0)
terser (1.2.5)
execjs (>= 0.3.0, < 3)
- thor (1.4.0)
+ thor (1.5.0)
thread_safe (0.3.6)
- tilt (2.6.1)
+ tilt (2.7.0)
timecop (0.9.10)
timeout (0.6.1)
+ tsort (0.2.0)
ttfunk (1.7.0)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
@@ -723,13 +716,14 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.5.0)
- uniform_notifier (1.16.0)
- uri (1.0.4)
+ uniform_notifier (1.18.0)
+ uri (1.1.1)
+ useragent (0.16.11)
version_gem (1.1.3)
- view_component (3.11.0)
- activesupport (>= 5.2.0, < 8.0)
- concurrent-ruby (~> 1.0)
- method_source (~> 1.0)
+ view_component (4.12.0)
+ actionview (>= 7.1.0)
+ activesupport (>= 7.1.0)
+ concurrent-ruby (~> 1)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
@@ -750,7 +744,7 @@ GEM
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket (1.2.11)
- websocket-driver (0.8.0)
+ websocket-driver (0.8.1)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -759,17 +753,16 @@ GEM
xpath (3.2.0)
nokogiri (~> 1.8)
yard (0.9.36)
- zeitwerk (2.7.3)
+ zeitwerk (2.8.2)
PLATFORMS
ruby
DEPENDENCIES
activestorage-validator
- administrate (~> 0.20.0)
+ administrate
administrate-field-active_storage
administrate-field-jsonb
- administrate-field-nested_has_many
attr_encrypted
audited
awesome_print
@@ -779,7 +772,7 @@ DEPENDENCIES
binding_of_caller
bootsnap (>= 1.5.1)
brakeman
- bullet (~> 7.1.0)
+ bullet
byebug
capybara
climate_control
@@ -817,6 +810,7 @@ DEPENDENCIES
omniauth-auth0
omniauth-oauth2
omniauth-rails_csrf_protection
+ ostruct
pagy
pg
pg_search
@@ -829,7 +823,7 @@ DEPENDENCIES
rack-attack
rack-cors
rack-mini-profiler
- rails (~> 7.1.0)
+ rails (~> 8.0)
rails-controller-testing
rails-erd
rails-healthcheck
@@ -840,6 +834,7 @@ DEPENDENCIES
rspec-rails
rspec_junit_formatter
ruby-debug-ide
+ sassc-rails
scout_apm
selenium-webdriver (~> 4.35.0)
sentry-rails
@@ -853,6 +848,7 @@ DEPENDENCIES
solargraph
spring
spring-watcher-listen
+ sprockets-rails
stackprof
standard
standard-rails
From c208364c260a8b3a1e8b38ebe5c4a5cba1726c04 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:48:07 +0100
Subject: [PATCH 02/30] Remove administrate-field-nested_has_many asset
manifest entries
ENG-1743
---
app/assets/config/manifest.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
index 6893239885..1c21ceb666 100644
--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
@@ -1,7 +1,5 @@
//= link administrate/application.css
//= link administrate/application.js
-//= link administrate-field-nested_has_many/application.css
-//= link administrate-field-nested_has_many/application.js
//= link administrate-field-active_storage/application.css
//= link administrate-field-jsonb/application.css
//= link administrate-field-jsonb/application.js
From 270078612db557a5756f75da851edc423206d137 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:48:27 +0100
Subject: [PATCH 03/30] Fix Rails 8 enum syntax in Badge
ENG-1743
---
app/models/badge.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/models/badge.rb b/app/models/badge.rb
index 3ba5528e6e..0149b9bf82 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -4,7 +4,7 @@ class Badge < ApplicationRecord
validates :academic_year, presence: true, format: /\A\d{4}-\d{2}\z/
validates :trigger_type, presence: true
- enum trigger_type: [:cpd, :completion]
+ enum :trigger_type, [:cpd, :completion]
scope :active, -> { where(active: true) }
end
From 65abfb75f9a107ab5ae0f5687758b63450be5b9b Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:50:39 +0100
Subject: [PATCH 04/30] Fix routes for Rails 8 and add ViewComponent preview
routes
Rails 8 rejects non-standard action names in :only. Remove :perform_sync and :perform_reset from users resources and add them as standalone named routes.
Add explicit ViewComponent preview routes before the CMS wildcard.
ENG-1743
---
config/routes.rb | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/config/routes.rb b/config/routes.rb
index 66e4946158..2fe7e5e0d4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -32,7 +32,7 @@
end
resources :sent_emails, only: %i[index show]
resources :support_audits, only: %i[index show update edit]
- resources :users, only: %i[index create show edit perform_sync perform_reset update] do
+ resources :users, only: %i[index create show edit update] do
get "/perform_sync/:user_id", to: "users#perform_sync", as: :perform_sync
get "/perform_reset/:user_id", to: "users#perform_reset_tests", as: :perform_reset
get "/generate_assessment_attempt", to: "users#generate_assessment_attempt", as: :generate_assessment_attempt unless Rails.env.production?
@@ -247,5 +247,10 @@
get "/blog", to: "cms#blog", as: :cms_posts
get "/blog/articles", to: redirect(path: "/blog")
get "/blog/:page_slug", to: "cms#blog_resource", as: :cms_post
+ if Rails.env.development? || Rails.env.staging?
+ get "/rails/components", to: "view_components#index"
+ get "/rails/components/*path", to: "view_components#previews"
+ end
+
get "/*page_slug", to: "cms#web_page_resource", as: :cms_page
end
From 9abbee26ca7d1a1faafa5f91d0af5307feee8a75 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:52:53 +0100
Subject: [PATCH 05/30] Fix ViewComponent 4 breaking changes
Initialize no longer accepts arguments, replace super with super() in components that define their own initialize
Add initializer to delegate image_pack_tag to helpers, which ViewComponent 4 no longer does automatically.
ENG-1743
---
app/components/modal_component.rb | 2 +-
app/components/related_links_component.rb | 2 +-
config/initializers/view_component_helpers.rb | 6 ++++++
3 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 config/initializers/view_component_helpers.rb
diff --git a/app/components/modal_component.rb b/app/components/modal_component.rb
index 07e0b584ef..0d921c4d8a 100644
--- a/app/components/modal_component.rb
+++ b/app/components/modal_component.rb
@@ -8,7 +8,7 @@ class ModalComponent < ViewComponent::Base
attr_reader :expanded, :title, :reopen_button_text, :show_corner_decoration, :class_name, :reopen_button_class, :modal_id
def initialize(title:, expanded: false, reopen_button_text: nil, show_corner_decoration: true, class_name: nil, reopen_button_class: nil, z_index: 1000, modal_id: SecureRandom.hex(6), data: {})
- super
+ super()
@expanded = expanded
@title = title
@reopen_button_text = reopen_button_text
diff --git a/app/components/related_links_component.rb b/app/components/related_links_component.rb
index 930fc18720..1780ebfe6a 100644
--- a/app/components/related_links_component.rb
+++ b/app/components/related_links_component.rb
@@ -4,7 +4,7 @@ class RelatedLinksComponent < ViewComponent::Base
include ViewComponent::Translatable
def initialize(links: [], class_name: nil, image_url: nil)
- super
+ super()
@links = links
@class_name = class_name
@image_url = image_url
diff --git a/config/initializers/view_component_helpers.rb b/config/initializers/view_component_helpers.rb
new file mode 100644
index 0000000000..f500418f77
--- /dev/null
+++ b/config/initializers/view_component_helpers.rb
@@ -0,0 +1,6 @@
+# image_pack_tag is a Webpacker helper. ViewComponent 4.x no longer auto-delegates
+# helper methods, so we delegate explicitly here. Remove in Phase 3 when webpacker
+# is replaced by importmap-rails and image_pack_tag becomes image_tag.
+Rails.application.config.to_prepare do
+ ViewComponent::Base.delegate :image_pack_tag, to: :helpers
+end
From 9327a64caf51760eaa9d1a953629a8c04f926d96 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:54:02 +0100
Subject: [PATCH 06/30] Update environment configs for Rails 8 and
ViewComponent 4
Remove config.sass, update mailer preview_path to preview_paths and update ViewComponent preview config to the 4 API
ENG-1743
---
config/environments/development.rb | 6 +++---
config/environments/production.rb | 2 --
config/environments/staging.rb | 8 +++-----
config/environments/test.rb | 2 --
4 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 5b7a9539ce..dca48c501a 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -81,7 +81,7 @@
# Suppress logger output for asset requests.
config.assets.quiet = true
- config.action_mailer.preview_path = "#{Rails.root}/previews/mailers"
+ config.action_mailer.preview_paths = ["#{Rails.root}/previews/mailers"]
config.action_mailer.asset_host = "http://localhost:3000"
config.action_mailer.default_url_options = {host: "http://localhost:3000"}
@@ -106,8 +106,8 @@
config.hosts << "teachcomputing.test"
config.autoload_paths << "lib"
- config.view_component.preview_paths << "#{Rails.root}/previews/components"
- config.view_component.preview_route = "/rails/components"
+ config.view_component.previews.paths << "#{Rails.root}/previews/components"
+ config.view_component.previews.route = "/rails/components"
config.view_component.generate.sidecar = true
config.action_mailer.default_url_options = {host: "teachcomputing.rpfdev.com"}
diff --git a/config/environments/production.rb b/config/environments/production.rb
index bc0578a038..abf371b470 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -27,8 +27,6 @@
# Compress JavaScripts and CSS.
config.assets.js_compressor = :terser
config.assets.css_compressor = nil
- config.sass.style = :compressed
- config.sass.line_comments = false
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
diff --git a/config/environments/staging.rb b/config/environments/staging.rb
index 387645d4eb..555054601e 100644
--- a/config/environments/staging.rb
+++ b/config/environments/staging.rb
@@ -25,8 +25,6 @@
# Compress JavaScripts and CSS.
config.assets.js_compressor = :terser
config.assets.css_compressor = nil
- config.sass.style = :compressed
- config.sass.line_comments = false
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
@@ -80,7 +78,7 @@
config.action_mailer.show_previews = true
config.action_mailer.asset_host = "https://staging.teachcomputing.org"
- config.action_mailer.preview_path = "#{Rails.root}/previews/mailers"
+ config.action_mailer.preview_paths = ["#{Rails.root}/previews/mailers"]
config.action_mailer.default_url_options = {host: "https://staging.teachcomputing.org"}
config.action_mailer.smtp_settings = {
address: "smtp.mandrillapp.com",
@@ -126,8 +124,8 @@
config.middleware.use Rack::Attack
- config.view_component.preview_paths << "#{Rails.root}/previews/components"
- config.view_component.preview_route = "/rails/components"
+ config.view_component.previews.paths << "#{Rails.root}/previews/components"
+ config.view_component.previews.route = "/rails/components"
config.view_component.show_previews = true
# Enable secure cookies (will only work on https)
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 97ef7138e3..28f6baf6c4 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -64,8 +64,6 @@
config.i18n.raise_on_missing_translations = true
config.assets.css_compressor = nil
- config.sass.style = :compressed
- config.sass.line_comments = false
config.middleware.use Rack::Attack
From 3e2ffcf5d90e6b16712ac01a853b815cd4dd5356 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:55:07 +0100
Subject: [PATCH 07/30] Fix Time integer arithmetic for Rails 8.1
Rails 8.1 no longer allows adding a plain integer to a TimeWithZone. Add .seconds to convert before adding
ENG-1743
---
app/views/certificates/cs_accelerator/_csa-test.html.erb | 2 +-
app/views/certificates/cs_accelerator/_exam.html.erb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/views/certificates/cs_accelerator/_csa-test.html.erb b/app/views/certificates/cs_accelerator/_csa-test.html.erb
index 1353a49748..0b8d97b9eb 100644
--- a/app/views/certificates/cs_accelerator/_csa-test.html.erb
+++ b/app/views/certificates/cs_accelerator/_csa-test.html.erb
@@ -16,7 +16,7 @@
<% elsif user_programme_assessment.can_take_test_at != 0 %>
- Your <%= to_word_ordinal(user_programme_assessment.failed_num_attempts + 1) %> attempt at the test can be done after<%= "#{(Time.zone.now + user_programme_assessment.can_take_test_at).in_time_zone('London').strftime('%l%P on %A')}" %>.
+ Your <%= to_word_ordinal(user_programme_assessment.failed_num_attempts + 1) %> attempt at the test can be done after <%= "#{(Time.zone.now + user_programme_assessment.can_take_test_at.seconds).in_time_zone('London').strftime('%-l%P on %A')}" %>.
You may want to consider completing additional courses before your next attempt. You can find full details of areas for improvement on your results email, and course recommendations in the <%= link_to 'CSA Handbook', 'https://ncce.io/csa-handbook', class: 'ncce-link', data: tracking_data('CSA Handbook') %>.
diff --git a/app/views/certificates/cs_accelerator/_exam.html.erb b/app/views/certificates/cs_accelerator/_exam.html.erb
index e742a2706d..1774e60626 100644
--- a/app/views/certificates/cs_accelerator/_exam.html.erb
+++ b/app/views/certificates/cs_accelerator/_exam.html.erb
@@ -18,7 +18,7 @@
<% if @user_programme_assessment.currently_taking_test? %>
You are currently taking the test. If you have recently failed, please come back in 2 hours.
<% elsif @user_programme_assessment.can_take_test_at != 0 %>
- Your <%= to_word_ordinal(@user_programme_assessment.num_attempts + 1) %> attempt at the test can be done after <%= "#{(Time.zone.now + @user_programme_assessment.can_take_test_at).in_time_zone('London').strftime('%l%P on %A')}" %>, please come back then!
+ Your <%= to_word_ordinal(@user_programme_assessment.num_attempts + 1) %> attempt at the test can be done after <%= "#{(Time.zone.now + @user_programme_assessment.can_take_test_at.seconds).in_time_zone('London').strftime('%-l%P on %A')}" %>, please come back then!
<% else %>
<%= form_for(current_user, method: :post, url: assessment_attempts_path(assessment_attempt: { assessment_id: @programme.assessment.id, user_id: current_user }),
html: {
From b0bafa7fe3daf1eb5ccd83184ed6909198d0c9ab Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 09:55:35 +0100
Subject: [PATCH 08/30] Fix specs for Rails 8 and ViewComponent 4 changes
ENG-1743
---
spec/components/no_courses_component_spec.rb | 4 ++--
spec/views/certificates/cs_accelerator/_csa-test_spec.rb | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/spec/components/no_courses_component_spec.rb b/spec/components/no_courses_component_spec.rb
index 87162ebe36..85c45896cc 100644
--- a/spec/components/no_courses_component_spec.rb
+++ b/spec/components/no_courses_component_spec.rb
@@ -2,12 +2,12 @@
RSpec.describe NoCoursesComponent, type: :component do
it "shows link to all courses" do
- render_inline(described_class.new(hub: nil))
+ render_inline(described_class.new)
expect(page).to have_link("Show all courses", href: "/courses#results-top")
end
it "shows correct text" do
- render_inline(described_class.new(hub: nil))
+ render_inline(described_class.new)
expect(page).to have_text("Sorry, we couldn't find any courses")
end
end
diff --git a/spec/views/certificates/cs_accelerator/_csa-test_spec.rb b/spec/views/certificates/cs_accelerator/_csa-test_spec.rb
index c72c2a0870..794606279d 100644
--- a/spec/views/certificates/cs_accelerator/_csa-test_spec.rb
+++ b/spec/views/certificates/cs_accelerator/_csa-test_spec.rb
@@ -70,12 +70,12 @@
user_programme_assessment = instance_double(UserProgrammeAssessment)
allow(user_programme_assessment).to receive(:currently_taking_test?).and_return(false)
- allow(user_programme_assessment).to receive(:can_take_test_at).and_return(time + 1.day)
+ allow(user_programme_assessment).to receive(:can_take_test_at).and_return(1.day.to_i)
allow(user_programme_assessment).to receive(:failed_num_attempts).and_return(1)
render partial: "csa-test", locals: {user_programme_assessment:}
- expect(rendered).to have_content("Your second attempt at the test can be done after 1am on Thursday.")
+ expect(rendered).to have_content("Your second attempt at the test can be done after 12pm on Thursday.")
end
end
end
From fc44e4f047310be22576f917a40f9b95111003a2 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 10:35:17 +0100
Subject: [PATCH 09/30] rspec-rails bumped to 8.0.4
Webpacker compile block now captures the result properly
ENG-1743
---
Gemfile.lock | 26 +++++++++++++-------------
spec/rails_helper.rb | 5 ++++-
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 7aee0b5ba1..2d72b68af8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -185,7 +185,7 @@ GEM
debug_inspector (1.2.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
- diff-lcs (1.5.1)
+ diff-lcs (1.6.2)
docile (1.4.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
@@ -553,24 +553,24 @@ GEM
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
- rspec-core (3.13.2)
+ rspec-core (3.13.6)
rspec-support (~> 3.13.0)
- rspec-expectations (3.13.3)
+ rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-json_expectations (2.2.0)
- rspec-mocks (3.13.2)
+ rspec-mocks (3.13.8)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
- rspec-rails (7.1.0)
- actionpack (>= 7.0)
- activesupport (>= 7.0)
- railties (>= 7.0)
- rspec-core (~> 3.13)
- rspec-expectations (~> 3.13)
- rspec-mocks (~> 3.13)
- rspec-support (~> 3.13)
- rspec-support (3.13.2)
+ rspec-rails (8.0.4)
+ actionpack (>= 7.2)
+ activesupport (>= 7.2)
+ railties (>= 7.2)
+ rspec-core (>= 3.13.0, < 5.0.0)
+ rspec-expectations (>= 3.13.0, < 5.0.0)
+ rspec-mocks (>= 3.13.0, < 5.0.0)
+ rspec-support (>= 3.13.0, < 5.0.0)
+ rspec-support (3.13.7)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.59.0)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 323b7e797a..fe58bb9fbb 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -86,7 +86,10 @@ def page
end
config.before(:suite) do
- Webpacker.compile if Webpacker.instance.compiler.stale?
+ if Webpacker.instance.compiler.stale?
+ success = Webpacker.compile
+ warn "Webpacker compilation failed" unless success
+ end
end
config.before(:each) { stub_cloudflare_ip_lookup }
From 229b66385899e73f4a9fd45d2bc88597cc964610 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 11:01:26 +0100
Subject: [PATCH 10/30] Improve Webpacker compilation error handling in RSpec
configuration
ENG-1743
---
spec/rails_helper.rb | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index fe58bb9fbb..9742f6409a 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -86,10 +86,9 @@ def page
end
config.before(:suite) do
- if Webpacker.instance.compiler.stale?
- success = Webpacker.compile
- warn "Webpacker compilation failed" unless success
- end
+ Webpacker.compile if Webpacker.instance.compiler.stale?
+ rescue => e
+ warn "Webpacker setup error (non-fatal): #{e.message}"
end
config.before(:each) { stub_cloudflare_ip_lookup }
From b10560ae2034256280c4af8b1803e34cfd011752 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 11:15:48 +0100
Subject: [PATCH 11/30] Add diagnostic hook for SimpleCov to handle spurious
exit codes
ENG-1743
---
spec/rails_helper.rb | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 9742f6409a..81f2d768d0 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,6 +21,16 @@
end
end
+# Diagnostic hook: runs just before SimpleCov's at_exit (LIFO order).
+# Remove once the root cause of the spurious exit code 1 is identified.
+at_exit do
+ next if $!.nil?
+ next if $!.is_a?(SystemExit) && $!.status == 0
+
+ warn "[SimpleCov diagnostic] $! = #{$!.class}: #{$!.message}"
+ warn $!.backtrace.first(10).join("\n") if $!.respond_to?(:backtrace) && $!.backtrace
+end
+
ENV["RAILS_ENV"] ||= "test"
Dotenv.overload(".env.test") # Ensure .env.test is used in dev environments
From 14ede4768ce89a4d33fa26fbf9fa5f6a80bb4417 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 12:30:32 +0100
Subject: [PATCH 12/30] Refactor SimpleCov at_exit hook to ensure report
generation despite spurious exit codes
ENG-1743
---
spec/rails_helper.rb | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 81f2d768d0..67a8b6cf1d 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,14 +21,12 @@
end
end
-# Diagnostic hook: runs just before SimpleCov's at_exit (LIFO order).
-# Remove once the root cause of the spurious exit code 1 is identified.
-at_exit do
- next if $!.nil?
- next if $!.is_a?(SystemExit) && $!.status == 0
-
- warn "[SimpleCov diagnostic] $! = #{$!.class}: #{$!.message}"
- warn $!.backtrace.first(10).join("\n") if $!.respond_to?(:backtrace) && $!.backtrace
+# Capybara's Selenium driver calls exit() in its at_exit hook to quit Chrome, which
+# sets $! to SystemExit before SimpleCov's at_exit runs. SimpleCov's default handler
+# treats any non-nil $! as a "previous error" and skips report generation, causing a
+# spurious exit code 1. Override it to always generate the report.
+SimpleCov.at_exit do
+ SimpleCov.result.format!
end
ENV["RAILS_ENV"] ||= "test"
From f82e427651cf61dfcfbbbf041bca585b7bde9fae Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 13:21:47 +0100
Subject: [PATCH 13/30] Set THOR_SILENCE_DEPRECATION environment variable and
remove SimpleCov at_exit override
ENG-1743
---
.circleci/config.yml | 1 +
spec/rails_helper.rb | 7 -------
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index da9f0dd523..c15f200018 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -39,6 +39,7 @@ jobs:
PGHOST: 127.0.0.1
PGUSER: ncce_test
RAILS_ENV: test
+ THOR_SILENCE_DEPRECATION: "1"
- image: cimg/postgres:14.7
environment:
POSTGRES_USER: postgres
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 67a8b6cf1d..f8a371285e 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,13 +21,6 @@
end
end
-# Capybara's Selenium driver calls exit() in its at_exit hook to quit Chrome, which
-# sets $! to SystemExit before SimpleCov's at_exit runs. SimpleCov's default handler
-# treats any non-nil $! as a "previous error" and skips report generation, causing a
-# spurious exit code 1. Override it to always generate the report.
-SimpleCov.at_exit do
- SimpleCov.result.format!
-end
ENV["RAILS_ENV"] ||= "test"
Dotenv.overload(".env.test") # Ensure .env.test is used in dev environments
From 56fad595318d9c1a68f319e593c648d3958e5813 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 13:25:00 +0100
Subject: [PATCH 14/30] Standardrb fix
ENG-1743
---
spec/rails_helper.rb | 1 -
1 file changed, 1 deletion(-)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index f8a371285e..9742f6409a 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,7 +21,6 @@
end
end
-
ENV["RAILS_ENV"] ||= "test"
Dotenv.overload(".env.test") # Ensure .env.test is used in dev environments
From bcd6e30bc6754d24e902228b5db9243d2f2a21f9 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 13:35:45 +0100
Subject: [PATCH 15/30] Diagnosing ci pipleine test discrepancies and
additional logging
ENG-1743
---
.circleci/config.yml | 10 ++++++++++
spec/rails_helper.rb | 10 ++++++++++
2 files changed, 20 insertions(+)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c15f200018..6fa04f7e36 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -83,11 +83,21 @@ jobs:
- browser-tools/install-browser-tools:
chrome-version: latest
replace-existing-chrome: true
+ - run:
+ name: Spec file count (dry run)
+ command: |
+ echo "=== Spec files on disk ==="
+ find spec -name "*_spec.rb" | wc -l
+ echo "=== Examples RSpec would load ==="
+ bundle exec rspec --dry-run --format progress 2>&1 | tail -3
- run:
name: Run rspec
command: |
mkdir ~/rspec
bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml
+ EXIT=$?
+ echo "=== RSpec exit code: $EXIT ==="
+ exit $EXIT
- store_test_results:
path: ~/rspec
- store_artifacts:
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 9742f6409a..022bd6b47c 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,6 +21,16 @@
end
end
+# Diagnostic: runs just before SimpleCov's at_exit (LIFO). Remove once root cause is identified.
+at_exit do
+ if $!
+ warn "[diagnostic] at_exit $! = #{$!.class}: #{$!.message} (status=#{$!.status if $!.respond_to?(:status)})"
+ warn "[diagnostic] backtrace:\n#{$!.backtrace&.first(15)&.join("\n")}"
+ else
+ warn "[diagnostic] at_exit $! is nil — clean exit"
+ end
+end
+
ENV["RAILS_ENV"] ||= "test"
Dotenv.overload(".env.test") # Ensure .env.test is used in dev environments
From 0d3bf2d89d24ab26cd3c7391298e06132ece171a Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 15:01:53 +0100
Subject: [PATCH 16/30] Refactor RSpec commands for improved logging and remove
diagnostic at_exit hook
ENG-1743
---
.circleci/config.yml | 10 +++++-----
spec/rails_helper.rb | 10 ----------
2 files changed, 5 insertions(+), 15 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6fa04f7e36..2890c71e1e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -84,19 +84,19 @@ jobs:
chrome-version: latest
replace-existing-chrome: true
- run:
- name: Spec file count (dry run)
+ name: Spec file count
command: |
echo "=== Spec files on disk ==="
find spec -name "*_spec.rb" | wc -l
- echo "=== Examples RSpec would load ==="
- bundle exec rspec --dry-run --format progress 2>&1 | tail -3
- run:
name: Run rspec
command: |
mkdir ~/rspec
- bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml
- EXIT=$?
+ bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml 2>&1 | tee /tmp/rspec.log
+ EXIT=${PIPESTATUS[0]}
echo "=== RSpec exit code: $EXIT ==="
+ echo "=== Spec load errors ==="
+ grep -A 3 "An error occurred while loading" /tmp/rspec.log || echo "None"
exit $EXIT
- store_test_results:
path: ~/rspec
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 022bd6b47c..9742f6409a 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -21,16 +21,6 @@
end
end
-# Diagnostic: runs just before SimpleCov's at_exit (LIFO). Remove once root cause is identified.
-at_exit do
- if $!
- warn "[diagnostic] at_exit $! = #{$!.class}: #{$!.message} (status=#{$!.status if $!.respond_to?(:status)})"
- warn "[diagnostic] backtrace:\n#{$!.backtrace&.first(15)&.join("\n")}"
- else
- warn "[diagnostic] at_exit $! is nil — clean exit"
- end
-end
-
ENV["RAILS_ENV"] ||= "test"
Dotenv.overload(".env.test") # Ensure .env.test is used in dev environments
From 2a7dce2536bbf4ffe35b4a0b900a1618cebc07eb Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 11 Jun 2026 15:49:00 +0100
Subject: [PATCH 17/30] CircleCI test debugging
---
.circleci/config.yml | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2890c71e1e..c5f8997d86 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -92,11 +92,18 @@ jobs:
name: Run rspec
command: |
mkdir ~/rspec
+ set +e
bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml 2>&1 | tee /tmp/rspec.log
EXIT=${PIPESTATUS[0]}
echo "=== RSpec exit code: $EXIT ==="
- echo "=== Spec load errors ==="
- grep -A 3 "An error occurred while loading" /tmp/rspec.log || echo "None"
+ echo "=== Load error count ==="
+ grep -c "An error occurred while loading" /tmp/rspec.log || echo "0"
+ echo "=== Load error details ==="
+ grep -A 5 "An error occurred while loading" /tmp/rspec.log || echo "None"
+ echo "=== Errors outside examples ==="
+ grep "errors occurred outside of examples" /tmp/rspec.log || echo "None"
+ echo "=== RSpec summary ==="
+ grep -E "^[0-9]+ examples?" /tmp/rspec.log || echo "Not found"
exit $EXIT
- store_test_results:
path: ~/rspec
From 22734810d31cf104e07e7ff1557dc6c684b191b2 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 08:19:44 +0100
Subject: [PATCH 18/30] Enhance SimpleCov debugging output for better error
tracking
ENG-1743
---
spec/rails_helper.rb | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 9742f6409a..8ea45bf1b2 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -3,6 +3,20 @@
require "simplecov"
SimpleCov.minimum_coverage ENV["SIMPLECOV_MIN_COVERAGE"].to_i
+
+SimpleCov.singleton_class.prepend(Module.new do
+ def exit_status_from_exception
+ err = $ERROR_INFO
+ if err
+ $stderr.puts "[SC Debug] $! = #{err.class}: #{err.message}"
+ $stderr.puts "[SC Debug] backtrace: #{err.backtrace&.first(3)&.join(" | ")}"
+ else
+ $stderr.puts "[SC Debug] $! is nil (clean exit)"
+ end
+ super
+ end
+end)
+
SimpleCov.start "rails" do
require_relative "support/simplecov_warnings_patch" # To remove excess warnings from line below
enable_coverage_for_eval
From 00ab3b5baf75232fb0caa4299be2862bf07b8446 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 08:50:39 +0100
Subject: [PATCH 19/30] Enhance SimpleCov error handling and coverage
processing in RSpec
ENG-1743
---
.circleci/config.yml | 6 +++++-
spec/rails_helper.rb | 22 +++++++++++++++++++++-
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c5f8997d86..25880158a6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -101,9 +101,13 @@ jobs:
echo "=== Load error details ==="
grep -A 5 "An error occurred while loading" /tmp/rspec.log || echo "None"
echo "=== Errors outside examples ==="
- grep "errors occurred outside of examples" /tmp/rspec.log || echo "None"
+ grep "error occurred outside of examples" /tmp/rspec.log || echo "None"
echo "=== RSpec summary ==="
grep -E "^[0-9]+ examples?" /tmp/rspec.log || echo "Not found"
+ echo "=== Pending examples ==="
+ grep -E "^[0-9]+ pending" /tmp/rspec.log || echo "None"
+ echo "=== JUnit formatter exit ==="
+ grep -i "junit\|xml\|format" /tmp/rspec.log | tail -5 || echo "None"
exit $EXIT
- store_test_results:
path: ~/rspec
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 8ea45bf1b2..5039f1bbfd 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -8,13 +8,33 @@
def exit_status_from_exception
err = $ERROR_INFO
if err
- $stderr.puts "[SC Debug] $! = #{err.class}: #{err.message}"
+ status = err.is_a?(SystemExit) ? err.status : "N/A"
+ $stderr.puts "[SC Debug] $! = #{err.class} (status=#{status}): #{err.message}"
$stderr.puts "[SC Debug] backtrace: #{err.backtrace&.first(3)&.join(" | ")}"
else
$stderr.puts "[SC Debug] $! is nil (clean exit)"
end
super
end
+
+ # Capybara's at_exit calls `exit @exit_status` to preserve the test suite exit code,
+ # which SimpleCov sees as a "previous error" and short-circuits without processing
+ # coverage. This override always runs coverage and exits with the max of both statuses.
+ def run_exit_tasks!
+ error_exit_status = exit_status_from_exception
+ at_exit.call
+
+ coverage_exit_status = 0
+ if ready_to_process_results?
+ coverage_exit_status = process_result(result)
+ if coverage_exit_status.positive? && print_error_status
+ warn("SimpleCov failed with exit #{coverage_exit_status} due to a coverage related error")
+ end
+ end
+
+ final_exit_status = [error_exit_status.to_i, coverage_exit_status].max
+ Kernel.exit(final_exit_status) if final_exit_status.positive?
+ end
end)
SimpleCov.start "rails" do
From 9dbdc50cd9690a97835f6a3c3328462d28a7a882 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 09:37:40 +0100
Subject: [PATCH 20/30] Enhance RSpec debugging output for improved error
tracking and clarity
ENG-1743
---
.circleci/config.yml | 4 ++++
spec/rails_helper.rb | 15 ++++++++++++---
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 25880158a6..c46a02ddf7 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -96,10 +96,14 @@ jobs:
bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml 2>&1 | tee /tmp/rspec.log
EXIT=${PIPESTATUS[0]}
echo "=== RSpec exit code: $EXIT ==="
+ echo "=== Runner debug ==="
+ grep "\[Runner Debug\]" /tmp/rspec.log || echo "None"
echo "=== Load error count ==="
grep -c "An error occurred while loading" /tmp/rspec.log || echo "0"
echo "=== Load error details ==="
grep -A 5 "An error occurred while loading" /tmp/rspec.log || echo "None"
+ echo "=== Suite/context hook errors ==="
+ grep "An error occurred in" /tmp/rspec.log || echo "None"
echo "=== Errors outside examples ==="
grep "error occurred outside of examples" /tmp/rspec.log || echo "None"
echo "=== RSpec summary ==="
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 5039f1bbfd..838192093b 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -9,10 +9,10 @@ def exit_status_from_exception
err = $ERROR_INFO
if err
status = err.is_a?(SystemExit) ? err.status : "N/A"
- $stderr.puts "[SC Debug] $! = #{err.class} (status=#{status}): #{err.message}"
- $stderr.puts "[SC Debug] backtrace: #{err.backtrace&.first(3)&.join(" | ")}"
+ warn "[SC Debug] $! = #{err.class} (status=#{status}): #{err.message}"
+ warn "[SC Debug] backtrace: #{err.backtrace&.first(3)&.join(" | ")}"
else
- $stderr.puts "[SC Debug] $! is nil (clean exit)"
+ warn "[SC Debug] $! is nil (clean exit)"
end
super
end
@@ -62,6 +62,15 @@ def run_exit_tasks!
abort("The Rails environment is running in production mode!") if Rails.env.production?
require "rspec/rails"
+
+RSpec::Core::Runner.prepend(Module.new do
+ def exit_code(examples_passed = false)
+ result = super
+ warn "[Runner Debug] exit_code: examples_passed=#{examples_passed}, non_example_failure=#{@world.non_example_failure}, result=#{result}"
+ result
+ end
+end)
+
require "webmock/rspec"
require "rspec/json_expectations"
require "capybara/rspec"
From b62749adfe842dffc59d3d592340d064b36d49d5 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 10:05:50 +0100
Subject: [PATCH 21/30] Enhance RSpec debugging output with additional reporter
and exit trace information
ENG-1743
---
.circleci/config.yml | 2 +-
spec/rails_helper.rb | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c46a02ddf7..f1febf447b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -97,7 +97,7 @@ jobs:
EXIT=${PIPESTATUS[0]}
echo "=== RSpec exit code: $EXIT ==="
echo "=== Runner debug ==="
- grep "\[Runner Debug\]" /tmp/rspec.log || echo "None"
+ grep "\[Runner Debug\]\|\[Reporter Debug\]\|\[Exit Trace\]" /tmp/rspec.log || echo "None"
echo "=== Load error count ==="
grep -c "An error occurred while loading" /tmp/rspec.log || echo "0"
echo "=== Load error details ==="
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 838192093b..f9a33f03ab 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -71,6 +71,26 @@ def exit_code(examples_passed = false)
end
end)
+RSpec::Core::Reporter.prepend(Module.new do
+ def finish
+ warn "[Reporter Debug] finish() starting, scope=#{RSpec.current_scope}"
+ super
+ warn "[Reporter Debug] finish() completed"
+ rescue => e
+ warn "[Reporter Debug] finish() raised #{e.class}: #{e.message}"
+ raise
+ end
+end)
+
+TracePoint.new(:raise) do |tp|
+ ex = tp.raised_exception
+ next unless ex.is_a?(SystemExit) && ex.status != 0
+
+ scope = RSpec.respond_to?(:current_scope) ? RSpec.current_scope : :unknown
+ warn "[Exit Trace] SystemExit(#{ex.status}) at #{tp.path}:#{tp.lineno}, scope=#{scope}"
+ warn "[Exit Trace] caller: #{caller.first(8).join(" | ")}"
+end.enable
+
require "webmock/rspec"
require "rspec/json_expectations"
require "capybara/rspec"
From e6e733f59ec2af95d3072821d70152c0683134c4 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 13:34:34 +0100
Subject: [PATCH 22/30] Fix generator invocation for view components and
improve error handling
ENG-1743
---
lib/generators/strapi/component_generator.rb | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/lib/generators/strapi/component_generator.rb b/lib/generators/strapi/component_generator.rb
index 0302985789..eca948011b 100644
--- a/lib/generators/strapi/component_generator.rb
+++ b/lib/generators/strapi/component_generator.rb
@@ -48,17 +48,19 @@ def create_data_text
def run_view_component_generator
Rails::Generators.invoke(
- "component",
+ "view_component:component",
["Cms::#{@component_name_class}", *@rails_param_names, "--test-framework=rspec", "--sidecar", "--preview"].compact,
- behaviour: :invoke,
+ behavior: :invoke,
destination_root:
)
- rescue
+ # Rails 8.1's Generators.invoke calls `exit 1` when the generator isn't
+ # found, so SystemExit must be caught here or it kills the process
+ rescue SystemExit, StandardError
puts <<~HEREDOC
#{"*" * 80}
Unable to create component, please run this command seperatly
- rails generate component Cms::#{@component_name_class} #{@rails_param_names.join(" ")} --test-framework=rspec
+ rails generate view_component:component Cms::#{@component_name_class} #{@rails_param_names.join(" ")} --test-framework=rspec
#{"*" * 80}
HEREDOC
From dca183baff281a43e2bb4116a88bd16e38634080 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 13:59:11 +0100
Subject: [PATCH 23/30] Remove CircleCI and rails_helper logging and
diagnostics
ENG-1743
---
.circleci/config.yml | 27 +------------------
spec/rails_helper.rb | 63 --------------------------------------------
2 files changed, 1 insertion(+), 89 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f1febf447b..c15f200018 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -83,36 +83,11 @@ jobs:
- browser-tools/install-browser-tools:
chrome-version: latest
replace-existing-chrome: true
- - run:
- name: Spec file count
- command: |
- echo "=== Spec files on disk ==="
- find spec -name "*_spec.rb" | wc -l
- run:
name: Run rspec
command: |
mkdir ~/rspec
- set +e
- bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml 2>&1 | tee /tmp/rspec.log
- EXIT=${PIPESTATUS[0]}
- echo "=== RSpec exit code: $EXIT ==="
- echo "=== Runner debug ==="
- grep "\[Runner Debug\]\|\[Reporter Debug\]\|\[Exit Trace\]" /tmp/rspec.log || echo "None"
- echo "=== Load error count ==="
- grep -c "An error occurred while loading" /tmp/rspec.log || echo "0"
- echo "=== Load error details ==="
- grep -A 5 "An error occurred while loading" /tmp/rspec.log || echo "None"
- echo "=== Suite/context hook errors ==="
- grep "An error occurred in" /tmp/rspec.log || echo "None"
- echo "=== Errors outside examples ==="
- grep "error occurred outside of examples" /tmp/rspec.log || echo "None"
- echo "=== RSpec summary ==="
- grep -E "^[0-9]+ examples?" /tmp/rspec.log || echo "Not found"
- echo "=== Pending examples ==="
- grep -E "^[0-9]+ pending" /tmp/rspec.log || echo "None"
- echo "=== JUnit formatter exit ==="
- grep -i "junit\|xml\|format" /tmp/rspec.log | tail -5 || echo "None"
- exit $EXIT
+ bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml
- store_test_results:
path: ~/rspec
- store_artifacts:
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index f9a33f03ab..9742f6409a 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -3,40 +3,6 @@
require "simplecov"
SimpleCov.minimum_coverage ENV["SIMPLECOV_MIN_COVERAGE"].to_i
-
-SimpleCov.singleton_class.prepend(Module.new do
- def exit_status_from_exception
- err = $ERROR_INFO
- if err
- status = err.is_a?(SystemExit) ? err.status : "N/A"
- warn "[SC Debug] $! = #{err.class} (status=#{status}): #{err.message}"
- warn "[SC Debug] backtrace: #{err.backtrace&.first(3)&.join(" | ")}"
- else
- warn "[SC Debug] $! is nil (clean exit)"
- end
- super
- end
-
- # Capybara's at_exit calls `exit @exit_status` to preserve the test suite exit code,
- # which SimpleCov sees as a "previous error" and short-circuits without processing
- # coverage. This override always runs coverage and exits with the max of both statuses.
- def run_exit_tasks!
- error_exit_status = exit_status_from_exception
- at_exit.call
-
- coverage_exit_status = 0
- if ready_to_process_results?
- coverage_exit_status = process_result(result)
- if coverage_exit_status.positive? && print_error_status
- warn("SimpleCov failed with exit #{coverage_exit_status} due to a coverage related error")
- end
- end
-
- final_exit_status = [error_exit_status.to_i, coverage_exit_status].max
- Kernel.exit(final_exit_status) if final_exit_status.positive?
- end
-end)
-
SimpleCov.start "rails" do
require_relative "support/simplecov_warnings_patch" # To remove excess warnings from line below
enable_coverage_for_eval
@@ -62,35 +28,6 @@ def run_exit_tasks!
abort("The Rails environment is running in production mode!") if Rails.env.production?
require "rspec/rails"
-
-RSpec::Core::Runner.prepend(Module.new do
- def exit_code(examples_passed = false)
- result = super
- warn "[Runner Debug] exit_code: examples_passed=#{examples_passed}, non_example_failure=#{@world.non_example_failure}, result=#{result}"
- result
- end
-end)
-
-RSpec::Core::Reporter.prepend(Module.new do
- def finish
- warn "[Reporter Debug] finish() starting, scope=#{RSpec.current_scope}"
- super
- warn "[Reporter Debug] finish() completed"
- rescue => e
- warn "[Reporter Debug] finish() raised #{e.class}: #{e.message}"
- raise
- end
-end)
-
-TracePoint.new(:raise) do |tp|
- ex = tp.raised_exception
- next unless ex.is_a?(SystemExit) && ex.status != 0
-
- scope = RSpec.respond_to?(:current_scope) ? RSpec.current_scope : :unknown
- warn "[Exit Trace] SystemExit(#{ex.status}) at #{tp.path}:#{tp.lineno}, scope=#{scope}"
- warn "[Exit Trace] caller: #{caller.first(8).join(" | ")}"
-end.enable
-
require "webmock/rspec"
require "rspec/json_expectations"
require "capybara/rspec"
From 1c148baf570e8d7a7d01d5f86ccaa37ec9ddef1b Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 12 Jun 2026 15:13:16 +0100
Subject: [PATCH 24/30] Fix ViewComponent previews by using the renamed
previews.enabled setting
ENG-1743
---
config/environments/staging.rb | 2 +-
config/routes.rb | 5 -----
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/config/environments/staging.rb b/config/environments/staging.rb
index 555054601e..c779ac8b73 100644
--- a/config/environments/staging.rb
+++ b/config/environments/staging.rb
@@ -126,7 +126,7 @@
config.view_component.previews.paths << "#{Rails.root}/previews/components"
config.view_component.previews.route = "/rails/components"
- config.view_component.show_previews = true
+ config.view_component.previews.enabled = true
# Enable secure cookies (will only work on https)
config.session_store :cookie_store,
diff --git a/config/routes.rb b/config/routes.rb
index 2fe7e5e0d4..847e44ba18 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -247,10 +247,5 @@
get "/blog", to: "cms#blog", as: :cms_posts
get "/blog/articles", to: redirect(path: "/blog")
get "/blog/:page_slug", to: "cms#blog_resource", as: :cms_post
- if Rails.env.development? || Rails.env.staging?
- get "/rails/components", to: "view_components#index"
- get "/rails/components/*path", to: "view_components#previews"
- end
-
get "/*page_slug", to: "cms#web_page_resource", as: :cms_page
end
From facb63f984ebe5bbfb68380df3be2661c8dab813 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Wed, 17 Jun 2026 09:18:41 +0100
Subject: [PATCH 25/30] Remove error handling for generator invocation in
ComponentGenerator
The rescue isn't needed anymore. The SystemExit was only being raised because "component" wasn't found - now that the namespace is correctly "view_component:component", the generator will be found and exit 1 won't be called
ENG-1743
---
lib/generators/strapi/component_generator.rb | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/lib/generators/strapi/component_generator.rb b/lib/generators/strapi/component_generator.rb
index eca948011b..f555d51928 100644
--- a/lib/generators/strapi/component_generator.rb
+++ b/lib/generators/strapi/component_generator.rb
@@ -53,17 +53,6 @@ def run_view_component_generator
behavior: :invoke,
destination_root:
)
- # Rails 8.1's Generators.invoke calls `exit 1` when the generator isn't
- # found, so SystemExit must be caught here or it kills the process
- rescue SystemExit, StandardError
- puts <<~HEREDOC
- #{"*" * 80}
- Unable to create component, please run this command seperatly
-
- rails generate view_component:component Cms::#{@component_name_class} #{@rails_param_names.join(" ")} --test-framework=rspec
- #{"*" * 80}
-
- HEREDOC
end
def print_method_defintions
From d6a20adb7061b20cdda17b381a62f2df1234b393 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Wed, 17 Jun 2026 14:39:48 +0100
Subject: [PATCH 26/30] Refactor user routes and controller actions to use
member routes and update parameter handling
ENG-1743
---
app/controllers/admin/users_controller.rb | 12 ++++++------
app/views/admin/users/_actions.html.erb | 8 ++++----
.../users/generate_assessment_attempt.html.erb | 2 +-
config/routes.rb | 10 ++++++----
spec/requests/admin/users_spec.rb | 14 +++++++-------
5 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 2e37a66aae..2b8d4d245b 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,7 +1,7 @@
module Admin
class UsersController < Admin::ApplicationController
def perform_sync
- user_id = params[:user_id]
+ user_id = params[:id]
Support::UserUtilities.sync(user_id)
redirect_back(
@@ -10,13 +10,13 @@ def perform_sync
)
end
- def perform_reset_tests
+ def perform_reset
admin_user = User.find_by_email(ENV.fetch("DEFAULT_ADMIN_EMAIL"))
- result = Support::UserUtilities.reset_tests(params[:user_id])
+ result = Support::UserUtilities.reset_tests(params[:id])
if result.empty?
redirect_back(
- fallback_location: admin_users_path(params[:user_id]),
+ fallback_location: admin_users_path(params[:id]),
flash: {notice: I18n.t("admin.users.actions.reset.empty")}
)
else
@@ -26,11 +26,11 @@ def perform_reset_tests
end
def generate_assessment_attempt
- @user = User.find(params[:user_id])
+ @user = User.find(params[:id])
end
def process_assessment_attempt
- @user = User.find(params[:user_id])
+ @user = User.find(params[:id])
assessment = Assessment.find(params[:assessment_id])
if assessment.activity
achievement = assessment.activity.achievements.find_or_initialize_by(user_id: @user.id)
diff --git a/app/views/admin/users/_actions.html.erb b/app/views/admin/users/_actions.html.erb
index 2f9cf757a8..1738b3ddfb 100644
--- a/app/views/admin/users/_actions.html.erb
+++ b/app/views/admin/users/_actions.html.erb
@@ -3,19 +3,19 @@
<%= link_to(
t(".sync.label"),
- admin_user_perform_sync_path(user_id: resource.id),
+ perform_sync_admin_user_path(resource),
class: "button"
) if accessible_action?(resource, :perform_sync) %>
<%= link_to(
t(".reset.label"),
- admin_user_perform_reset_path(user_id: resource.id),
+ perform_reset_admin_user_path(resource),
class: "button"
- ) if accessible_action?(resource, :perform_reset_tests) %>
+ ) if accessible_action?(resource, :perform_reset) %>
<%= link_to(
'Generate Assessment Attempt',
- admin_user_generate_assessment_attempt_path(user_id: resource.id),
+ generate_assessment_attempt_admin_user_path(resource),
class: "button"
) unless Rails.env.production? %>
diff --git a/app/views/admin/users/generate_assessment_attempt.html.erb b/app/views/admin/users/generate_assessment_attempt.html.erb
index ee5cab096e..e2f5415658 100644
--- a/app/views/admin/users/generate_assessment_attempt.html.erb
+++ b/app/views/admin/users/generate_assessment_attempt.html.erb
@@ -7,7 +7,7 @@
- <%= form_tag(admin_user_process_assessment_attempt_path(user_id: @user.id), method: :post) do %>
+ <%= form_tag(process_assessment_attempt_admin_user_path(@user), method: :post) do %>
diff --git a/config/routes.rb b/config/routes.rb
index 847e44ba18..a5f02a0742 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -33,10 +33,12 @@
resources :sent_emails, only: %i[index show]
resources :support_audits, only: %i[index show update edit]
resources :users, only: %i[index create show edit update] do
- get "/perform_sync/:user_id", to: "users#perform_sync", as: :perform_sync
- get "/perform_reset/:user_id", to: "users#perform_reset_tests", as: :perform_reset
- get "/generate_assessment_attempt", to: "users#generate_assessment_attempt", as: :generate_assessment_attempt unless Rails.env.production?
- post "/process_assessment_attempt", to: "users#process_assessment_attempt", as: :process_assessment_attempt unless Rails.env.production?
+ member do
+ get :perform_sync
+ get :perform_reset
+ get :generate_assessment_attempt unless Rails.env.production?
+ post :process_assessment_attempt unless Rails.env.production?
+ end
end
resources :user_programme_enrolments, only: %i[index show edit update] do
member do
diff --git a/spec/requests/admin/users_spec.rb b/spec/requests/admin/users_spec.rb
index 97908b33a7..efa186a82e 100644
--- a/spec/requests/admin/users_spec.rb
+++ b/spec/requests/admin/users_spec.rb
@@ -15,14 +15,14 @@
end
it "calls the sync service and redirects back with a notice" do
- get admin_user_perform_sync_path(user_id: user.id)
+ get perform_sync_admin_user_path(user)
expect(response).to redirect_to(admin_users_path(user_id: user.id))
expect(flash[:notice]).to eq("Sync complete")
end
end
- describe "GET #perform_reset_tests" do
+ describe "GET #perform_reset" do
before do
allow(User).to receive(:find_by_email).and_return(admin_user)
allow(Support::UserUtilities).to receive(:reset_tests).and_return([])
@@ -30,7 +30,7 @@
context "when reset tests result is empty" do
it "calls the reset service and redirects back with a notice" do
- get admin_user_perform_reset_path(user_id: user.id)
+ get perform_reset_admin_user_path(user)
expect(response).to redirect_to(admin_users_path(user.id))
expect(flash[:notice]).to eq("Nothing to do!")
@@ -46,7 +46,7 @@
end
it "calls the reset service and redirects to edit the last support audit" do
- get admin_user_perform_reset_path(user_id: user.id)
+ get perform_reset_admin_user_path(user)
expect(response).to redirect_to(edit_admin_support_audit_path(id: support_audit.id))
end
@@ -55,7 +55,7 @@
context "GET #generate_assessment_attempt" do
before do
- get admin_user_generate_assessment_attempt_path(user_id: user.id)
+ get generate_assessment_attempt_admin_user_path(user)
end
it "should render correct template" do
@@ -66,7 +66,7 @@
context "POST #process_assessment_attempt" do
context "with passing score" do
before do
- post admin_user_process_assessment_attempt_path(user_id: user.id), params: {
+ post process_assessment_attempt_admin_user_path(user), params: {
assessment_id: assessment.id,
score: 85
}
@@ -84,7 +84,7 @@
context "with failing score" do
before do
- post admin_user_process_assessment_attempt_path(user_id: user.id), params: {
+ post process_assessment_attempt_admin_user_path(user), params: {
assessment_id: assessment.id,
score: 25
}
From cc99694635cab1015db92f69395ea7ea99013a3f Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Wed, 17 Jun 2026 14:48:26 +0100
Subject: [PATCH 27/30] Update omniauth-rails_csrf_protection to 8.1 compatible
version
ENG-1743
---
Gemfile | 2 +-
Gemfile.lock | 18 ++++++++++--------
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/Gemfile b/Gemfile
index 2a42ca1728..7a4dca9be6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,7 +34,7 @@ gem "oauth2"
gem "omniauth"
gem "omniauth-oauth2"
gem "omniauth-auth0"
-gem "omniauth-rails_csrf_protection"
+gem "omniauth-rails_csrf_protection", "~> 2.0"
gem "pagy"
gem "pg"
gem "pg_search"
diff --git a/Gemfile.lock b/Gemfile.lock
index 2d72b68af8..1676405ab3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -171,7 +171,7 @@ GEM
combine_pdf (1.0.23)
matrix
ruby-rc4 (>= 0.1.5)
- concurrent-ruby (1.3.6)
+ concurrent-ruby (1.3.7)
connection_pool (3.0.2)
crack (0.4.5)
rexml
@@ -304,13 +304,14 @@ GEM
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
hashdiff (1.0.1)
- hashie (5.0.0)
+ hashie (5.1.0)
+ logger
htmlentities (4.3.4)
http-accept (1.7.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
humanize (2.5.1)
- i18n (1.14.8)
+ i18n (1.15.0)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
interception (0.5)
@@ -322,7 +323,7 @@ GEM
reline (>= 0.4.2)
jaro_winkler (1.5.6)
jmespath (1.6.2)
- json (2.19.8)
+ json (2.19.9)
jwt (2.7.1)
kaminari (1.2.2)
activesupport (>= 4.1.0)
@@ -407,8 +408,9 @@ GEM
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (~> 1.1)
- omniauth (2.1.2)
+ omniauth (2.1.4)
hashie (>= 3.4.6)
+ logger
rack (>= 2.2.3)
rack-protection
omniauth-auth0 (3.1.1)
@@ -417,7 +419,7 @@ GEM
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
omniauth (~> 2.0)
- omniauth-rails_csrf_protection (1.0.1)
+ omniauth-rails_csrf_protection (2.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
ostruct (0.6.3)
@@ -463,7 +465,7 @@ GEM
rack (>= 2.0.0)
rack-mini-profiler (3.1.1)
rack (>= 1.2.0)
- rack-protection (4.1.1)
+ rack-protection (4.2.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
@@ -809,7 +811,7 @@ DEPENDENCIES
omniauth
omniauth-auth0
omniauth-oauth2
- omniauth-rails_csrf_protection
+ omniauth-rails_csrf_protection (~> 2.0)
ostruct
pagy
pg
From d863797b5f1e0fb23f0bfcf8474dc0b158ffde38 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Wed, 17 Jun 2026 15:02:16 +0100
Subject: [PATCH 28/30] Refactor routes to address sonarcloud warnings
ENG-1743
---
config/routes.rb | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/config/routes.rb b/config/routes.rb
index a5f02a0742..2eef56ff84 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,6 @@
Rails.application.routes.draw do
Healthcheck.routes(self)
- root to: "cms#home", action: :home
+ root to: "cms#home"
resources :achievements, only: %i[create destroy update] do
collection do
@@ -34,10 +34,12 @@
resources :support_audits, only: %i[index show update edit]
resources :users, only: %i[index create show edit update] do
member do
- get :perform_sync
- get :perform_reset
- get :generate_assessment_attempt unless Rails.env.production?
- post :process_assessment_attempt unless Rails.env.production?
+ get "perform_sync", to: "users#perform_sync"
+ get "perform_reset", to: "users#perform_reset"
+ unless Rails.env.production?
+ get "generate_assessment_attempt", to: "users#generate_assessment_attempt"
+ post "process_assessment_attempt", to: "users#process_assessment_attempt"
+ end
end
end
resources :user_programme_enrolments, only: %i[index show edit update] do
From 1740155be9b2c137005f1745bab6dee156d04720 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Thu, 18 Jun 2026 10:08:51 +0100
Subject: [PATCH 29/30] Rename perform_reset action to perform_reset_tests and
update related routes and views
ENG-1743
---
app/controllers/admin/users_controller.rb | 2 +-
app/views/admin/users/_actions.html.erb | 4 ++--
config/routes.rb | 2 +-
spec/requests/admin/users_spec.rb | 6 +++---
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 2b8d4d245b..f4a0821637 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -10,7 +10,7 @@ def perform_sync
)
end
- def perform_reset
+ def perform_reset_tests
admin_user = User.find_by_email(ENV.fetch("DEFAULT_ADMIN_EMAIL"))
result = Support::UserUtilities.reset_tests(params[:id])
diff --git a/app/views/admin/users/_actions.html.erb b/app/views/admin/users/_actions.html.erb
index 1738b3ddfb..efe5ae4171 100644
--- a/app/views/admin/users/_actions.html.erb
+++ b/app/views/admin/users/_actions.html.erb
@@ -9,9 +9,9 @@
<%= link_to(
t(".reset.label"),
- perform_reset_admin_user_path(resource),
+ perform_reset_tests_admin_user_path(resource),
class: "button"
- ) if accessible_action?(resource, :perform_reset) %>
+ ) if accessible_action?(resource, :perform_reset_tests) %>
<%= link_to(
'Generate Assessment Attempt',
diff --git a/config/routes.rb b/config/routes.rb
index 2eef56ff84..6d56cfe3bb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -35,7 +35,7 @@
resources :users, only: %i[index create show edit update] do
member do
get "perform_sync", to: "users#perform_sync"
- get "perform_reset", to: "users#perform_reset"
+ get "perform_reset_tests", to: "users#perform_reset_tests"
unless Rails.env.production?
get "generate_assessment_attempt", to: "users#generate_assessment_attempt"
post "process_assessment_attempt", to: "users#process_assessment_attempt"
diff --git a/spec/requests/admin/users_spec.rb b/spec/requests/admin/users_spec.rb
index efa186a82e..723d1d1592 100644
--- a/spec/requests/admin/users_spec.rb
+++ b/spec/requests/admin/users_spec.rb
@@ -22,7 +22,7 @@
end
end
- describe "GET #perform_reset" do
+ describe "GET #perform_reset_tests" do
before do
allow(User).to receive(:find_by_email).and_return(admin_user)
allow(Support::UserUtilities).to receive(:reset_tests).and_return([])
@@ -30,7 +30,7 @@
context "when reset tests result is empty" do
it "calls the reset service and redirects back with a notice" do
- get perform_reset_admin_user_path(user)
+ get perform_reset_tests_admin_user_path(user)
expect(response).to redirect_to(admin_users_path(user.id))
expect(flash[:notice]).to eq("Nothing to do!")
@@ -46,7 +46,7 @@
end
it "calls the reset service and redirects to edit the last support audit" do
- get perform_reset_admin_user_path(user)
+ get perform_reset_tests_admin_user_path(user)
expect(response).to redirect_to(edit_admin_support_audit_path(id: support_audit.id))
end
From 89a9cfb3a50d421a03a95595fe152baf7bf43be7 Mon Sep 17 00:00:00 2001
From: Adam Wheatley
Date: Fri, 19 Jun 2026 11:01:55 +0100
Subject: [PATCH 30/30] Pin connection_pool gem version to ~> 2.4
Sidekiq 7.3.9 calls ConnectionPool::TimedStack#pop with a positional argument. connection_pool 3.0 changed this to keyword-only, breaking the Sidekiq scheduler thread on startup
ENG-1743
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Gemfile b/Gemfile
index 7a4dca9be6..8bc7b3e695 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,7 +13,7 @@ gem "aws-sdk-s3", require: false
gem "bootsnap", ">= 1.5.1", require: false
gem "cloudflare-rails"
gem "combine_pdf", ">= 1.0.18"
-gem "connection_pool"
+gem "connection_pool", "~> 2.4"
gem "dalli"
gem "enumerize"
gem "faraday"
diff --git a/Gemfile.lock b/Gemfile.lock
index 1676405ab3..12c893ad0d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -172,7 +172,7 @@ GEM
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.3.7)
- connection_pool (3.0.2)
+ connection_pool (2.5.5)
crack (0.4.5)
rexml
crass (1.0.6)
@@ -780,7 +780,7 @@ DEPENDENCIES
climate_control
cloudflare-rails
combine_pdf (>= 1.0.18)
- connection_pool
+ connection_pool (~> 2.4)
dalli
debase
dotenv-rails