fix(banking): treat Indexa Capital performance 404 as empty data (#386)
## Problem Sentry: [PHP-LARAVEL-1X](https://whisper-money.sentry.io/issues/PHP-LARAVEL-1X) — 6 events, 2 users. `IndexaCapitalClient::getPerformance()` throws `RequestException` when Indexa Capital returns 404 (HTML page) for an account without performance data — e.g. brand-new or inactive accounts. This aborts `SyncBankingConnectionJob::syncIndexaCapital` and pages us via Sentry. ## Fix Catch the 404 inside the client, log at info level, and return an empty array. The downstream `IndexaCapitalBalanceSyncService` already short-circuits when `portfolios` is empty, so the sync no-ops cleanly. Other HTTP errors (auth, 5xx) keep throwing. ## Test Added `returns empty performance when indexa capital responds 404` in `IndexaCapitalBalanceSyncTest`. ``` php artisan test --filter=IndexaCapitalBalanceSyncTest Tests: 12 passed ``` Fixes PHP-LARAVEL-1X
This commit is contained in:
parent
31b9198775
commit
06e7eed4e2
|
|
@ -64,27 +64,46 @@ class IndexaCapitalClient
|
|||
/**
|
||||
* Get performance data for an account, including current portfolio value.
|
||||
*
|
||||
* Returns an empty array when Indexa Capital responds with 404 (no
|
||||
* performance data available yet for the account, e.g. brand-new or
|
||||
* inactive accounts) instead of throwing, so the sync can no-op cleanly.
|
||||
*
|
||||
* @return array{total_amount?: float, return?: float, return_percentage?: float, portfolios?: array<int, array{date?: string, total_amount?: float, return?: float}>, net_amounts?: array<string, float>}
|
||||
*/
|
||||
public function getPerformance(string $accountNumber): array
|
||||
{
|
||||
$response = $this->client()->get("/accounts/{$accountNumber}/performance");
|
||||
$response = $this->client(throwOnError: false)->get("/accounts/{$accountNumber}/performance");
|
||||
|
||||
if ($response->status() === 404) {
|
||||
Log::info('No Indexa Capital performance data available', [
|
||||
'account_number' => $accountNumber,
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$response->throw();
|
||||
|
||||
return $response->json();
|
||||
$json = $response->json();
|
||||
|
||||
return is_array($json) ? $json : [];
|
||||
}
|
||||
|
||||
private function client(): PendingRequest
|
||||
private function client(bool $throwOnError = true): PendingRequest
|
||||
{
|
||||
return Http::baseUrl(self::BASE_URL)
|
||||
$client = Http::baseUrl(self::BASE_URL)
|
||||
->withHeaders(['X-AUTH-TOKEN' => $this->apiToken])
|
||||
->acceptJson()
|
||||
->throw(function ($response, $exception) {
|
||||
Log::error('Indexa Capital API error', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->json(),
|
||||
]);
|
||||
});
|
||||
->acceptJson();
|
||||
|
||||
if (! $throwOnError) {
|
||||
return $client;
|
||||
}
|
||||
|
||||
return $client->throw(function ($response, $exception) {
|
||||
Log::error('Indexa Capital API error', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->json(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,29 @@ test('skips account without external_account_id', function () {
|
|||
expect($account->balances()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('returns empty performance when indexa capital responds 404', function () {
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->indexaCapital()->create([
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
$account = Account::factory()->connected()->create([
|
||||
'user_id' => $user->id,
|
||||
'banking_connection_id' => $connection->id,
|
||||
'external_account_id' => 'IC-404',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'api.indexacapital.com/accounts/IC-404/performance' => Http::response('<html>not found</html>', 404),
|
||||
]);
|
||||
|
||||
$client = new IndexaCapitalClient('test-token');
|
||||
$service = app(IndexaCapitalBalanceSyncService::class);
|
||||
|
||||
$service->sync($account, $client);
|
||||
|
||||
expect($account->balances()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('handles missing portfolios gracefully', function () {
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->indexaCapital()->create([
|
||||
|
|
|
|||
Loading…
Reference in New Issue