From 269b90ccb53caa0d38220a19cbb4d580ca40e8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Sat, 4 Jul 2026 20:52:15 +0200 Subject: [PATCH] Fake the external currency-rate provider in balance-evolution tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stray-request guard added earlier surfaced five non-hermetic tests that reached the real currency-rate CDN (cdn.jsdelivr.net / currency-api.pages.dev) without an Http::fake(): four balance-evolution cases in AccountControllerTest and one loan projection case in LoanTest. They passed on main only because CI has network access and the request actually went out; under the guard the unfaked request now throws, returning 500. Adds a fakeCurrencyApi() helper in tests/Pest.php that stubs the provider's /currencies/{code}.min.json endpoint with a deterministic 1:1 rate in the exact shape the services parse ($response->json($currency)), and calls it from both files' beforeEach. The stub returns null for non-matching URLs, so it never shadows another test's own fakes nor the stray-request guard. Note: a single global default fake in Pest.php was rejected on purpose — Laravel resolves stubs first-match-wins in registration order, so a beforeEach-level fake would shadow the per-test currency fakes in CurrencyConversionServiceTest and ExchangeRateServiceTest and break their assertions. --- tests/Feature/AccountControllerTest.php | 5 ++++ tests/Feature/LoanTest.php | 5 ++++ tests/Pest.php | 35 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/tests/Feature/AccountControllerTest.php b/tests/Feature/AccountControllerTest.php index 3220e2d1..2076e130 100644 --- a/tests/Feature/AccountControllerTest.php +++ b/tests/Feature/AccountControllerTest.php @@ -14,6 +14,11 @@ use App\Models\User; beforeEach(function () { config(['landing.hide_auth_buttons' => false]); + // The balance-evolution endpoint converts account currency via the external + // currency-rate provider; fake it so these tests stay hermetic under the + // stray-request guard instead of hitting the CDN. + fakeCurrencyApi(); + $this->user = User::factory()->onboarded()->create(); $this->actingAs($this->user); }); diff --git a/tests/Feature/LoanTest.php b/tests/Feature/LoanTest.php index 6b140f8f..9f2f1521 100644 --- a/tests/Feature/LoanTest.php +++ b/tests/Feature/LoanTest.php @@ -15,6 +15,11 @@ use function Pest\Laravel\assertDatabaseHas; use function Pest\Laravel\assertDatabaseMissing; beforeEach(function () { + // The balance-evolution endpoint converts account currency via the external + // currency-rate provider; fake it so these tests stay hermetic under the + // stray-request guard instead of hitting the CDN. + fakeCurrencyApi(); + $this->user = User::factory()->onboarded()->create(); $this->bank = Bank::factory()->create(); $this->service = app(LoanAmortizationService::class); diff --git a/tests/Pest.php b/tests/Pest.php index 69540061..ac647803 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -13,6 +13,7 @@ use App\Services\Banking\BalanceSyncService; use App\Services\Banking\Sync\BankingConnectionSyncerFactory; use App\Services\Banking\TransactionSyncService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\Client\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; use Stripe\Collection as StripeCollection; @@ -303,3 +304,37 @@ function runSync( $job->handle(app(BankingConnectionSyncerFactory::class)); } + +/** + * Fake the external currency-rate provider (the jsdelivr CDN and its pages.dev + * fallback) that CurrencyConversionService and ExchangeRateService fetch from, + * returning a deterministic 1:1 rate for every currency. Tests that trigger a + * currency conversion (e.g. the balance-evolution endpoint) can call this in + * their setup to stay hermetic under the stray-request guard. + * + * The stub only matches the provider's `/currencies/{code}.min.json` path and + * returns null otherwise, so it never shadows another test's own Http::fake() + * nor the stray-request guard for unrelated hosts. + */ +function fakeCurrencyApi(): void +{ + Http::fake(function (Request $request) { + if (! preg_match('#/currencies/([a-z0-9]+)\.min\.json#i', $request->url(), $matches)) { + return null; + } + + $currency = strtolower($matches[1]); + + return Http::response([ + 'date' => '2024-01-01', + $currency => [ + 'usd' => 1.0, + 'eur' => 1.0, + 'gbp' => 1.0, + 'jpy' => 1.0, + 'btc' => 1.0, + 'eth' => 1.0, + ], + ]); + }); +}