Files
OpsLog/internal/award/confirm_test.go
T
rouggy f4a72da118 fix(hamlog): read the site's own confirmation field, not a guessed one
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.
2026-08-23 13:43:10 +02:00

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")
}
}