CluPilotCloud/tests/Feature
nexxo bd779f00e5
tests / pest (push) Successful in 7m55s Details
tests / assets (push) Successful in 30s Details
tests / release (push) Successful in 6s Details
Show times on the operator's clock, keep storing them in UTC
The console announced an update for "spätestens um 15:21" while the clock on
the wall said 17:21. Not a formatting slip in one string: fourteen views and
four components printed database values straight out, so every absolute time in
the product was two hours out all summer and one hour out all winter.

Two of those views looked as though they had handled it. They called

    ->timezone(config('app.timezone'))

which reads like "convert to local time" and, app.timezone being the STORAGE
zone that must stay UTC, converts to UTC — a no-op wearing the costume of a
fix, which is worse than no call at all because it stops the next person
looking. That exact string is now banned by test.

Worse than the labels were two FORMS. Maintenance windows and plan versions
filled their datetime-local fields from UTC and parsed what came back as UTC. A
datetime-local field carries no offset — it is the digits a person reads off
their own clock — so an operator typing 21:00 scheduled a window for 23:00
their time, and nothing anywhere said so. App\Support\LocalTime now holds
toField() and fromField() side by side, because getting one end right is not
half a fix, it is a fresh bug.

The mechanism is one Carbon macro, ->local(). It copies before converting:
Illuminate\Support\Carbon is mutable, so without that, rendering a timestamp
would rewrite the model attribute as a side effect and anything comparing or
saving it afterwards would be an hour or two out. Both Carbon classes get the
same body — Carbon keeps macros in one global table, so the second registration
replaces the first for every Carbon class, and two different bodies meant the
immutable version, safe for itself, silently became the mutable one's
implementation too. My own test caught that.

The existing tests had not caught any of this because they built their expected
values with the same wrong call the views used — a test that recomputes the
implementation asserts nothing. They now assert the wall clock, and the queued
update additionally asserts that the UTC time is NOT shown.

Recorded as R19 in CLAUDE.md and enforced by tests/Feature/DisplayTimezoneTest:
no Blade or Livewire component may format an absolute time without ->local(),
the UTC no-op is banned, storage stays UTC, both sides of the DST boundary are
checked, ->local() must not mutate, and a form field must round-trip to the
same instant.

APP_DISPLAY_TIMEZONE, default Europe/Vienna. 615 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:32:21 +02:00
..
Admin Show times on the operator's clock, keep storing them in UTC 2026-07-27 17:32:21 +02:00
Auth Land a sign-in where it was performed, not always in the customer portal 2026-07-27 06:15:16 +02:00
Billing fix(billing): hold only what cannot be matched, and let migrations roll back 2026-07-26 13:57:11 +02:00
Provisioning feat(billing): Stripe owns the billing cycle, we own capability 2026-07-26 13:36:28 +02:00
BillingTest.php feat(portal): billing page — current plan, upgrades, extra storage, add-ons 2026-07-25 13:36:52 +02:00
CartTest.php fix(billing): normalise both sides of the VAT comparison; cast the timestamp 2026-07-26 09:36:07 +02:00
ComponentTest.php feat(portal): Fortify auth + Login/2FA/Dashboard + component kit 2026-07-25 01:20:25 +02:00
CustomerTwoFactorTest.php Give customers two-factor, and stop every button on the settings page reacting at once 2026-07-27 08:48:34 +02:00
DashboardTest.php One design system for every surface 2026-07-27 16:03:47 +02:00
DisplayTimezoneTest.php Show times on the operator's clock, keep storing them in UTC 2026-07-27 17:32:21 +02:00
DowngradeTest.php Measure availability, and let a customer move down again 2026-07-27 16:41:15 +02:00
ErrorPagesTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
ExampleTest.php chore: bootstrap CluPilot control-plane (Laravel 13, Docker stack) 2026-07-25 00:21:30 +02:00
IconLayoutTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
ImpersonationTest.php fix(admin): harden impersonation + datacenter code per Codex review 2026-07-25 13:50:16 +02:00
InstanceMetricsTest.php Measure what the template draws 2026-07-27 16:29:28 +02:00
LandingPriceSheetTest.php Report the estate from the database, and print the traffic that is sold 2026-07-27 04:48:51 +02:00
MonitoringStatusSyncTest.php Move the console off /admin, give the status page its own address, and measure monitoring 2026-07-27 06:05:40 +02:00
OperatorInPortalTest.php fix(portal): no silent no-ops without a customer; maintenance visible per instance; maintenance form redesign 2026-07-25 19:10:44 +02:00
PortalTabsTest.php feat(billing): the plan catalogue becomes three tables, and config stops selling 2026-07-26 12:05:56 +02:00
PublicSiteGateTest.php fix(traffic): release the throttle at month end; close the Livewire bypass 2026-07-25 23:36:38 +02:00
ReleaseVersionTest.php feat(deploy): releases you can pin to, and a version that tells the truth 2026-07-26 15:21:38 +02:00
SeatsTest.php feat(portal): seats management — invite/role/revoke against plan limit 2026-07-25 14:40:18 +02:00
SettingsTest.php fix(portal): enforce customer lifecycle per Codex review 2026-07-25 14:45:03 +02:00
TrafficTest.php Measure availability, and let a customer move down again 2026-07-27 16:41:15 +02:00
TranslationParityTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
WelcomeTest.php Give customers two-factor, and stop every button on the settings page reacting at once 2026-07-27 08:48:34 +02:00