package lookup import ( "context" "path/filepath" "testing" "hamlog/internal/db" ) func testCache(t *testing.T) *Cache { t.Helper() conn, err := db.Open(filepath.Join(t.TempDir(), "c.db")) if err != nil { t.Fatalf("open: %v", err) } t.Cleanup(func() { conn.Close() }) return NewCache(conn, 0) } // Adding a field to the cache leaves every row already in it without that field, // and the cache lasts thirty days. So a callsign looked up before the change // would go a MONTH without its island reference — which is exactly what the // first test of the feature ran into: a QRZ record plainly carrying // EU-048, and no IOTA on the entry. // // A row that predates the column is therefore treated as stale and refetched // once. NULL and "" mean different things here, and that is the whole mechanism. func TestCacheRefetchesRowsWrittenBeforeTheIOTAColumn(t *testing.T) { c := testCache(t) ctx := context.Background() // An operator with an island: stored and returned. if err := c.Put(ctx, Result{Callsign: "F5IRH", Name: "Max", IOTA: "EU-048", Source: "qrz"}); err != nil { t.Fatalf("put: %v", err) } got, ok := c.Get(ctx, "F5IRH") if !ok || got.IOTA != "EU-048" { t.Fatalf("Get = (%+v,%v), want the island back", got, ok) } // An operator with NO island: an empty string is stored, and the row stays // usable. If this wrote NULL, every ordinary callsign would refetch for ever. if err := c.Put(ctx, Result{Callsign: "M0ABC", Name: "Ann", Source: "qrz"}); err != nil { t.Fatalf("put: %v", err) } got, ok = c.Get(ctx, "M0ABC") if !ok { t.Fatal("a callsign with no island was treated as stale — every lookup would repeat for ever") } if got.IOTA != "" { t.Errorf("IOTA = %q for an operator with no island", got.IOTA) } }