queue(200, json_decode((string) file_get_contents(__DIR__ . '/fixtures/forecast.json'), true)); $result = (new OpenMeteoClient($http))->forecast(51.82, 6.55, 'Europe/Berlin'); self::assertCount(6, $result['daily']['time']); self::assertCount(144, $result['hourly']['time']); self::assertSame('https://api.open-meteo.com/v1/forecast', $http->requests[0]['url']); self::assertSame([ 'latitude' => 51.82, 'longitude' => 6.55, 'daily' => 'weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max', 'hourly' => 'temperature_2m,precipitation,wind_speed_10m,wind_direction_10m', 'timezone' => 'Europe/Berlin', 'forecast_days' => 6, ], $http->requests[0]['params']); } public function testErrorStatusIsUpstreamException(): void { $http = (new FakeHttp())->queue(429, ['reason' => 'rate limit']); $this->expectException(UpstreamException::class); (new OpenMeteoClient($http))->forecast(1, 2, 'UTC'); } public function testMissingDailyIsUpstreamException(): void { $http = (new FakeHttp())->queue(200, ['hourly' => []]); $this->expectException(UpstreamException::class); (new OpenMeteoClient($http))->forecast(1, 2, 'UTC'); } public function testMissingHourlyGivesEmptyHourlyBlockInsteadOfError(): void { $http = (new FakeHttp())->queue(200, ['daily' => ['time' => ['2026-09-11']]]); $result = (new OpenMeteoClient($http))->forecast(1, 2, 'UTC'); self::assertSame([], $result['hourly']); } }