Files
OpsLog/unconfrepro_test.go
T
rouggy 4c4b3b6c2d feat(flex): send SmartSDR's spot priority; fix(chase): the same station on the same slot has nothing left to give
The panadapter has finite room: spots close in frequency are stacked
behind a '+' and only one is drawn, chosen by PRIORITY — a parameter
'spot add' accepts and OpsLog never sent. So a new entity sat invisible
behind three stations already in the log. DXHunter has sent it for
years; the tiers here are the same idea in the operator's own words:
the entity never worked (with my own callsign, for a multi-op), then
band/mode/slot, then the reference hunts, then everything else.

And a callsign already worked on this exact band and mode stops
advertising a need. Working it again cannot turn a missing QSL into a
confirmation — the QSO is already there — so shouting NEW DXCC over a
station worked an hour ago only teaches an operator to distrust the
colour. The need is real and stays on every OTHER station of the entity,
which is where it can be answered. Applied on both paths out of the
verdict, including the early one that leaves the loop first.
2026-09-01 23:33:56 +02:00

115 lines
4.0 KiB
Go

package main
import (
"context"
"path/filepath"
"testing"
"time"
"hamlog/internal/db"
"hamlog/internal/dxcc"
"hamlog/internal/qso"
"hamlog/internal/settings"
)
// The operator's exact case, end to end through the real verdict path.
//
// South Africa is confirmed (LoTW) on 20m and worked-but-unconfirmed on 15m,
// with the hunt set to "new + unconfirmed" over LoTW and paper QSL. A 15m FT8
// decode must come back NEW BAND — that is what the settings ask — and carry
// the UNCONFIRMED flag, which is what draws the badge as a missing QSL rather
// than a band never worked.
func TestClusterStatusFlagsUnconfirmedBand(t *testing.T) {
dir := t.TempDir()
conn, err := db.Open(filepath.Join(dir, "log.db"))
if err != nil {
t.Fatalf("open: %v", err)
}
t.Cleanup(func() { conn.Close() })
a := &App{ctx: context.Background(), qso: qso.NewRepo(conn), settings: settings.NewStore(conn)}
a.settingsScoped.Store(true)
if err := a.settings.Set(a.ctx, keyChaseMode, "new_unconfirmed"); err != nil {
t.Fatalf("set chase mode: %v", err)
}
if err := a.settings.Set(a.ctx, keyChaseConfirm, "lotw,card"); err != nil {
t.Fatalf("set chase sources: %v", err)
}
// The real prefix table, so ZS4AW resolves the way it does in the app.
a.dxcc = dxcc.NewManager(filepath.Join("build", "bin", "data"))
if err := a.dxcc.LoadFromDisk(); err != nil {
t.Skipf("cty.dat not available here: %v", err)
}
za := 462
add := func(call, band, mode, lotw string) {
hz := int64(21074000)
if _, err := a.qso.Add(a.ctx, qso.QSO{
Callsign: call, Band: band, Mode: mode, FreqHz: &hz,
QSODate: time.Now().UTC().Add(-72 * time.Hour),
DXCC: &za, Country: "South Africa", LOTWRcvd: lotw,
}); err != nil {
t.Fatalf("add %s: %v", call, err)
}
}
add("ZS1AAA", "20m", "FT8", "Y") // the entity IS confirmed, elsewhere
add("ZS6GAV", "15m", "FT8", "N") // 15m worked, never confirmed
add("ZS4AW", "15m", "FT8", "N")
// A station of the entity that is NOT the one already worked on this slot:
// that case has a rule of its own, tested below.
got := a.ClusterSpotStatuses([]SpotQuery{{Call: "ZS9XX", Band: "15m", Mode: "FT8"}})
if len(got) != 1 {
t.Fatalf("got %d results", len(got))
}
if got[0].Status != "new-band" {
t.Fatalf("status = %q, want new-band", got[0].Status)
}
if !got[0].UnconfStatus {
t.Error("UnconfStatus is false — the badge draws solid, as if 15m had never been worked")
}
}
// The same station, already worked on the same band and mode, has nothing left
// to give: working it again cannot turn a missing QSL into a confirmation. The
// need belongs to the entity, not to that callsign, so the badge goes quiet on
// it — and stays on every other station of the entity.
func TestWorkedSameSlotDropsTheUnconfirmedBadge(t *testing.T) {
dir := t.TempDir()
conn, err := db.Open(filepath.Join(dir, "log.db"))
if err != nil {
t.Fatalf("open: %v", err)
}
t.Cleanup(func() { conn.Close() })
a := &App{ctx: context.Background(), qso: qso.NewRepo(conn), settings: settings.NewStore(conn)}
a.settingsScoped.Store(true)
_ = a.settings.Set(a.ctx, keyChaseMode, "new_unconfirmed")
_ = a.settings.Set(a.ctx, keyChaseConfirm, "lotw,card")
a.dxcc = dxcc.NewManager(filepath.Join("build", "bin", "data"))
if err := a.dxcc.LoadFromDisk(); err != nil {
t.Skipf("cty.dat not available here: %v", err)
}
hz := int64(10136000)
if _, err := a.qso.Add(a.ctx, qso.QSO{
Callsign: "TN8GD", Band: "30m", Mode: "FT8", FreqHz: &hz,
QSODate: time.Now().UTC().Add(-24 * time.Hour),
}); err != nil {
t.Fatalf("add: %v", err)
}
got := a.ClusterSpotStatuses([]SpotQuery{
{Call: "TN8GD", Band: "30m", Mode: "FT8"}, // the very station worked
{Call: "TN4XY", Band: "30m", Mode: "FT8"}, // another in the same entity
})
if got[0].Status == "new" || got[0].UnconfStatus {
t.Errorf("the worked station still advertises a need: status=%q unconf=%v",
got[0].Status, got[0].UnconfStatus)
}
if got[1].Status != "new" {
t.Errorf("another station in the entity lost its badge: status=%q", got[1].Status)
}
}