Files
fuj-management/go/internal/io/fio/fio_test.go
Jan Novak b41b8ef29c
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
fix(go/fio): accept 2-digit year format in transparent date parser
Fio's transparent account page now serves dates as DD.MM.YY (e.g.
07.05.26) rather than the previously expected 4-digit-year format.
Extends parseCzechDate to try all eight layout variants: padded and
non-padded, dot and slash separators, 4-digit and 2-digit years.

Go maps 2-digit year 00-68 → 2000-2068, so 26 → 2026.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:12:34 +02:00

179 lines
4.8 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <table> inside the target must not cause early exit.
html := `<table class="table"><tr><td>nav</td></tr></table>
<table class="table">
<thead><tr><th>Date</th></tr></thead>
<tbody>
<tr><td>7.5.2026</td><td><table><tr><td>nested</td></tr></table></td></tr>
<tr><td>6.5.2026</td><td></td></tr>
</tbody>
</table>`
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