package fio import ( "context" "io" "net/http" "net/http/httptest" "os" "testing" "time" ) func TestAPIClient_ParseResponse(t *testing.T) { body, err := os.ReadFile("testdata/api_response.json") if err != nil { t.Fatal(err) } txns, err := parseAPIResponse(body) if err != nil { t.Fatal(err) } if len(txns) != 1 { t.Fatalf("want 1 txn (outgoing filtered), got %d", len(txns)) } tx := txns[0] if tx.Date != "2026-04-10" { t.Errorf("date: want '2026-04-10', got %q", tx.Date) } if tx.Amount != 750 { t.Errorf("amount: want 750, got %v", tx.Amount) } if tx.Sender != "Jana Novakova" { t.Errorf("sender: want 'Jana Novakova', got %q", tx.Sender) } if tx.Message != "duben 2026" { t.Errorf("message: want 'duben 2026', got %q", tx.Message) } if tx.VS != "123" { t.Errorf("vs: want '123', got %q", tx.VS) } if tx.BankID != "12345678901" { t.Errorf("bank_id: want '12345678901', got %q", tx.BankID) } } func TestAPIClient_HTTPRoundTrip(t *testing.T) { body, _ := os.ReadFile("testdata/api_response.json") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write(body) })) defer srv.Close() c := &apiClient{token: "TESTTOKEN", hc: &overrideClient{base: srv.Client(), baseURL: srv.URL}} txns, err := c.FetchTransactions(context.Background(), time.Now().AddDate(0, -1, 0), time.Now()) if err != nil { t.Fatal(err) } if len(txns) != 1 { t.Fatalf("want 1 txn, got %d", len(txns)) } } func TestTransparentClient_ParseHTML(t *testing.T) { body, err := os.ReadFile("testdata/transparent.html") if err != nil { t.Fatal(err) } txns, err := parseTransparentHTML(body) if err != nil { t.Fatal(err) } // Only the incoming row (750 CZK) should be kept; -200 is outgoing if len(txns) != 1 { t.Fatalf("want 1 txn (outgoing filtered), got %d", len(txns)) } tx := txns[0] if tx.Date != "2026-04-10" { t.Errorf("date: want '2026-04-10', got %q", tx.Date) } if tx.Amount != 750 { t.Errorf("amount: want 750, got %v", tx.Amount) } if tx.Sender != "Jana Novakova" { t.Errorf("sender: want 'Jana Novakova', got %q", tx.Sender) } if tx.VS != "123" { t.Errorf("vs: want '123', got %q", tx.VS) } if tx.BankID != "" { t.Errorf("bank_id: want empty on HTML path, got %q", tx.BankID) } } func TestParseCzechDate(t *testing.T) { cases := []struct{ in, want string }{ {"10.04.2026", "2026-04-10"}, {"10/04/2026", "2026-04-10"}, {"7.5.2026", "2026-05-07"}, // non-padded — real Fio transparent page format {"3.12.2025", "2025-12-03"}, // non-padded single-digit day, double-digit month {"07.05.26", "2026-05-07"}, // padded 2-digit year — current Fio transparent page format {"7.5.26", "2026-05-07"}, // non-padded 2-digit year {"07/05/26", "2026-05-07"}, // slash variant {"", ""}, {"invalid", ""}, } for _, c := range cases { if got := parseCzechDate(c.in); got != c.want { t.Errorf("parseCzechDate(%q) = %q, want %q", c.in, got, c.want) } } } func TestExtractSecondTableRows_NestedTable(t *testing.T) { // Regression: a nested inside the target must not cause early exit. html := `
nav
Date
7.5.2026
nested
6.5.2026
` rows := extractSecondTableRows([]byte(html)) if len(rows) != 2 { t.Errorf("want 2 data rows, got %d: %v", len(rows), rows) } } func TestParseCzechAmount(t *testing.T) { cases := []struct { in string want float64 }{ {"750,00 CZK", 750}, {"1.500,00", 1500}, {"1500.00", 1500}, {"-200,00 CZK", -200}, } for _, c := range cases { if got := parseCzechAmount(c.in); got != c.want { t.Errorf("parseCzechAmount(%q) = %v, want %v", c.in, got, c.want) } } } func TestFake(t *testing.T) { f := &Fake{Transactions: []Transaction{{Date: "2026-04-01", Amount: 500}}} txns, err := f.FetchTransactions(context.Background(), time.Now(), time.Now()) if err != nil { t.Fatal(err) } if len(txns) != 1 || txns[0].Date != "2026-04-01" { t.Errorf("unexpected: %v", txns) } } // overrideClient replaces the URL in requests so we can hit a local test server // instead of the real Fio URL. type overrideClient struct { base *http.Client baseURL string } func (o *overrideClient) Do(req *http.Request) (*http.Response, error) { r2, _ := http.NewRequestWithContext(req.Context(), req.Method, o.baseURL+req.URL.Path, nil) resp, err := o.base.Do(r2) if err != nil { return nil, err } // The api client reads the body, so re-serve whatever the test server returned. return resp, nil } // verify Fake satisfies Client var _ Client = (*Fake)(nil) // ensure io.ReadAll isn't called at top level (compile-time reference suppressor) var _ = io.ReadAll