85 lines
3.0 KiB
Go
85 lines
3.0 KiB
Go
package qso
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// LoTW never hands back the submode it was given: every digital contact comes
|
|
// back as the mode GROUP, "DATA". Matched on the exact mode string that is
|
|
// simply never equal, so an operator downloading their confirmations was told
|
|
// their own QSOs were not in their log — reported for FT2 contacts confirmed as
|
|
// DATA, and true of FT4 and FT8 alike.
|
|
func TestClassKeyMatchesAConfirmationCarryingTheModeGroup(t *testing.T) {
|
|
r := openRepo(t)
|
|
ctx := context.Background()
|
|
when := time.Date(2026, 8, 16, 16, 29, 0, 0, time.UTC)
|
|
id, err := r.Add(ctx, QSO{Callsign: "F1NQP", QSODate: when, Band: "20m", Mode: "FT2"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
minute := when.Format("2006-01-02T15:04")
|
|
|
|
exact, err := r.DedupeKeyIDs(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := exact[DedupeKey("F1NQP", minute, "20m", "DATA")]; found {
|
|
t.Fatal("the exact index matched DATA against FT2 — the test proves nothing")
|
|
}
|
|
|
|
byClass, err := r.DedupeClassKeyIDs(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, found := byClass[DedupeClassKey("F1NQP", minute, "20m", "DATA")]
|
|
if !found || got != id {
|
|
t.Errorf("class match = (%d, %v), want (%d, true) — the confirmation would be reported as having no local QSO", got, found, id)
|
|
}
|
|
}
|
|
|
|
// Two digital contacts with the same station, same band, same minute is barely
|
|
// physical — but if it happens, stamping the confirmation on whichever row the
|
|
// map happened to keep is a silent error in an award credit. Ambiguity maps to
|
|
// 0 so the caller reports it unmatched instead of guessing.
|
|
func TestAnAmbiguousClassKeyIsRefusedRatherThanGuessed(t *testing.T) {
|
|
r := openRepo(t)
|
|
ctx := context.Background()
|
|
when := time.Date(2026, 8, 16, 16, 29, 0, 0, time.UTC)
|
|
for _, m := range []string{"FT8", "RTTY"} {
|
|
if _, err := r.Add(ctx, QSO{Callsign: "F1NQP", QSODate: when, Band: "20m", Mode: m}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
byClass, err := r.DedupeClassKeyIDs(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := byClass[DedupeClassKey("F1NQP", when.Format("2006-01-02T15:04"), "20m", "DATA")]; got != 0 {
|
|
t.Errorf("ambiguous class key resolved to %d — one of two QSOs was picked at random", got)
|
|
}
|
|
}
|
|
|
|
// Phone and CW keep their own classes: a CW confirmation must never land on an
|
|
// SSB contact just because the rest of the key agrees.
|
|
func TestClassMatchDoesNotCrossPhoneAndCW(t *testing.T) {
|
|
r := openRepo(t)
|
|
ctx := context.Background()
|
|
when := time.Date(2026, 8, 16, 16, 29, 0, 0, time.UTC)
|
|
if _, err := r.Add(ctx, QSO{Callsign: "F1NQP", QSODate: when, Band: "20m", Mode: "SSB"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
byClass, err := r.DedupeClassKeyIDs(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
minute := when.Format("2006-01-02T15:04")
|
|
if _, found := byClass[DedupeClassKey("F1NQP", minute, "20m", "CW")]; found {
|
|
t.Error("a CW confirmation matched an SSB contact")
|
|
}
|
|
if _, found := byClass[DedupeClassKey("F1NQP", minute, "20m", "USB")]; !found {
|
|
t.Error("USB did not match the SSB contact — the phone sidebands are one class")
|
|
}
|
|
}
|