From 8cb1dab5cedb43a4020b7df56a01a82b3e0a51a3 Mon Sep 17 00:00:00 2001 From: nagelm <10207686+nagelm@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:25:38 +1200 Subject: [PATCH 1/2] test(frontend): make date assertions machine-independent Three tests encoded the machine's timezone or locale into their expectations and fail on any box east of UTC (observed on UTC+12/13; CI's UTC and US-locale dev machines never see it): - dateTimeUtils isSame: compared 10:00Z/11:00Z on the 15th as 'same day', but isSame works on local calendar days and no UTC instant falls on the same date in every timezone (at UTC+13 those are 23:00 on the 15th and 00:00 on the 16th). Use timezone-less datetimes, which mean the same calendar day everywhere. - RecordingUtils toDateString: formatted a UTC-noon instant and expected the UTC date, but toDateString renders local time (UTC+12 renders the next day). Construct the Date with local components. - SeriesModalUtils getEpisodeAirdate: the code renders the viewer's locale via toLocaleDateString() by design, but the test hardcoded the en-US shape (it tolerated the timezone day-shift with /1[4|5]/ yet not the day/month order). Compute the expectation with the same API so the test asserts the wiring, not a specific locale. No production code changes. --- frontend/src/utils/__tests__/dateTimeUtils.test.js | 8 ++++---- .../utils/components/__tests__/SeriesModalUtils.test.js | 2 +- frontend/src/utils/forms/__tests__/RecordingUtils.test.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/utils/__tests__/dateTimeUtils.test.js b/frontend/src/utils/__tests__/dateTimeUtils.test.js index 3bc642af..0e841074 100644 --- a/frontend/src/utils/__tests__/dateTimeUtils.test.js +++ b/frontend/src/utils/__tests__/dateTimeUtils.test.js @@ -132,14 +132,14 @@ describe('dateTimeUtils', () => { describe('isSame', () => { it('should return true when dates are same day', () => { - const date1 = '2024-01-15T10:00:00Z'; - const date2 = '2024-01-15T11:00:00Z'; + const date1 = '2024-01-15 10:00:00'; + const date2 = '2024-01-15 11:00:00'; expect(dateTimeUtils.isSame(date1, date2)).toBe(true); }); it('should return false when dates are different days', () => { - const date1 = '2024-01-15T10:00:00Z'; - const date2 = '2024-01-16T10:00:00Z'; + const date1 = '2024-01-15 10:00:00'; + const date2 = '2024-01-16 10:00:00'; expect(dateTimeUtils.isSame(date1, date2)).toBe(false); }); diff --git a/frontend/src/utils/components/__tests__/SeriesModalUtils.test.js b/frontend/src/utils/components/__tests__/SeriesModalUtils.test.js index d986cfcc..5f4f4490 100644 --- a/frontend/src/utils/components/__tests__/SeriesModalUtils.test.js +++ b/frontend/src/utils/components/__tests__/SeriesModalUtils.test.js @@ -353,7 +353,7 @@ describe('SeriesModalUtils', () => { it('should format valid air date', () => { const episode = { air_date: '2024-01-15' }; const formatted = getEpisodeAirdate(episode); - expect(formatted).toMatch(/1\/1[4|5]\/2024/); + expect(formatted).toBe(new Date('2024-01-15').toLocaleDateString()); }); it('should return N/A for missing air date', () => { diff --git a/frontend/src/utils/forms/__tests__/RecordingUtils.test.js b/frontend/src/utils/forms/__tests__/RecordingUtils.test.js index 6e3ae901..db74a1f9 100644 --- a/frontend/src/utils/forms/__tests__/RecordingUtils.test.js +++ b/frontend/src/utils/forms/__tests__/RecordingUtils.test.js @@ -111,7 +111,7 @@ describe('RecordingUtils', () => { describe('toDateString', () => { it('formats a Date to YYYY-MM-DD', () => { - const d = new Date('2024-06-15T12:00:00Z'); + const d = new Date(2024, 5, 15, 12, 0, 0); const result = toDateString(d); expect(result).toBe('2024-06-15'); }); From 9da83d76facfbf96a31d4e6e5b4a799ca3a74900 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Fri, 17 Jul 2026 15:44:25 +0000 Subject: [PATCH 2/2] changelog: Update for pr 1442 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 814f54b5..101935a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **Frontend date/time unit tests no longer fail outside UTC / en-US.** Three tests encoded the machine timezone or locale into their expectations (`dateTimeUtils.isSame`, `RecordingUtils.toDateString`, `SeriesModalUtils.getEpisodeAirdate`), so the suite failed on boxes east of UTC or non-US locales. Fixtures now use local calendar datetimes and locale-computed expectations so the suite passes in every environment. — Thanks [@nagelm](https://github.com/nagelm) - **API error toasts no longer dump raw HTML error pages.** Failed responses that return Django or nginx HTML (for example 500/502/504 when the backend is down) were interpolated into the toast body as markup. Errors are now formatted for display: JSON bodies prefer `detail` / `error` / field messages, HTML and empty bodies collapse to a short status line, and long plain-text bodies are truncated. — Thanks [@nagelm](https://github.com/nagelm) - **Swagger/OpenAPI schema generation no longer fails under concurrent gevent requests.** Concurrent hits to `/api/schema/` (for example Swagger UI loading) could race on DRF's shared `AutoSchema` state and raise `AssertionError: Schema generation REQUIRES a view instance`, leaving Swagger empty. Cold builds could also hang the gevent uWSGI worker. Schema introspection now runs in a real OS thread, builds are single-flight per process, and the result is cached in Django's cache (Redis) so all workers share it. - **`/proxy/ts/change_stream` now persists `stream_id` and waits for the real switch outcome in multi-worker deployments.** When the request landed on a worker that didn't own the channel, the owning worker's `STREAM_SWITCH` pubsub handler performed the switch but wrote only `url`/`user_agent` back to the channel's Redis metadata, so `GET /proxy/ts/status` kept reporting the old `stream_id` indefinitely; the handler now persists the full switch metadata (`stream_id`, `m3u_profile`, stream name) via the same `_update_channel_metadata()` helper the direct owner path uses. `stream_name` from the initial `get_stream_info_for_switch()` lookup is now forwarded through the pubsub payload (and from `change_stream` / `next_stream` on the direct owner path) so the owner no longer re-queries the database when writing metadata. The endpoint also no longer returns an optimistic 200 on the non-owner path: the requesting worker waits (up to 15s) for the owner to confirm via the `switch_status` Redis key and answers 502 if the owner reports failure or 504 if no confirmation arrives (e.g. owner worker gone); `next_stream` gets the same treatment. Requesting a switch to the URL the channel is already playing is now treated as success (with a metadata refresh) instead of a silent no-op, and the `switch_status` key now expires (60s TTL) instead of persisting forever. (Fixes #1412)