fetcher = new BrentPriceFetcher( new EiaBrentPriceSource($apiLogger), new FredBrentPriceSource($apiLogger), ); }); it('fetches and stores brent prices from EIA', function (): void { Http::fake([ '*eia.gov/*' => Http::response([ 'response' => [ 'data' => [ ['period' => '2026-04-02', 'value' => '73.80'], ['period' => '2026-04-01', 'value' => '75.10'], ], ], ]), ]); $this->fetcher->fetchFromEia(); expect(BrentPrice::count())->toBe(2) ->and(BrentPrice::find('2026-04-02')->price_usd)->toBe('73.80'); }); it('throws when EIA returns a 500', function (): void { Http::fake(['*eia.gov/*' => Http::response([], 500)]); $this->fetcher->fetchFromEia(); })->throws(BrentPriceFetchException::class); it('throws when EIA returns empty data', function (): void { Http::fake(['*eia.gov/*' => Http::response(['response' => ['data' => []]])]); $this->fetcher->fetchFromEia(); })->throws(BrentPriceFetchException::class); it('filters out EIA missing value markers', function (): void { Http::fake([ '*eia.gov/*' => Http::response([ 'response' => [ 'data' => [ ['period' => '2026-04-01', 'value' => '75.10'], ['period' => '2026-04-02', 'value' => '.'], ['period' => '2026-04-03', 'value' => '74.20'], ], ], ]), ]); $this->fetcher->fetchFromEia(); expect(BrentPrice::count())->toBe(2) ->and(BrentPrice::find('2026-04-02'))->toBeNull(); }); it('fetches and stores brent prices from FRED', function (): void { Http::fake([ '*/fred/series/observations*' => Http::response([ 'observations' => [ ['date' => '2026-04-01', 'value' => '75.10'], ['date' => '2026-04-02', 'value' => '73.80'], ], ]), ]); $this->fetcher->fetchFromFred(); expect(BrentPrice::count())->toBe(2); }); it('throws when FRED fails', function (): void { Http::fake(['*/fred/series/observations*' => Http::response([], 500)]); $this->fetcher->fetchFromFred(); })->throws(BrentPriceFetchException::class); it('filters out FRED missing value markers', function (): void { Http::fake([ '*/fred/series/observations*' => Http::response([ 'observations' => [ ['date' => '2026-04-01', 'value' => '75.10'], ['date' => '2026-04-02', 'value' => '.'], ], ]), ]); $this->fetcher->fetchFromFred(); expect(BrentPrice::count())->toBe(1); }); it('upserts existing rows on refetch', function (): void { Http::fake([ '*eia.gov/*' => Http::sequence() ->push(['response' => ['data' => [['period' => '2026-04-01', 'value' => '74.00']]]]) ->push(['response' => ['data' => [['period' => '2026-04-01', 'value' => '75.50']]]]), ]); $this->fetcher->fetchFromEia(); $this->fetcher->fetchFromEia(); expect(BrentPrice::count())->toBe(1) ->and(BrentPrice::find('2026-04-01')->price_usd)->toBe('75.50'); });