whisper-money/tests/Feature/CurrencyConversionServiceTe...

193 lines
5.6 KiB
PHP

<?php
use App\Services\CurrencyConversionService;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
beforeEach(function () {
Cache::flush();
});
test('converts crypto to fiat using CDN rates', function () {
Http::fake([
'cdn.jsdelivr.net/*currencies/eur*' => 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('degrades to zero 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;
$result = $service->convert('BTC', 'EUR', 1.0, '2026-01-15');
expect($result)->toBe(0.0);
});
test('degrades to zero and stops walking dates when the source times out', function () {
$attempts = 0;
Http::fake(function () use (&$attempts) {
$attempts++;
throw new ConnectionException('cURL error 28: Operation timed out');
});
$service = new CurrencyConversionService;
$result = $service->convert('BTC', 'EUR', 1.0, '2026-01-15');
expect($result)->toBe(0.0);
// Only the first candidate date is attempted (primary + fallback); a timing
// out source must not be retried across every historical lookback date.
expect($attempts)->toBe(2);
});
test('caches rates across service instances so repeat lookups skip HTTP', function () {
Http::fake([
'cdn.jsdelivr.net/*currencies/eur*' => Http::response([
'eur' => ['btc' => 0.000015],
]),
]);
(new CurrencyConversionService)->convert('BTC', 'EUR', 1.0, '2026-01-15');
(new CurrencyConversionService)->convert('BTC', 'EUR', 1.0, '2026-01-15');
Http::assertSentCount(1);
});
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);
});