From 21367a7e3e5c458b26abe750dc50ff042ab749e0 Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:00:45 +0000 Subject: [PATCH] CLDC-4235, CLDC-4280: staging go-live testing prep (#3262) * CLDC-4235: set allow_future_form_use false in staging * CLDC-4280: add time travel behaviour for staging (and check on test first) * CLDC-4280: actually check on review * CLDC-4280: time travel on staging now review works * CLDC-4280: temp add review to diff again * CLDC-4280: send notify emails in real time in all environments to avoid auth/time skew errors * CLDC-4280: also apply with_timecop to s3 * CLDC-4280: undo changes to review --- Gemfile | 5 +- app/helpers/timecop_helper.rb | 9 ++++ app/mailers/devise_notify_mailer.rb | 13 +++-- app/mailers/notify_mailer.rb | 13 +++-- app/services/feature_toggle.rb | 2 +- app/services/storage/s3_service.rb | 76 ++++++++++++++++++----------- config/initializers/timecop.rb | 4 ++ 7 files changed, 82 insertions(+), 40 deletions(-) create mode 100644 app/helpers/timecop_helper.rb create mode 100644 config/initializers/timecop.rb diff --git a/Gemfile b/Gemfile index 91ed6c8a8..d7f1db035 100644 --- a/Gemfile +++ b/Gemfile @@ -103,6 +103,10 @@ group :development do gem "rubocop-rails", require: false end +group :test, :staging do + gem "timecop", "~> 0.9.4" +end + group :test do gem "axe-core-rspec" gem "capybara", require: false @@ -111,7 +115,6 @@ group :test do gem "rspec-rails", require: false gem "selenium-webdriver", require: false gem "simplecov", require: false - gem "timecop", "~> 0.9.4" gem "webmock", require: false end diff --git a/app/helpers/timecop_helper.rb b/app/helpers/timecop_helper.rb new file mode 100644 index 000000000..2780cc918 --- /dev/null +++ b/app/helpers/timecop_helper.rb @@ -0,0 +1,9 @@ +module TimecopHelper + def without_timecop(&block) + if defined?(Timecop) + Timecop.return(&block) + else + yield + end + end +end diff --git a/app/mailers/devise_notify_mailer.rb b/app/mailers/devise_notify_mailer.rb index 4065e3aa9..d023ca843 100644 --- a/app/mailers/devise_notify_mailer.rb +++ b/app/mailers/devise_notify_mailer.rb @@ -1,4 +1,5 @@ class DeviseNotifyMailer < Devise::Mailer + include TimecopHelper require "notifications/client" def notify_client @@ -8,11 +9,13 @@ class DeviseNotifyMailer < Devise::Mailer def send_email(email_address, template_id, personalisation) return true if intercept_send?(email_address) - notify_client.send_email( - email_address:, - template_id:, - personalisation:, - ) + without_timecop do + notify_client.send_email( + email_address:, + template_id:, + personalisation:, + ) + end rescue Notifications::Client::BadRequestError => e Sentry.capture_exception(e) diff --git a/app/mailers/notify_mailer.rb b/app/mailers/notify_mailer.rb index 21a6e0270..93187a749 100644 --- a/app/mailers/notify_mailer.rb +++ b/app/mailers/notify_mailer.rb @@ -1,4 +1,5 @@ class NotifyMailer < ApplicationMailer + include TimecopHelper require "notifications/client" def notify_client @@ -8,11 +9,13 @@ class NotifyMailer < ApplicationMailer def send_email(email, template_id, personalisation) return true if intercept_send?(email) - notify_client.send_email( - email_address: email, - template_id:, - personalisation:, - ) + without_timecop do + notify_client.send_email( + email_address: email, + template_id:, + personalisation:, + ) + end end def personalisation(record, token, url, username: false) diff --git a/app/services/feature_toggle.rb b/app/services/feature_toggle.rb index 6f557722c..fd9c2cf1c 100644 --- a/app/services/feature_toggle.rb +++ b/app/services/feature_toggle.rb @@ -1,6 +1,6 @@ class FeatureToggle def self.allow_future_form_use? - Rails.env.development? || Rails.env.review? || Rails.env.staging? + Rails.env.development? || Rails.env.review? end def self.bulk_upload_duplicate_log_check_enabled? diff --git a/app/services/storage/s3_service.rb b/app/services/storage/s3_service.rb index a6eef7d49..c94a418b7 100644 --- a/app/services/storage/s3_service.rb +++ b/app/services/storage/s3_service.rb @@ -1,5 +1,7 @@ module Storage class S3Service < StorageService + include TimecopHelper + attr_reader :configuration def initialize(config_service, instance_name) @@ -11,61 +13,79 @@ module Storage end def list_files(folder) - @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder) - .flat_map { |response| response.contents.map(&:key) } + without_timecop do + @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder) + .flat_map { |response| response.contents.map(&:key) } + end end def folder_present?(folder) - response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1) - response.key_count == 1 + without_timecop do + response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1) + response.key_count == 1 + end end def get_presigned_url(file_name, duration, response_content_disposition: nil) - Aws::S3::Presigner - .new({ client: @client }) - .presigned_url(:get_object, bucket: @configuration.bucket_name, key: file_name, expires_in: duration, response_content_disposition:) + without_timecop do + Aws::S3::Presigner + .new({ client: @client }) + .presigned_url(:get_object, bucket: @configuration.bucket_name, key: file_name, expires_in: duration, response_content_disposition:) + end end def get_file_io(file_name) - @client.get_object(bucket: @configuration.bucket_name, key: file_name) - .body + without_timecop do + @client.get_object(bucket: @configuration.bucket_name, key: file_name) + .body + end end def get_file(file_name) - @client.get_object(bucket: @configuration.bucket_name, key: file_name) - .body.read + without_timecop do + @client.get_object(bucket: @configuration.bucket_name, key: file_name) + .body.read + end end def write_file(file_name, data, content_type: nil) - if content_type.nil? - @client.put_object( - body: data, - bucket: @configuration.bucket_name, - key: file_name, - ) - else - @client.put_object( - body: data, - bucket: @configuration.bucket_name, - key: file_name, - content_type:, - ) + without_timecop do + if content_type.nil? + @client.put_object( + body: data, + bucket: @configuration.bucket_name, + key: file_name, + ) + else + @client.put_object( + body: data, + bucket: @configuration.bucket_name, + key: file_name, + content_type:, + ) + end end end def get_file_metadata(file_name) - @client.head_object(bucket: @configuration.bucket_name, key: file_name) + without_timecop do + @client.head_object(bucket: @configuration.bucket_name, key: file_name) + end end def file_exists?(file_name) - @client.head_object(bucket: @configuration.bucket_name, key: file_name) - true + without_timecop do + @client.head_object(bucket: @configuration.bucket_name, key: file_name) + true + end rescue Aws::S3::Errors::NotFound false end def delete_file(file_name) - @client.delete_object(bucket: @configuration.bucket_name, key: file_name) + without_timecop do + @client.delete_object(bucket: @configuration.bucket_name, key: file_name) + end end private diff --git a/config/initializers/timecop.rb b/config/initializers/timecop.rb new file mode 100644 index 000000000..6e1f6fdf7 --- /dev/null +++ b/config/initializers/timecop.rb @@ -0,0 +1,4 @@ +if Rails.env.staging? + require "timecop" + Timecop.travel(Time.zone.local(2026, 4, 1)) +end