A real record from their export settles it: <CALL:4>RL6M … <CNTY:5>RO-19 <APP_HAMLOG_R150COUNTRY:6>Russia <APP_HAMLOG_QSO_CFM:1>Y The confirmation lives in APP_HAMLOG_QSO_CFM. The four names guessed before a file was available — APP_HAMLOG_QSL and friends — were all wrong, which is the argument for reading one rather than reasoning about it. So that becomes the canonical key everywhere: the award source, the row colours, the grid column and the bulk editor. Importing a log downloaded from HAMLOG now carries its confirmations into OpsLog with nothing to rename. The older names, including the APP_OPSLOG_HAMLOG_QSL that OpsLog itself wrote in the meantime, are still honoured on read so nothing already stamped stops counting.
125 lines
4.9 KiB
Go
125 lines
4.9 KiB
Go
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:
|
|
//
|
|
// <APP_HAMLOG_QSO_CFM:1>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")
|
|
}
|
|
}
|