whisper-money/tests/Feature
Víctor Falcón eb60f2eb30
fix(open-banking): unblock account mapping when the bank reports accounts without a uid (#746)
## Problem

A paying user reported that "Save & Sync" on the account mapping screen
did nothing — no error, no navigation, nothing.

EnableBanking returned their Société Générale card (`CB Visa`) with
`uid: null`:

```json
{"uid": null, "name": "CB Visa", "cash_account_type": "CARD", "account_id": {"iban": null, "other": {"scheme_name": "CPAN", "identification": "************8210"}}}
```

The page renders one mapping row per pending account, so the form posted
`bank_account_uid: null`. `MapAccountsRequest` requires it, the request
422'd, and since the page never rendered validation errors the button
looked dead. The user retried the connection six times.

A second problem kept them from doing what they actually wanted: both
accounts reported `currency: "XXX"` — ISO 4217 for "no currency".
`getCompatibleAccounts` filtered their existing EUR accounts against
`"XXX"`, matched nothing, and the "Link to existing account" option was
never rendered. The backend already knew `XXX` isn't a currency
(`AccountUserCurrencyService::resolveImportedCurrency`); the frontend
didn't.

## Fix

- Accounts without a uid are filtered out of the mapping screen. They
can never be synced anyway — every sync service early-returns on an
empty `external_account_id` — and `CreatesAccountsFromPending` already
skipped them during onboarding. That rule now lives in one place,
`BankingConnection::mappablePendingAccounts()`.
- The skipped accounts are named on the page, so users don't go hunting
for an account they can see in their bank app.
- A connection where *no* account has a uid is closed out instead of
parking on a mapping page that could only ever 422.
- Validation errors surface as a toast, with app-authored messages in
`MapAccountsRequest::messages()` instead of raw field paths.
- `XXX` (and a blank/lowercase variant) is treated as "unknown
currency": the code isn't displayed, and it no longer filters out every
linkable account.

## Scope

One user affected in production (the reporter). Their connection has one
valid account alongside the card, so this fix unblocks them on deploy —
no data change needed.

## Testing

- `tests/Feature/OpenBanking` — 315 passed, including two new cases:
uid-less accounts are hidden and named, and an all-uid-less connection
is closed rather than left awaiting mapping.
- Browser QA against a local reproduction of the exact production
payload: only `Compte Bancaire` is offered, `CB Visa` is named as
skipped, "Link to existing account" appears despite the `XXX` currency,
submitting without picking an account toasts the error, and picking
`Dany SG` links it (`external_account_id` set, connection `active`).

## Demo


https://github.com/user-attachments/assets/6275834a-a518-47b9-9015-a698e5926d71
<!-- PLACEHOLDER: drag account-mapping-fix-qa.mp4 here -->

## Not in this PR

`AuthorizationController::refreshAccountIds()` consumes the raw pending
list on reconnect; its positional fallback can pair a legacy account
with a uid-less entry. Not reachable for any account in production
today, it belongs to a different flow, and it deserves its own test —
filed separately rather than smuggled in here.
2026-08-09 18:41:35 +02:00
..
Ai fix(ai): stop a learned rule title from 500-ing the user's category change (#741) 2026-08-09 15:39:14 +00:00
Api
…
Auth
…
Console fix(banking): strip the ISO 20022 remittance tag from transaction descriptions (#744) 2026-08-09 18:11:05 +02:00
Events
…
Jobs
…
Listeners
…
Mcp
…
Onboarding fix(onboarding): don't trap users on the syncing step when a bank sync fails (#745) 2026-08-09 18:40:49 +02:00
OpenBanking fix(open-banking): unblock account mapping when the bank reports accounts without a uid (#746) 2026-08-09 18:41:35 +02:00
Services
…
Settings
…
Spaces
…
Sync
…
AccountBalanceControllerTest.php
…
AccountControllerTest.php
…
AccountImportConfigTest.php
…
AccountUserCurrencyServiceTest.php
…
AiConsentSettingsTest.php
…
AiConsentTest.php
…
AlignAccountsEncryptedFlagMigrationTest.php
…
ApplyRealEstateRevaluationTest.php
…
AuthenticatedLayoutSafeAreaTest.php
…
AutomationRuleApplicationTest.php
…
AutomationRuleEvaluationTest.php
…
AutomationRuleTest.php fix(ai): stop a learned rule title from 500-ing the user's category change (#741) 2026-08-09 15:39:14 +00:00
BackfillAccountIbansCommandTest.php
…
BackfillXxxAccountCurrenciesTest.php
…
BalanceLookupTest.php
…
BudgetHistoricalAssignmentTest.php
…
BudgetNotificationTest.php
…
BudgetPeriodDateTest.php
…
BudgetPeriodServiceTest.php
…
BudgetTest.php
…
BudgetTransactionServiceTest.php
…
BulkUpdateTransactionsTest.php
…
CancelFreeEnableBankingConnectionsCommandTest.php
…
CashflowAnalyticsTest.php
…
CashflowPageTest.php
…
CatchAllBudgetTest.php
…
CategoryMonthlyBreakdownTest.php
…
CurrencyConversionServiceTest.php
…
DashboardAnalyticsTest.php
…
DashboardTest.php
…
DecryptTransactionsTest.php
…
DeleteUserCommandTest.php
…
DemoAccountRestrictionsTest.php
…
DisconnectBankingConnectionsCommandTest.php
…
DiscordWebhookTest.php
…
EncryptionTest.php
…
ExampleTest.php
…
ExchangeRateServiceTest.php
…
GenerateStripePromotionCodesCommandTest.php
…
IdorVulnerabilityTest.php
…
ImportDataTest.php
…
InertiaSharedDataTest.php
…
IntegrationRequestTest.php
…
LabelTest.php
…
LoanTest.php
…
LocalizationTest.php
…
LoggingConfigTest.php
…
MailSenderTest.php
…
NewTransactionsMarkerTest.php
…
PersistUpsellSourceFromStripeTest.php
…
PlaintextTransactionsTest.php
…
PlanFeatureTest.php
…
PopoverSafeAreaTest.php
…
PostStripeEventToDiscordTest.php
…
PurgeResidualEncryptionArtifactsJobTest.php
…
PwaTest.php
…
QueueConfigTest.php
…
ReEvaluateTransactionRulesTest.php
…
RealEstateAvailabilityTest.php
…
RealEstateTest.php
…
ResendSyncCommandTest.php
…
RouteNotificationForMailTest.php
…
RuleEngineParityTest.php
…
SavedFilterTest.php
…
SelfServeRefundTest.php
…
SendAiCohortReportCommandTest.php
…
SendAiConsentFollowUpEmailsCommandTest.php
…
SendDailyStatsReportCommandTest.php
…
SendExperimentFunnelReportCommandTest.php
…
SendStuckCohortReportCommandTest.php
…
SendSubscriptionFunnelReportCommandTest.php
…
SentryConfigTest.php
…
SentryUserMiddlewareTest.php
…
SetLocaleTest.php
…
SitemapTest.php
…
StrayHttpRequestGuardTest.php
…
StripeSubscriptionStatsCommandTest.php
…
SubscriptionExperimentTest.php
…
SubscriptionTest.php
…
SuggestionPersistenceTest.php
…
SyncBankingConnectionsCommandTest.php
…
SyncStripePricesCommandTest.php
…
TrackLastActiveAtTest.php
…
TransactionAnalysisTest.php
…
TransactionFilterTest.php
…
TransactionSideClassificationTest.php
…
TransactionTest.php
…
WelcomeBanksOrderingTest.php
…