Files
postal/spec/lib/migration_waiter_spec.rb
T
Adam Cooke 5eeed7c023 chore(deps): upgrade to Rails 7.2.3.2, Ruby 3.4.10 and latest gems
Upgrades Rails from 7.1.6 to 7.2.3.2, Ruby from 3.4.6 to 3.4.10, and
brings the rest of the bundle up to date.

Two things were broken by Rails 7.2 and are fixed here:

* MigrationWaiter called `ActiveRecord::Base.connection.migration_context`,
  which moved to the connection pool in 7.2. This raised NoMethodError on
  boot for any deployment with `migration_waiter.enabled` set, and had no
  test covering it.
* `config.action_dispatch.show_exceptions = false` was deprecated in 7.1
  and removed in 7.2. Now `:none`.

`json` is pinned to < 3. Active Support 7.2 passes `quirks_mode:` to both
`JSON.generate` and `JSON.parse`, and json 3.0 removed that option, so an
unpinned json resolves to 3.x and makes every `to_json` raise - webhook
payloads included. Nothing else in the bundle constrains it. The pin can go
when we reach Rails 8.1, which no longer passes the option.

`dynamic_form` is dropped. It is unmaintained and from 1.3.1 requires Ruby
< 3.4, so it could never be updated again. Ten views used it, so
`error_messages_for` now lives in ErrorMessagesHelper and `f.error_messages`
in a form builder extension. The rendered markup is unchanged apart from a
heading and description the stylesheet had always hidden; the rules that
hid them are removed too.

`execjs` is unpinned. The `< 2.8` constraint dated from the Rails 6.0
upgrade and asset output is byte-identical without it.

Notable gem majors: puma 8, haml 7, secure_headers 7, sentry 7, jwt 3,
prometheus-client 5, highline 3, omniauth-rails_csrf_protection 2,
rspec-rails 8, shoulda-matchers 8, rubocop 1.91. secure_headers 7 changes
X-XSS-Protection from "1; mode=block" to "0", which is the correct modern
default and matches Rails' own.

`config.load_defaults` deliberately stays at 7.0. The pending 7.1 and 7.2
options are listed, commented out, in new_framework_defaults_7_1.rb and
_7_2.rb so they can be adopted in stages - several change the signed cookie
and cache formats and need a staged deploy. The vestigial
new_framework_defaults_7_0.rb is removed, as `load_defaults 7.0` already
applies all of it.

Adds tests for the areas this touched, which were mostly uncovered:
MigrationWaiter, the serialized columns that gate a defaults bump, JSON
encoding, HAML template compilation for all 103 templates, page rendering,
form error rendering and security headers. The assets Gemfile group now
includes :test so the view layer can be exercised at all.
2026-09-15 10:27:16 +01:00

90 lines
3.3 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
require "migration_waiter"
RSpec.describe MigrationWaiter do
describe ".pending_migration_count" do
# This talks to Active Record's migration APIs directly and is the part of
# this class most likely to break on a Rails upgrade (the call moved from
# the connection to the connection pool in Rails 7.2). The rest of this
# class is stubbed, so this example is what actually exercises it.
it "returns the number of migrations which have not been applied" do
expect(described_class.pending_migration_count).to be_a Integer
end
it "returns zero when the test database is up to date" do
expect(described_class.pending_migration_count).to eq 0
end
end
describe ".wait" do
before do
allow(Postal.logger).to receive(:info)
allow(described_class).to receive(:sleep)
end
it "returns immediately when there are no pending migrations" do
allow(described_class).to receive(:pending_migration_count).and_return(0)
described_class.wait
expect(described_class).to have_received(:pending_migration_count).once
expect(described_class).to_not have_received(:sleep)
end
it "waits until there are no pending migrations remaining" do
allow(described_class).to receive(:pending_migration_count).and_return(2, 1, 0)
described_class.wait
expect(described_class).to have_received(:sleep).with(described_class::SLEEP_TIME).twice
end
it "exits when migrations are still pending after the maximum number of attempts" do
stub_const("#{described_class}::ATTEMPTS", 3)
allow(described_class).to receive(:pending_migration_count).and_return(1)
# Process.exit raises SystemExit for real. It has to do so here too,
# otherwise the wait loop never terminates.
allow(Process).to receive(:exit).and_raise(SystemExit)
expect { described_class.wait }.to raise_error(SystemExit)
expect(Process).to have_received(:exit).with(1)
expect(described_class).to have_received(:sleep).twice
end
end
describe ".wait_if_appropriate" do
before do
allow(described_class).to receive(:wait)
allow(described_class).to receive(:console?).and_return(false)
allow(described_class).to receive(:rake_task?).and_return(false)
allow(described_class).to receive(:rails_command?).and_return(false)
end
context "when the migration waiter is not enabled" do
it "does not wait" do
allow(Postal::Config.migration_waiter).to receive(:enabled?).and_return(false)
described_class.wait_if_appropriate
expect(described_class).to_not have_received(:wait)
end
end
context "when the migration waiter is enabled" do
before do
allow(Postal::Config.migration_waiter).to receive(:enabled?).and_return(true)
end
it "waits" do
described_class.wait_if_appropriate
expect(described_class).to have_received(:wait)
end
%w[console? rake_task? rails_command?].each do |predicate|
it "does not wait when #{predicate.delete_suffix('?')} is true" do
allow(described_class).to receive(predicate).and_return(true)
described_class.wait_if_appropriate
expect(described_class).to_not have_received(:wait)
end
end
end
end
end