init
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"finance-duck/internal/banking"
|
||||
"finance-duck/internal/domain"
|
||||
)
|
||||
|
||||
type bankScenario struct {
|
||||
session banking.Session
|
||||
fail bool
|
||||
}
|
||||
|
||||
func (b *bankScenario) Authorize(context.Context, string, string, string) (string, error) {
|
||||
return "https://bank.example/authorize", nil
|
||||
}
|
||||
func (b *bankScenario) Exchange(context.Context, string) (banking.Session, error) {
|
||||
return b.session, nil
|
||||
}
|
||||
func (b *bankScenario) Status(context.Context, string) (banking.Session, error) {
|
||||
if b.fail {
|
||||
return banking.Session{}, errors.New("expired")
|
||||
}
|
||||
return b.session, nil
|
||||
}
|
||||
func (b *bankScenario) Balances(context.Context, string) ([]banking.Balance, error) {
|
||||
return []banking.Balance{{Amount: "100.00", Currency: "EUR", Type: "CLBD"}}, nil
|
||||
}
|
||||
func (b *bankScenario) Transactions(_ context.Context, a domain.Account, from, to string) ([]domain.Facts, error) {
|
||||
if b.fail {
|
||||
return nil, errors.New("offline")
|
||||
}
|
||||
return []domain.Facts{{Source: "enablebanking", AccountID: a.ID, BookingDate: time.Now().UTC().AddDate(0, 0, -1).Format("2006-01-02"), Amount: "-42.80", Currency: "EUR", RawDescription: "REWE", ExternalID: "entry_stable"}}, nil
|
||||
}
|
||||
func TestSyncRestoresSavedConsentBindingsAndDoesNotDuplicateFacts(t *testing.T) {
|
||||
a, s := testApp(t)
|
||||
account := s.Data.Accounts[0]
|
||||
account.ExternalAccountID = "provider_new"
|
||||
account.IBAN = "DE89370400440532013000"
|
||||
provider := &bankScenario{session: banking.Session{ID: "new_session", ValidUntil: time.Now().Add(24 * time.Hour).Format(time.RFC3339), Accounts: []domain.Account{account}}}
|
||||
a.bank = provider
|
||||
a.ops.Sessions = []banking.Session{provider.session}
|
||||
if err := a.saveOps(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first, err := a.Sync(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(first.Data.Accounts) != 1 || first.Data.Accounts[0].ExternalAccountID != "provider_new" || len(first.Data.Transactions) != 1 {
|
||||
t.Fatalf("saved session did not recover/import: %+v", first.Data)
|
||||
}
|
||||
again, err := a.Sync(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(first.Data, again.Data) {
|
||||
t.Fatal("repeated bank synchronization changed canonical financial data")
|
||||
}
|
||||
provider.fail = true
|
||||
failed, err := a.Sync(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if failed.Status.SyncError == "" || !reflect.DeepEqual(again.Data, failed.Data) {
|
||||
t.Fatal("provider failure was not isolated from canonical data")
|
||||
}
|
||||
}
|
||||
func TestReconnectReplacesOldConsentWithoutDuplicatingLocalAccount(t *testing.T) {
|
||||
a, s := testApp(t)
|
||||
account := s.Data.Accounts[0]
|
||||
account.ExternalAccountID = "old_uid"
|
||||
account.IBAN = "DE89370400440532013000"
|
||||
s, err := a.Mutate(context.Background(), s.Revision, func(d *domain.Dataset) error { return SaveAccount(d, account) })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.ops.Sessions = []banking.Session{{ID: "old_session", Accounts: []domain.Account{account}}}
|
||||
renewed := account
|
||||
renewed.ID = "provider_local_id"
|
||||
renewed.ExternalAccountID = "new_uid"
|
||||
renewed.DisplayName = "Bank-generated name"
|
||||
a.bank = &bankScenario{session: banking.Session{ID: "new_session", ValidUntil: time.Now().Add(24 * time.Hour).Format(time.RFC3339), Accounts: []domain.Account{renewed}}}
|
||||
a.authStates["one_time_state"] = authorization{Expires: time.Now().Add(time.Minute), Institution: "N26", Country: "DE"}
|
||||
if err = a.Callback(context.Background(), "bank_code", "one_time_state"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err := a.Snapshot(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(after.Data.Accounts) != 1 || after.Data.Accounts[0].ID != account.ID || after.Data.Accounts[0].DisplayName != account.DisplayName || after.Data.Accounts[0].ExternalAccountID != "new_uid" {
|
||||
t.Fatal("reconnect duplicated account or lost local display name")
|
||||
}
|
||||
if len(after.Sessions) != 1 || after.Sessions[0].ID != "new_session" {
|
||||
t.Fatal("expired session remains active after reconnect")
|
||||
}
|
||||
if err = a.Callback(context.Background(), "bank_code", "one_time_state"); err == nil {
|
||||
t.Fatal("authorization state replay was accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user