whisper-money/database/factories
Víctor Falcón a3eafecf60
fix(onboarding): don't trap users on the syncing step when a bank sync fails (#745)
Reported by a user who spent over an hour and a half on the onboarding
syncing step, on two devices, without ever getting into the app.

## What was happening

When Enable Banking rate limits a connection, `SyncBankingConnectionJob`
records the error and backs off, but leaves the connection **Active with
`last_synced_at` still NULL** — the exact shape `syncStatus` treated as
"still syncing". The step polled that endpoint forever, and there was no
deadline or escape on the client.

Confirmed in production for the reporter: a Trade Republic connection
returned `429` on every scheduled sync for two days straight. They
unblocked themselves by deleting the connection — `onboarded_at` was set
four seconds later. Three other users are in the same state right now,
one of them from today.

## What changed

- **`syncStatus` no longer waits on a connection that already failed**,
and reports it as `failed` separately from `pending`.
- **The step says so instead of pretending it worked.** Advancing
silently dropped the user on a "You are all set!" screen with an empty
dashboard and no way to find out why. Now they get a short explanation
and a Continue button.
- **Client deadline raised to 5 minutes** — the previous 2 was under the
worst case of a healthy first sync (3 attempts of a 120s job, 30s apart)
— and the status request now has a timeout, so a hanging poll can't
stall the step either. A failing status check keeps polling until the
deadline rather than skipping the user ahead.
- **`AccountMappingController` clears the stale error** when it
reactivates a connection, so a genuine sync in flight isn't reported as
failed.

## Testing

- Feature tests for the rate-limited case, for a healthy connection
syncing alongside a failed one, and for the failed flag; a
`rateLimited()` factory state replaces the hand-rolled attributes.
- Vitest coverage for the three client branches: failed, deadline
reached, and the normal advance.
- Browser QA against the real app, reproducing the production state: the
honest screen appears instead of the spinner, Continue moves on, the
user completes onboarding and reaches the app; and a genuinely pending
sync still spins and then advances by itself once the connection syncs.
No console errors.

## Demo


https://github.com/user-attachments/assets/e54e029d-db7a-47b0-8b93-0da5569aa363


<!-- PLACEHOLDER: drag the QA video here -->

## Not in this PR

`settings/connections.tsx` has the same `active && !last_synced_at` =
"syncing" heuristic, so a rate-limited connection shows a permanent
green "Syncing" badge there and its error is never rendered (the error
block is gated on `status === 'error'`). Same bug class, separate
surface — worth a follow-up.
2026-08-09 18:40:49 +02:00
..
AccountBalanceFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
AccountFactory.php feat(accounts): add loan amortization projections for loan accounts (#246) 2026-03-26 15:06:09 +01:00
AccountImportConfigFactory.php feat(import): persist per-account import configuration on the backend (#698) 2026-07-18 16:10:40 +02:00
AiConsentFactory.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
AutomationRuleFactory.php feat(ai): learn from category corrections so the AI stops repeating the same mistake (#608) 2026-06-29 19:12:15 +02:00
BankFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
BankingConnectionFactory.php fix(onboarding): don't trap users on the syncing step when a bank sync fails (#745) 2026-08-09 18:40:49 +02:00
BudgetFactory.php feat: add catch-all budgets (#527) 2026-06-15 16:07:19 +00:00
BudgetPeriodFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
BudgetTransactionFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
CategoryCorrectionFactory.php feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
CategoryFactory.php feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
ExchangeRateFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
IntegrationRequestFactory.php feat(integration-requests): add done status and fix review command crash on orphaned author (#601) 2026-06-27 14:42:09 +00:00
IntegrationRequestVoteFactory.php feat(integration-requests): community board to request & vote bank integrations (#550) 2026-06-17 12:50:51 +00:00
LabelFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
LoanDetailFactory.php feat(accounts): add loan amortization projections for loan accounts (#246) 2026-03-26 15:06:09 +01:00
RealEstateDetailFactory.php feat(accounts): add market value and annual revaluation to real estate accounts (#245) 2026-03-26 11:02:20 +01:00
RuleSuggestionFactory.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
SavedFilterFactory.php feat(transactions): save and reuse transaction filters (#496) 2026-06-05 18:00:14 +02:00
SpaceFactory.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
SuggestionRunFactory.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
TransactionFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
UserFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
UserLeadFactory.php refactor: remove HIDE_AUTH_BUTTONS launch gate and waitlist apparatus (#717) 2026-07-22 08:51:48 +02:00
UserMailLogFactory.php feat(drip): email users stuck on the paywall a day after onboarding (#562) 2026-06-19 14:11:12 +00:00
UserSettingFactory.php fix(budgets): keep per-transaction email notifications opt-in (#735) 2026-07-25 12:36:13 +02:00