Files
OpsLog/uls_lookup_test.go
T
rouggy 29591c5f0c feat: the offline FCC database answers during entry, not only at save
ULS was wired into AddQSO alone, so an operator who downloaded it and has no
QRZ.com/HamQTH account saw nothing while typing a US call: cty.dat gives the
country, the zones, and a 4-character grid that is the ENTITY centroid — a
thousand kilometres from the station. The county and the real square were stamped
after logging, too late to point an antenna with.

The enrichment runs last in the lookup wrapper and only fills blanks, so a
provider always wins. ULS carries no name and no address, so it completes a QRZ
record and can never replace one.

The grid needed a rule of its own. refineGrid keeps what is there unless the new
value extends it — correct for a QRZ square, wrong against an entity centroid,
which is simply a different square. A per-callsign FCC square therefore beats any
4-character grid, while a 6-character one already present is left untouched.
Lat/lon are recomputed only when we actually moved the grid.

Portable calls are skipped: W1AW/4 is not what the FCC licensed.
2026-07-28 22:10:22 +02:00

61 lines
2.0 KiB
Go

package main
import (
"testing"
"hamlog/internal/uls"
)
// The ULS grid rules, which are NOT the same as refineGrid's.
//
// cty.dat answers a US call with the ENTITY centroid — a 4-character square in
// the middle of the country. refineGrid keeps it (the ULS square is not an
// extension of it, it is a different square), which is right for a QRZ grid and
// wrong here: a per-callsign FCC square beats any entity centroid. But a
// 6-character grid already present came from a provider and must survive.
func TestULSGridPreference(t *testing.T) {
cases := []struct {
name string
have string // grid already on the result
uls string // grid from the FCC database
want string
}{
{"nothing known → take ULS", "", "EM12AB", "EM12AB"},
{"cty.dat entity centroid → ULS wins", "EN90", "EM12AB", "EM12AB"},
{"4-char square, same square → ULS extends it", "EM12", "EM12AB", "EM12AB"},
{"provider gave 6 chars → untouched", "FN31PR", "EM12AB", "FN31PR"},
{"ULS has only 4 chars, provider has 6 → untouched", "FN31PR", "EM12", "FN31PR"},
{"ULS empty → untouched", "EN90", "", "EN90"},
}
for _, c := range cases {
got := c.have
if g := refineGrid(got, c.uls); g != "" && g != got {
got = g
} else if len(got) <= 4 && len(c.uls) >= 6 {
got = c.uls
}
if got != c.want {
t.Errorf("%s: have=%q uls=%q → %q, want %q", c.name, c.have, c.uls, got, c.want)
}
}
}
// CNTY is what gets written to the QSO's county field, so its shape is part of
// the contract: ADIF wants "State,County" and nothing when either half is
// missing — a lone county name would be an invalid CNTY on export.
func TestULSCountyFormat(t *testing.T) {
cases := []struct {
loc uls.Location
want string
}{
{uls.Location{State: "MA", County: "Middlesex"}, "MA,Middlesex"},
{uls.Location{State: "", County: "Middlesex"}, ""},
{uls.Location{State: "MA", County: ""}, ""},
}
for _, c := range cases {
if got := c.loc.CNTY(); got != c.want {
t.Errorf("Location%+v.CNTY() = %q, want %q", c.loc, got, c.want)
}
}
}