package dxped import ( "reflect" "testing" "time" ) // Pinned against REAL feed text: the parser reads a fixed sentence, and a // wrong split silently empties a DXpedition list rather than failing loudly. func TestParseActivation(t *testing.T) { desc := "Aug 24-31, 2026 -- St Kitts and Nevis -- V47JA -- QSL: LoTW -- " + "Source: W5JON (Aug 1, 2026) -- By W5JON as V47JA fm Calypso Bay; 160-6m; SSB FT8; yagi, verticals" a := parseActivation(desc, "https://www.qrz.com/lookup/v47ja") if a == nil { t.Fatal("parseActivation returned nil on a real ADXO description") } if a.DXCC != "St Kitts and Nevis" { t.Errorf("DXCC = %q", a.DXCC) } if a.Callsign != "V47JA" { t.Errorf("Callsign = %q, want V47JA (mined from 'as')", a.Callsign) } if a.QSL != "LoTW" { t.Errorf("QSL = %q", a.QSL) } if a.StartDate != "Aug 24, 2026" || a.EndDate != "Aug 31, 2026" { t.Errorf("dates = %q..%q", a.StartDate, a.EndDate) } if want := []string{"160m", "6m"}; !reflect.DeepEqual(a.Bands, want) { t.Errorf("Bands = %v, want %v", a.Bands, want) } if want := []string{"SSB", "FT8"}; !reflect.DeepEqual(a.Modes, want) { t.Errorf("Modes = %v, want %v", a.Modes, want) } } // The callsign column often holds only the prefix; the operators prose holds // the real thing, and a slashed call must lead with the DXCC prefix or no spot // will ever match it. func TestCallsAfterAsAndNormalise(t *testing.T) { if got := callsAfterAs("SQ2RAD as VP2EAD, M0PLX as VP2ELX"); !reflect.DeepEqual(got, []string{"VP2EAD", "VP2ELX"}) { t.Errorf("callsAfterAs = %v", got) } if got := normalizeCall("JG8NQJ/JD1", "JD1"); got != "JD1/JG8NQJ" { t.Errorf("normalizeCall = %q, want JD1/JG8NQJ", got) } if got := normalizeCall("PJ2/W2APF", "PJ2"); got != "PJ2/W2APF" { t.Errorf("normalizeCall rewrote an already-correct call: %q", got) } } func TestActivationStatus(t *testing.T) { now := time.Date(2026, 8, 27, 12, 0, 0, 0, time.UTC) cases := []struct{ start, end, want string }{ {"Aug 24, 2026", "Aug 31, 2026", "active"}, {"Sep 10, 2026", "Sep 20, 2026", "upcoming"}, {"Aug 1, 2026", "Aug 10, 2026", "ended"}, {"Aug 24, 2026", "Aug 27, 2026", "active"}, // ends TODAY: still on {"garbage", "garbage", "upcoming"}, } for _, c := range cases { if got := activationStatus(c.start, c.end, now); got != c.want { t.Errorf("activationStatus(%q,%q) = %q, want %q", c.start, c.end, got, c.want) } } } // Mining a headline must find calls without inventing them out of jargon. func TestCallsInHeadline(t *testing.T) { cases := []struct { title string want []string }{ {"3B7M, St Brandon", []string{"3B7M"}}, {"TX5S team lands on Clipperton", []string{"TX5S"}}, {"FT8 activity on 160m in 2026", nil}, {"VP6D QSL via OQRS, LoTW", []string{"VP6D"}}, {"JD1/JG8NQJ from Minami Torishima", []string{"JD1/JG8NQJ"}}, } for _, c := range cases { if got := callsInHeadline(c.title); !reflect.DeepEqual(got, c.want) { t.Errorf("callsInHeadline(%q) = %v, want %v", c.title, got, c.want) } } }