Import the longest bank-permitted history and surface real provider errors

Manual history imports failed opaquely once a bank capped lookback on an
established consent (N26 rejects date_from beyond ~90 days with
WRONG_TRANSACTIONS_PERIOD). Backfill now requests the documented longest
fetching strategy, reports the coverage the bank actually provided, and
non-2xx responses surface allowlisted documented error codes instead of a
generic fallback. Dead-session codes map to reconnection. Failed syncs
retry hourly so a stale sync banner no longer persists for a day.
This commit is contained in:
Lars Nolden
2026-09-10 22:58:03 +02:00
parent 4324660888
commit a8722d58c3
7 changed files with 213 additions and 36 deletions
+12 -3
View File
@@ -18,6 +18,7 @@ type backfillBank struct {
statusIDs []string
accounts []domain.Account
toDates []string
longests []bool
statusErr error
fetchErr error
}
@@ -30,10 +31,11 @@ func (b *backfillBank) Status(ctx context.Context, id string) (banking.SessionSt
return b.historyBank.Status(ctx, id)
}
func (b *backfillBank) Transactions(ctx context.Context, account domain.Account, from, to string) ([]domain.Facts, error) {
func (b *backfillBank) Transactions(ctx context.Context, account domain.Account, from, to string, longest bool) ([]domain.Facts, error) {
b.accounts = append(b.accounts, account)
b.toDates = append(b.toDates, to)
rows, err := b.historyBank.Transactions(ctx, account, from, to)
b.longests = append(b.longests, longest)
rows, err := b.historyBank.Transactions(ctx, account, from, to, longest)
if b.fetchErr != nil {
// A provider can fail after accumulating a page: none of it is importable.
return rows, b.fetchErr
@@ -99,7 +101,7 @@ func TestBackfillImportsOlderFactsOnceWithoutChangingSyncState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
b.statusIDs, b.accounts, b.fromDates, b.toDates = nil, nil, nil, nil
b.statusIDs, b.accounts, b.fromDates, b.toDates, b.longests = nil, nil, nil, nil, nil
// Provider-local IDs need not match our canonical account ID.
providerAccount := s.Data.Accounts[0]
providerAccount.ID = "provider_generated_id"
@@ -121,6 +123,13 @@ func TestBackfillImportsOlderFactsOnceWithoutChangingSyncState(t *testing.T) {
if len(b.fromDates) != 1 || (b.fromDates[0] != fromBefore && b.fromDates[0] != fromAfter) || (b.toDates[0] != toBefore && b.toDates[0] != toAfter) {
t.Fatalf("backfill did not request the selected calendar-month range: %v to %v", b.fromDates, b.toDates)
}
if !reflect.DeepEqual(b.longests, []bool{true}) {
t.Fatal("manual history import did not request the tolerant longest-period strategy")
}
oldest := time.Now().UTC().AddDate(0, 0, -400).Format("2006-01-02")
if result.RequestedFrom != b.fromDates[0] || result.EarliestFetched != oldest {
t.Fatalf("backfill misreported its history coverage: %+v", result)
}
for _, existing := range s.Data.Transactions {
found := false
for _, tx := range result.State.Data.Transactions {