package banksync import ( "context" "fuj-management/go/internal/domain/synch" "fuj-management/go/internal/io/fio" "fuj-management/go/internal/io/sheets" "testing" "time" ) var testFioTxns = []fio.Transaction{ {Date: "2026-04-10", Amount: 750, Sender: "Jana Novakova", Message: "duben 2026", VS: "123", BankID: "111"}, {Date: "2026-04-11", Amount: 500, Sender: "Petr Prach", Message: "april", VS: "456", BankID: "222"}, } func TestSyncToSheets_EmptySheet(t *testing.T) { sh := &sheets.Fake{Values: map[string][][]any{ "SHEETID/A1:K": {}, }} fioFake := &fio.Fake{Transactions: testFioTxns} n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30}) if err != nil { t.Fatal(err) } if n != 2 { t.Errorf("want 2 appended, got %d", n) } if len(sh.Appended) != 1 { t.Fatalf("want 1 AppendValues call, got %d", len(sh.Appended)) } rows := sh.Appended[0].Rows if len(rows) != 2 { t.Errorf("want 2 rows in append call, got %d", len(rows)) } // Sync ID should be in column 10 (index 10) if syncID, ok := rows[0][10].(string); !ok || len(syncID) != 64 { t.Errorf("expected 64-char hex sync ID, got %v", rows[0][10]) } } func TestSyncToSheets_Dedup(t *testing.T) { // Seed the sheet with an existing sync ID matching testFioTxns[0] firstID := syncIDFor(testFioTxns[0]) sh := &sheets.Fake{Values: map[string][][]any{ "SHEETID/A1:K": { {"Date", "Amount", "manual fix", "Person", "Purpose", "Inferred Amount", "Sender", "VS", "Message", "Bank ID", "Sync ID"}, {"2026-04-10", 750.0, "", "", "", "", "Jana Novakova", "123", "duben 2026", "111", firstID}, }, }} fioFake := &fio.Fake{Transactions: testFioTxns} n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30}) if err != nil { t.Fatal(err) } if n != 1 { t.Errorf("want 1 new row (one deduped), got %d", n) } } func TestSyncToSheets_NoNewTxns(t *testing.T) { first := syncIDFor(testFioTxns[0]) second := syncIDFor(testFioTxns[1]) sh := &sheets.Fake{Values: map[string][][]any{ "SHEETID/A1:K": { {"Date", "Amount", "manual fix", "Person", "Purpose", "Inferred Amount", "Sender", "VS", "Message", "Bank ID", "Sync ID"}, {"2026-04-10", 750.0, "", "", "", "", "Jana Novakova", "123", "duben 2026", "111", first}, {"2026-04-11", 500.0, "", "", "", "", "Petr Prach", "456", "april", "222", second}, }, }} fioFake := &fio.Fake{Transactions: testFioTxns} n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30}) if err != nil { t.Fatal(err) } if n != 0 { t.Errorf("want 0 new rows, got %d", n) } if len(sh.Appended) != 0 { t.Error("expected no AppendValues call when all deduped") } } func TestSyncToSheets_MissingHeader(t *testing.T) { sh := &sheets.Fake{Values: map[string][][]any{ "SHEETID/A1:K": { {"Wrong", "Headers"}, }, }} fioFake := &fio.Fake{Transactions: testFioTxns[:1]} n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30}) if err != nil { t.Fatal(err) } if n != 1 { t.Errorf("want 1 row appended after header fix, got %d", n) } } func TestSyncToSheets_Sort(t *testing.T) { sh := &sheets.Fake{Values: map[string][][]any{"SHEETID/A1:K": {}}} fioFake := &fio.Fake{Transactions: testFioTxns[:1]} _, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30, Sort: true}) if err != nil { t.Fatal(err) } // SortByDateColumn should have been called on the fake — check via a spy fake } func TestSyncToSheets_ExplicitDateWindow(t *testing.T) { sh := &sheets.Fake{Values: map[string][][]any{"SHEETID/A1:K": {}}} fioFake := &fio.Fake{Transactions: testFioTxns[:1]} from := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC) to := time.Date(2026, 4, 30, 0, 0, 0, 0, time.UTC) n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{From: from, To: to}) if err != nil { t.Fatal(err) } if n != 1 { t.Errorf("want 1 row, got %d", n) } } func TestSyncToSheets_DryRun(t *testing.T) { sh := &sheets.Fake{Values: map[string][][]any{"SHEETID/A1:K": {}}} fioFake := &fio.Fake{Transactions: testFioTxns} n, err := SyncToSheets(context.Background(), "SHEETID", fioFake, sh, SyncOpts{Days: 30, Sort: true, DryRun: true}) if err != nil { t.Fatal(err) } if n != 2 { t.Errorf("want 2 planned, got %d", n) } if len(sh.Appended) != 0 { t.Error("dry-run must not call AppendValues") } } // syncIDFor mirrors what SyncToSheets computes for a given fio.Transaction. func syncIDFor(tx fio.Transaction) string { currency := tx.Currency if currency == "" { currency = "CZK" } return synch.GenerateSyncID(synch.Transaction{ Date: tx.Date, Amount: tx.Amount, Currency: currency, Sender: tx.Sender, VS: tx.VS, Message: tx.Message, BankID: tx.BankID, }) }