All checks were successful
Deploy to K8s / deploy (push) Successful in 11s
- io/attendance: CSV-over-public-URL client + Fake for adult/junior tabs - io/drive: Drive v3 modifiedTime client + Fake - io/sheets: Sheets v4 client (GetValues/AppendValues/BatchUpdateValues/ WriteHeader/SortByDateColumn) + Fake with call-capture - io/cache: Drive-modifiedTime-gated FileCache; two TTL knobs; atomic writes; generic Get[T]; Python-compatible JSON format; Flush() - io/fio: Client interface backed by Fio REST API (apiClient) and HTML scraper (transparentClient); Fake; testdata fixtures - membership/sources: NewSources wires attendance CSV + Sheets + cache into LoadAdults/LoadJuniors/LoadTransactions/LoadExceptions; Czech month parsing + merged-month maps - banksync: SyncToSheets (SHA-256 dedup, optional sort) and InferPayments ([?] review prefix, dry-run) — tested with fakes - cmd/fuj: sync and infer subcommands wired; fees and reconcile use real NewSources; go.mod gains google.golang.org/api + x/net - gofumpt extra-rules applied across all packages; lint clean Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fuj-management/go/internal/io/drive"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestGet_FreshFetch(t *testing.T) {
|
|
dir := t.TempDir()
|
|
d := &drive.Fake{Times: map[string]string{"sheet1": "2026-01-01T00:00:00Z"}}
|
|
fc := New(d, dir, map[string]string{"mykey": "sheet1"}, time.Minute, time.Minute)
|
|
|
|
calls := 0
|
|
got, err := Get(context.Background(), fc, "mykey", func(_ context.Context) ([]string, error) {
|
|
calls++
|
|
return []string{"a", "b"}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 2 || got[0] != "a" {
|
|
t.Errorf("unexpected: %v", got)
|
|
}
|
|
if calls != 1 {
|
|
t.Errorf("want 1 fetch call, got %d", calls)
|
|
}
|
|
}
|
|
|
|
func TestGet_CacheHit(t *testing.T) {
|
|
dir := t.TempDir()
|
|
d := &drive.Fake{Times: map[string]string{"sheet1": "2026-01-01T00:00:00Z"}}
|
|
fc := New(d, dir, map[string]string{"mykey": "sheet1"}, time.Minute, time.Minute)
|
|
|
|
fetch := func(_ context.Context) ([]string, error) { return []string{"a"}, nil }
|
|
if _, err := Get(context.Background(), fc, "mykey", fetch); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Second call — modifiedTime unchanged, should hit cache
|
|
calls := 0
|
|
got, err := Get(context.Background(), fc, "mykey", func(_ context.Context) ([]string, error) {
|
|
calls++
|
|
return []string{"SHOULD_NOT_CALL"}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got[0] != "a" {
|
|
t.Errorf("want cache hit with 'a', got %q", got[0])
|
|
}
|
|
if calls != 0 {
|
|
t.Errorf("want 0 fetch calls on hit, got %d", calls)
|
|
}
|
|
}
|
|
|
|
func TestGet_CacheMiss_OnModifiedTimeChange(t *testing.T) {
|
|
dir := t.TempDir()
|
|
d := &drive.Fake{Times: map[string]string{"sheet1": "2026-01-01T00:00:00Z"}}
|
|
// No TTL guards so we always hit Drive
|
|
fc := New(d, dir, map[string]string{"mykey": "sheet1"}, 0, 0)
|
|
|
|
fetch := func(_ context.Context) ([]string, error) { return []string{"v1"}, nil }
|
|
if _, err := Get(context.Background(), fc, "mykey", fetch); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Sheet updated — change modifiedTime
|
|
d.Times["sheet1"] = "2026-02-01T00:00:00Z"
|
|
got, err := Get(context.Background(), fc, "mykey", func(_ context.Context) ([]string, error) {
|
|
return []string{"v2"}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got[0] != "v2" {
|
|
t.Errorf("want v2 after sheet update, got %q", got[0])
|
|
}
|
|
}
|
|
|
|
func TestGet_DriveFailureFallback(t *testing.T) {
|
|
dir := t.TempDir()
|
|
d := &drive.Fake{Err: errors.New("drive down")}
|
|
fc := New(d, dir, nil, 0, 0)
|
|
|
|
calls := 0
|
|
_, err := Get(context.Background(), fc, "mykey", func(_ context.Context) ([]string, error) {
|
|
calls++
|
|
return []string{"fallback"}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls != 1 {
|
|
t.Errorf("want 1 fetch call, got %d", calls)
|
|
}
|
|
}
|
|
|
|
func TestFlush(t *testing.T) {
|
|
dir := t.TempDir()
|
|
d := &drive.Fake{Times: map[string]string{"sheet1": "t1"}}
|
|
fc := New(d, dir, map[string]string{"k": "sheet1"}, 0, 0)
|
|
|
|
if _, err := Get(context.Background(), fc, "k", func(_ context.Context) (int, error) { return 42, nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
n, err := fc.Flush()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("want 1 deleted file, got %d", n)
|
|
}
|
|
// Cache dir should be empty of _cache.json files
|
|
entries, _ := os.ReadDir(dir)
|
|
for _, e := range entries {
|
|
if e.Name() != "" {
|
|
t.Errorf("expected empty dir after flush, found %s", e.Name())
|
|
}
|
|
}
|
|
}
|