package award import ( "testing" "time" "hamlog/internal/qso" ) func day(s string) time.Time { t, err := time.Parse("2006-01-02", s) if err != nil { panic(err) } return t } // A reference is not forever. A park is delisted, a district is merged, a castle // loses its number. A contact made while it existed still counts — it was a // valid contact on the day — and one made after it stopped existing does not. func TestRefValidityWindow(t *testing.T) { m := RefMeta{Code: "KL-01", Valid: true, ValidTo: "2025-08-31"} for _, tc := range []struct { day string want bool }{ {"2019-01-01", true}, {"2025-08-31", true}, // the last day it existed still counts {"2025-09-01", false}, {"2026-08-12", false}, } { if got := m.activeOn(tc.day); got != tc.want { t.Errorf("KL-01 on %s: active=%v, want %v", tc.day, got, tc.want) } } // A reference that only came into being partway through. n := RefMeta{Code: "KL-99", Valid: true, ValidFrom: "2025-01-15"} if n.activeOn("2025-01-14") { t.Error("counted a QSO from before the reference existed") } if !n.activeOn("2025-01-15") { t.Error("the first day it existed must count") } // No window of its own: the award's own dates govern, as they already do for // every QSO in the award. Nothing here may narrow that. if !(RefMeta{Code: "X", Valid: true}).activeOn("1970-01-01") { t.Error("a reference with no window must count on any date") } } // The whole point: the same QSO counts before the cutoff and does not after. func TestExpiredRefStopsCountingForLaterQSOs(t *testing.T) { d := &Def{ Code: "RDA", Name: "Russian District Award", Valid: true, Type: TypeQSOFields, Field: "note", MatchBy: "code", Confirm: []string{"lotw"}, } metas := []RefMeta{ {Code: "KL-01", Name: "Petrozavodsk", Valid: true, ValidTo: "2025-08-31"}, {Code: "KL-04", Name: "Kostomuksha", Valid: true}, } q := func(ref, on string) *qso.QSO { return &qso.QSO{Callsign: "RA1ABC", Band: "20m", Notes: ref, QSODate: day(on)} } if got := MatchQSO(*d, metas, q("KL-01", "2025-06-01")); len(got) != 1 || got[0] != "KL-01" { t.Errorf("a QSO made while KL-01 existed must count: got %v", got) } if got := MatchQSO(*d, metas, q("KL-01", "2025-09-15")); len(got) != 0 { t.Errorf("a QSO made after KL-01 ceased to exist must not count: got %v", got) } if got := MatchQSO(*d, metas, q("KL-04", "2025-09-15")); len(got) != 1 || got[0] != "KL-04" { t.Errorf("a reference with no window is unaffected: got %v", got) } } // The claim that an empty per-reference window "inherits the award's" has to be // a fact about the code, not a comment. Compute and MatchQSO both gate on // inScope, which enforces the award's own dates — so a reference with no window // of its own is already bounded by them, and duplicating the check per reference // would only create a second place for the same dates to disagree. func TestEmptyRefWindowInheritsTheAward(t *testing.T) { d := Def{ Code: "RDA", Name: "Russian District Award", Valid: true, Type: TypeQSOFields, Field: "note", MatchBy: "code", Confirm: []string{"lotw"}, ValidFrom: "1991-06-12", // the award itself starts here } metas := []RefMeta{{Code: "KL-04", Name: "Kostomuksha", Valid: true}} // no window of its own q := func(on string) *qso.QSO { return &qso.QSO{Callsign: "RA1ABC", Band: "20m", Notes: "KL-04", QSODate: day(on)} } if got := MatchQSO(d, metas, q("1991-06-11")); len(got) != 0 { t.Errorf("a QSO before the AWARD's start counted for a reference with no window of its own: %v", got) } if got := MatchQSO(d, metas, q("1991-06-12")); len(got) != 1 { t.Errorf("a QSO on the award's first day must count: %v", got) } // And a reference window NARROWER than the award's still applies on top. metas[0].ValidTo = "2025-08-31" if got := MatchQSO(d, metas, q("2025-09-01")); len(got) != 0 { t.Errorf("the reference's own end date was ignored: %v", got) } }