Files
OpsLog/unconfband_test.go
T
rouggy f39bda110a fix(chase): the unconfirmed flag survives the next QSO
Reported as 'South Africa shows NEW BAND although I have worked 15m'.
The verdict was right — his hunt counts only LoTW and paper QSL, and no
15m contact is confirmed by either — but the badge drew SOLID, saying
'never worked here' about a band with four contacts in the log.

Proved with his own data: the confirmed ledger holds 10/12/17/20/30/40/80
and not 15, the all-QSO ledger holds 15m FT8 four times, and the backend
sets UnconfStatus correctly for exactly that case (new test, end to end
through ClusterSpotStatuses).

The flag died in the frontend. FOUR hand-written copies of the same
backend-result mapping, and they had drifted: the two that RE-fetch —
after a QSO is logged, and when a pane becomes visible — rebuilt each
entry without the unconf_* flags, so the first refresh after any contact
turned every dimmed badge solid for good. None of the four ever copied
grid_state either, so a known-but-unconfirmed square never dimmed at all.
One mapper now, used by all four.
2026-09-01 11:56:20 +02:00

75 lines
2.5 KiB
Go

package main
import (
"testing"
"time"
"hamlog/internal/qso"
)
// The operator's report: South Africa worked on 15m FT8 but confirmed only by
// eQSL/Club Log/QRZ, with the chase set to "new + unconfirmed" over LoTW and
// paper QSL. The verdict must be NEW BAND — that is what the settings ask for —
// and it must be flagged UNCONFIRMED, so the badge reads "worked, QSL missing"
// rather than "never worked here".
func TestUnconfirmedBandIsFlagged(t *testing.T) {
a := syncTestApp(t)
hz := int64(21074000)
add := func(call, band, mode string, lotw, eqsl string, dxcc int) {
q := qso.QSO{
Callsign: call, Band: band, Mode: mode,
QSODate: time.Now().UTC().Add(-48 * time.Hour),
FreqHz: &hz, DXCC: &dxcc, Country: "South Africa",
LOTWRcvd: lotw, EQSLRcvd: eqsl,
}
if _, err := a.qso.Add(a.ctx, q); err != nil {
t.Fatalf("add %s: %v", call, err)
}
}
// 20m is LoTW-confirmed, so the ENTITY is confirmed…
add("ZS1AAA", "20m", "FT8", "Y", "Y", 462)
// …but every 15m contact is confirmed only by eQSL, which this operator
// does not count.
add("ZS6GAV", "15m", "FT8", "N", "Y", 462)
add("ZS4AW", "15m", "FT8", "N", "N", 462)
pred := qso.ConfirmSourcesPredicate([]string{"lotw", "card"})
all, err := a.qso.EntitySlotMapPred(a.ctx, func(_ string, dx int, _ string) int { return dx }, nil, "")
if err != nil {
t.Fatalf("all ledger: %v", err)
}
conf, err := a.qso.EntitySlotMapPred(a.ctx, func(_ string, dx int, _ string) int { return dx }, nil, pred)
if err != nil {
t.Fatalf("confirmed ledger: %v", err)
}
if _, ok := all[462]; !ok {
t.Fatal("the all-QSO ledger has no South Africa at all")
}
if _, ok := all[462].Bands["15m"]; !ok {
t.Error("all-QSO ledger: 15m missing — the dimming test can never fire")
}
if _, ok := all[462].Slots["15m"]["FT8"]; !ok {
t.Error("all-QSO ledger: 15m/FT8 slot missing")
}
c, ok := conf[462]
if !ok {
t.Fatal("confirmed ledger: South Africa missing — the 20m LoTW QSO should put it there")
}
if _, ok := c.Bands["15m"]; ok {
t.Error("confirmed ledger has 15m, but no 15m QSO is confirmed by LoTW or card")
}
// The two halves the UI shows.
status := spotEntityStatus(true, false, true, false, "FT8")
if status != "new-band" {
t.Errorf("status = %q, want new-band", status)
}
_, bAll := all[462].Bands["15m"]
_, mAll := all[462].Modes["FT8"]
_, sAll := all[462].Slots["15m"]["FT8"]
if got := spotEntityStatus(true, bAll, mAll, sAll, "FT8"); got != "worked" {
t.Errorf("all-ledger verdict = %q, want worked (→ the badge should be dimmed)", got)
}
}