From 48d68b86fa97876ad6abcad666d6f8fd8e8ca4c3 Mon Sep 17 00:00:00 2001 From: Theo Weijmer Date: Sun, 13 Sep 2026 10:20:00 +0200 Subject: [PATCH] fix: treat a stale/legacy-shaped forecast cache as unavailable, not silently empty Na de deploy van het uuroverzicht (commit 8e90eb1) bleef de nog geldige cache op de server een tijdlang in het oude platte 'daily'-blok staan i.p.v. het nieuwe {daily, hourly}. DashboardService::forecast() las daaruit stil een lege verwachting zonder errors-melding. Nu wordt een cache- of bronresultaat zonder geldig 'daily'-blok behandeld als een storing (forecast_unavailable), zichtbaar in plaats van stil. Handmatig verholpen op productie door de verouderde cachefile te wissen; deze fix voorkomt herhaling bij een volgende formaatwijziging. Co-Authored-By: Claude Sonnet 5 --- src/DashboardService.php | 13 ++++++++++--- tests/DashboardServiceTest.php | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/DashboardService.php b/src/DashboardService.php index 0032b69..6df5f62 100644 --- a/src/DashboardService.php +++ b/src/DashboardService.php @@ -123,11 +123,18 @@ final class DashboardService return ['days' => [], 'stale' => true]; } $result = $this->fetch('forecast', fn() => $this->source->forecast($lat, $lon, $tz->getName()), self::ERR_FORECAST); - if ($result['data'] === null) { - return ['days' => [], 'stale' => $result['stale']]; + $daily = is_array($result['data']['daily'] ?? null) ? $result['data']['daily'] : null; + if ($daily === null) { + // Data ontbreekt (echte storing, al gemeld door fetch()), of de cache staat nog in een ouder + // formaat na een deploy dat de opgeslagen vorm wijzigde. In beide gevallen liever zichtbaar + // niets tonen dan stil een lege verwachting zonder foutmelding. + if ($result['data'] !== null) { + $this->addError(self::ERR_FORECAST); + } + return ['days' => [], 'stale' => true]; } - $days = ForecastMapper::map($result['data']['daily'] ?? []); + $days = ForecastMapper::map($daily); $hourlyByDate = HourlyForecastMapper::mapByDate($result['data']['hourly'] ?? [], $tz); foreach ($days as &$day) { $day['hourly'] = $hourlyByDate[$day['date']] ?? []; diff --git a/tests/DashboardServiceTest.php b/tests/DashboardServiceTest.php index 0b05402..a562ab3 100644 --- a/tests/DashboardServiceTest.php +++ b/tests/DashboardServiceTest.php @@ -182,6 +182,22 @@ final class DashboardServiceTest extends TestCase self::assertSame(16.4, $p['outdoor']['temp']); } + public function testLegacyCachedForecastShapeIsTreatedAsUnavailableNotSilentlyEmpty(): void + { + // Vóór 2026-09-13 cachete DashboardService het platte "daily"-blok direct onder de sleutel + // "forecast". Na de uitbreiding met uurdata verwacht de code {daily, hourly}. Een nog niet + // verlopen cache in het oude formaat mag niet stil een lege verwachting zonder foutmelding geven. + $legacyDailyOnly = json_decode((string) file_get_contents(__DIR__ . '/fixtures/forecast.json'), true)['daily']; + (new Cache($this->dir))->write('forecast', $legacyDailyOnly, (new \DateTimeImmutable('2026-09-11 12:30:00 UTC'))->getTimestamp()); + + $p = $this->service()->build(); + + self::assertSame([], $p['forecast']); + self::assertTrue($p['forecast_stale']); + self::assertSame(['forecast_unavailable'], $p['errors']); + self::assertSame(0, $this->source->calls['forecast'], 'de cache was nog vers, dus geen live call verwacht'); + } + public function testSecondBuildWithinTtlUsesCache(): void { $this->service()->build();