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