Files
sure/test/controllers/concerns
GFRandClaude Sonnet 5 746d56c4bd fix: gracefully handle invalid family timezone instead of crashing (#2821)
* fix: gracefully handle invalid family timezone instead of crashing

Family#timezone is a free-text IANA zone name with no validation on
write. If it becomes stale (e.g. tzdata renames a zone, like the
historical Europe/Kiev -> Europe/Kyiv switch) or a migration meant to
remap legacy names never ran, Localize#switch_timezone passed the raw
string straight to Time.use_zone, which raises ArgumentError for any
unrecognized zone.

Since switch_timezone runs as an around_action on every request, this
crashed the entire app for the affected family, including the login
page.

Now validates the zone via ActiveSupport::TimeZone[] first and falls
back to the app default (logging a DebugLogEntry) instead of raising.
The log write is debounced per (family, bad value) via Rails.cache
(once per day) so an affected family doesn't write one DebugLogEntry
row per page view indefinitely.

Fixes #390

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: address review feedback on timezone fallback

- Make the invalid-timezone debounce lease atomic. Rails.cache.fetch
  is read-then-write, not atomic, so two concurrent requests could
  both observe a cache miss and both log before either write landed.
  Rails.cache.write(unless_exist: true) maps to Redis's atomic SET NX
  in production, so only one request ever wins the lease.
  (via CodeRabbit)

- Stop using "Europe/Kiev" as the invalid-timezone value in tests.
  Whether ActiveSupport::TimeZone still resolves that legacy alias
  depends on the host's installed tzdata version (tzinfo-data is
  Windows/JRuby-only per Gemfile), so the test's pass/fail behavior
  wasn't deterministic across machines/CI. Use a deliberately
  nonexistent name instead.
  (via Codex)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: validate Family#timezone on write to address root cause of #390

The previous commit made the *crash* graceful, but left the actual
defect in place: nothing stopped an unrecognized IANA zone name from
being written to Family#timezone in the first place (direct DB/API
access, an old dump predating a tzdata rename, or a future rename of
a currently-valid zone).

Add a Family-level validation using the same ActiveSupport::TimeZone[]
lookup Localize#resolved_timezone uses at request time, so "valid at
save" and "valid when rendering" can't drift apart.

Deliberately not `inclusion: { in: ActiveSupport::TimeZone.all.map(&:name) }`,
matching the neighboring locale/date_format validations: verified
empirically that the settings form submits `tz.tzinfo.identifier` (e.g.
"America/New_York"), not `tz.name` (e.g. "Eastern Time (US & Canada)"),
and those differ for all 150 zones Rails ships. An inclusion check
against `.name` would have rejected every legitimate value the form
submits.

The validation only runs when timezone is actually being changed
(if: :timezone_changed?). A family with a pre-existing bad value (the
exact #390 scenario) must still be able to save unrelated changes --
otherwise this would turn a previously-harmless bad value into a
blocker for any other settings update or background job touching that
family's record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 02:58:40 +02:00
..