package award import ( "testing" "hamlog/internal/qso" ) // Every source the editor offers must actually do something. "qrzcom" and // "custom" were listed in the UI and in Def's doc comment but had no case in the // switch, so ticking either marked nothing as confirmed — a checkbox that is // trusted and silently inert. func TestConfirmedSources(t *testing.T) { cases := []struct { name string q qso.QSO d Def sources []string want bool }{ {"lotw", qso.QSO{LOTWRcvd: "Y"}, Def{}, []string{"lotw"}, true}, {"qsl", qso.QSO{QSLRcvd: "Y"}, Def{}, []string{"qsl"}, true}, {"eqsl", qso.QSO{EQSLRcvd: "Y"}, Def{}, []string{"eqsl"}, true}, // QRZ confirms on the DOWNLOAD status. The upload one is us telling QRZ // about the QSO, which is not a confirmation of anything. {"qrz download", qso.QSO{QRZComDownloadStatus: "Y"}, Def{}, []string{"qrzcom"}, true}, {"qrz upload only", qso.QSO{QRZComUploadStatus: "Y"}, Def{}, []string{"qrzcom"}, false}, // Custom, no value required: any non-empty value counts. This is the // OpsLog card case — the marker holds a timestamp, not a flag. {"custom any value", qso.QSO{Extras: map[string]string{"APP_OPSLOG_QSL_RCVD": "2026-08-09T01:00:00Z"}}, Def{ConfirmField: "APP_OPSLOG_QSL_RCVD"}, []string{"custom"}, true}, {"custom field empty", qso.QSO{Extras: map[string]string{"APP_OPSLOG_QSL_RCVD": ""}}, Def{ConfirmField: "APP_OPSLOG_QSL_RCVD"}, []string{"custom"}, false}, // Custom with an explicit value list, matched case-insensitively. {"custom value match", qso.QSO{Extras: map[string]string{"APP_CLUB_CONF": "v"}}, Def{ConfirmField: "APP_CLUB_CONF", ConfirmValue: "Y,V"}, []string{"custom"}, true}, {"custom value mismatch", qso.QSO{Extras: map[string]string{"APP_CLUB_CONF": "N"}}, Def{ConfirmField: "APP_CLUB_CONF", ConfirmValue: "Y,V"}, []string{"custom"}, false}, // A custom source naming no field must confirm NOTHING — never everything. {"custom without a field", qso.QSO{Extras: map[string]string{"APP_OPSLOG_QSL_RCVD": "x"}}, Def{}, []string{"custom"}, false}, // Known QSO fields resolve too, not just extras. {"custom on a known field", qso.QSO{State: "TX"}, Def{ConfirmField: "state", ConfirmValue: "TX"}, []string{"custom"}, true}, // Any source in the list is enough. {"first source misses, second hits", qso.QSO{EQSLRcvd: "Y"}, Def{}, []string{"lotw", "eqsl"}, true}, {"no source set", qso.QSO{LOTWRcvd: "Y"}, Def{}, nil, false}, } for _, c := range cases { if got := Confirmed(&c.q, c.d, c.sources); got != c.want { t.Errorf("%s: Confirmed = %v, want %v", c.name, got, c.want) } } } // HAMLOG.online is a named confirmation source, not something to express through // "custom": an award ticks it the way it ticks LoTW. func TestHamlogConfirmationSource(t *testing.T) { def := Def{Confirm: []string{"hamlog"}, Validate: []string{"hamlog"}} yes := func(extras map[string]string) bool { return Confirmed(&qso.QSO{Extras: extras}, def, def.Confirm) } if !yes(map[string]string{HamlogQSLKey: "Y"}) { t.Error("our own key did not confirm") } // Their export's field name is unpublished, so the plausible shapes count too. if !yes(map[string]string{"APP_HAMLOG_QSL": "20260823"}) { t.Error("a date in an alternative key did not confirm") } // A value is not a confirmation when it says no. if yes(map[string]string{HamlogQSLKey: "N"}) { t.Error(`"N" was taken as a confirmation`) } if yes(map[string]string{HamlogQSLKey: " "}) { t.Error("blank was taken as a confirmation") } if yes(nil) || yes(map[string]string{"SOMETHING_ELSE": "Y"}) { t.Error("confirmed with nothing to confirm it") } // And it does not leak into an award that never asked for it. other := Def{Confirm: []string{"lotw"}} if Confirmed(&qso.QSO{Extras: map[string]string{HamlogQSLKey: "Y"}}, other, other.Confirm) { t.Error("a hamlog confirmation counted for an award that only accepts LoTW") } } // The field name is theirs, taken from a real export: // // Y // // Pinned as a test because it was GUESSED wrong first — an import that carries // its confirmations into OpsLog with nothing to rename is the whole point, and // a renamed constant would break it in silence. func TestHamlogUsesTheSitesOwnFieldName(t *testing.T) { if HamlogQSLKey != "APP_HAMLOG_QSO_CFM" { t.Fatalf("HamlogQSLKey = %q — their export writes APP_HAMLOG_QSO_CFM", HamlogQSLKey) } def := Def{Confirm: []string{"hamlog"}} // The record as HAMLOG exports it. q := &qso.QSO{Extras: map[string]string{ "APP_HAMLOG_R150COUNTRY": "Russia", "APP_HAMLOG_QSO_CFM": "Y", }} if !Confirmed(q, def, def.Confirm) { t.Error("a real HAMLOG export did not confirm") } // Anything OpsLog stamped before that export existed keeps counting. old := &qso.QSO{Extras: map[string]string{"APP_OPSLOG_HAMLOG_QSL": "Y"}} if !Confirmed(old, def, def.Confirm) { t.Error("a QSO stamped with the older key stopped counting") } }