package watchlist import ( "encoding/json" "os" "path/filepath" "testing" ) func TestGlobMatch(t *testing.T) { s := New(filepath.Join(t.TempDir(), "watchlist.json")) if err := s.Add("RI0SP*", false); err != nil { t.Fatal(err) } if err := s.Add("N8W", false); err != nil { t.Fatal(err) } // A starred entry is a family: the expedition's portable forms. for _, call := range []string{"RI0SP", "RI0SP/MM", "RI0SP/P"} { if _, ok := s.Match(call); !ok { t.Errorf("Match(%q) = false, want true", call) } } // A bare entry is THAT call — the reported bug was N8W lighting up for // N8WCR, a different station entirely. if _, ok := s.Match("N8W"); !ok { t.Error("exact entry must match its own call") } if _, ok := s.Match("N8WCR"); ok { t.Error("exact entry must NOT match a longer call") } if _, ok := s.Match("RI0S"); ok { t.Error("a SHORTER call must not match a starred entry") } if err := s.Add("VK*9", false); err == nil { t.Error("a mid-string star must be refused") } if err := s.Add("*", false); err == nil { t.Error("a bare star must be refused") } } func TestDXHunterFileRoundTrips(t *testing.T) { // A real DXHunter entry, ClubLog block included. It must survive // load → save byte-meaningfully: same keys, values preserved. src := `[{"callsign":"C5SP","lastSeen":"0001-01-01T00:00:00Z","lastSeenStr":"Never", "addedAt":"2026-01-17T00:18:43.89Z","spotCount":7,"isContest":true,"notify":true, "isExpedition":true,"clubLogQSOs24h":120,"clubLogTotalQSOs":30500, "clubLogHasOQRS":true,"clubLogLiveStream":true,"clubLogUpdatedAt":"2026-08-28T22:32:18Z"}]` dir := t.TempDir() path := filepath.Join(dir, "watchlist.json") if err := os.WriteFile(path, []byte(src), 0o644); err != nil { t.Fatal(err) } s := New(path) s.Flush() out, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var list []map[string]any if err := json.Unmarshal(out, &list); err != nil { t.Fatal(err) } if len(list) != 1 { t.Fatalf("got %d entries", len(list)) } e := list[0] for k, want := range map[string]any{ "callsign": "C5SP", "isContest": true, "notify": true, "isExpedition": true, "clubLogQSOs24h": float64(120), "clubLogTotalQSOs": float64(30500), "clubLogHasOQRS": true, } { if e[k] != want { t.Errorf("%s = %v, want %v", k, e[k], want) } } } func TestMarkSeenAndNotify(t *testing.T) { s := New(filepath.Join(t.TempDir(), "watchlist.json")) _ = s.Add("HB040A", false) _ = s.SetNotify("HB040A", true) entry, notify, ok := s.MarkSeen("HB040A") if !ok || !notify || entry != "HB040A" { t.Fatalf("MarkSeen = %q %v %v", entry, notify, ok) } list := s.Entries() if list[0].SpotCount != 1 || list[0].LastSeenStr != "Just now" { t.Errorf("entry after MarkSeen: %+v", list[0]) } }