Http::response([ 'eur' => [ 'btc' => 0.000015, 'eth' => 0.0004, ], ]), ]); $service = new CurrencyConversionService; // 1 EUR = 0.000015 BTC → 1 BTC = 1/0.000015 = 66666.67 EUR // 2 BTC = 133333.33 EUR $result = $service->convert('BTC', 'EUR', 2.0, '2026-01-15'); expect($result)->toBe(2.0 / 0.000015); }); test('returns quantity when source equals target', function () { Http::fake(); $service = new CurrencyConversionService; $result = $service->convert('EUR', 'EUR', 150.0, '2026-01-15'); expect($result)->toBe(150.0); Http::assertNothingSent(); }); test('returns zero when source currency not found', function () { Http::fake([ 'cdn.jsdelivr.net/*currencies/eur*' => Http::response([ 'eur' => [ 'btc' => 0.000015, ], ]), ]); $service = new CurrencyConversionService; $result = $service->convert('UNKNOWN', 'EUR', 10.0, '2026-01-15'); expect($result)->toBe(0.0); }); test('uses fallback URL when primary fails', function () { Http::fake([ 'cdn.jsdelivr.net/*' => Http::response('Server Error', 500), 'currency-api.pages.dev/*currencies/eur*' => Http::response([ 'eur' => [ 'btc' => 0.00002, ], ]), ]); $service = new CurrencyConversionService; $result = $service->convert('BTC', 'EUR', 1.0, '2026-01-15'); expect($result)->toBe(1.0 / 0.00002); }); test('throws when both primary and fallback fail', function () { Http::fake([ 'cdn.jsdelivr.net/*' => Http::response('Server Error', 500), 'currency-api.pages.dev/*' => Http::response('Server Error', 500), ]); $service = new CurrencyConversionService; $service->convert('BTC', 'EUR', 1.0, '2026-01-15'); })->throws(RuntimeException::class); test('returns zero when base currency is unknown and all sources return 404', function () { Http::fake([ 'cdn.jsdelivr.net/*' => Http::response('Not Found', 404), 'currency-api.pages.dev/*' => Http::response('Not Found', 404), ]); $service = new CurrencyConversionService; $result = $service->convert('BTC', 'XXX', 1.0, '2026-01-15'); expect($result)->toBe(0.0); }); test('falls back to previous historical date when requested release is missing', function () { Http::fake([ 'cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@2025-12-10/*' => Http::response('Not Found', 404), 'currency-api.pages.dev/v1/2025-12-10/*' => Http::response('Not Found', 404), 'cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@2025-12-09/*' => Http::response([ 'usd' => [ 'eur' => 0.9, ], ]), ]); $service = new CurrencyConversionService; $result = $service->convert('EUR', 'USD', 90.0, '2025-12-10'); expect($result)->toBe(100.0); Http::assertSentCount(3); }); test('caches rates so same currency and date makes only one HTTP request', function () { Http::fake([ 'cdn.jsdelivr.net/*currencies/eur*' => Http::response([ 'eur' => [ 'btc' => 0.000015, 'eth' => 0.0004, ], ]), ]); $service = new CurrencyConversionService; $service->convert('BTC', 'EUR', 1.0, '2026-01-15'); $service->convert('ETH', 'EUR', 5.0, '2026-01-15'); Http::assertSentCount(1); }); test('different dates make separate requests', function () { Http::fake([ 'cdn.jsdelivr.net/*currencies/eur*' => Http::response([ 'eur' => [ 'btc' => 0.000015, ], ]), ]); $service = new CurrencyConversionService; $service->convert('BTC', 'EUR', 1.0, '2026-01-15'); $service->convert('BTC', 'EUR', 1.0, '2026-01-16'); Http::assertSentCount(2); }); test('converts new latam fiat currency using CDN rates', function () { Http::fake([ 'cdn.jsdelivr.net/*currencies/usd*' => Http::response([ 'usd' => [ 'ars' => 1400.0, ], ]), ]); $service = new CurrencyConversionService; $result = $service->convert('ARS', 'USD', 2800.0, '2026-01-15'); expect($result)->toBe(2.0); });