218 lines
6.6 KiB
Go
218 lines
6.6 KiB
Go
package dxcc
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const sampleCty = `Sov Mil Order of Malta: 15: 28: EU: 41.90: -12.43: -1.0: 1A:
|
|
1A;
|
|
Monaco: 14: 27: EU: 43.73: -7.40: -1.0: 3A:
|
|
3A;
|
|
France: 14: 27: EU: 46.00: -2.00: -1.0: F:
|
|
F,HW,HX,HY,TH,TM,TO,TP,TQ,TV,TX;
|
|
Germany: 14: 28: EU: 51.00: -10.00: -1.0: DL:
|
|
DA,DB,DC,DD,DE,DF,DG,DH,DI,DJ,DK,DL,DM,DN,DO,DP,DQ,DR;
|
|
United States: 05: 08: NA: 37.53: 91.67: 5.0: K:
|
|
=W1AW(5)[7],K,N,W,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK;
|
|
`
|
|
|
|
func TestLookup(t *testing.T) {
|
|
db, err := Load(strings.NewReader(sampleCty))
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
cases := []struct {
|
|
call string
|
|
wantEnt string
|
|
}{
|
|
{"F4NIE", "France"},
|
|
{"F4BPO", "France"},
|
|
{"F4BPO/P", "France"},
|
|
{"DL/F4NIE", "Germany"},
|
|
{"DL5XYZ", "Germany"},
|
|
{"K1ABC", "United States"},
|
|
{"N0CALL", "United States"},
|
|
{"3A2MD", "Monaco"},
|
|
{"W1AW", "United States"}, // exact match wins
|
|
}
|
|
for _, c := range cases {
|
|
m, ok := db.Lookup(c.call)
|
|
if !ok {
|
|
t.Errorf("%s: no match", c.call)
|
|
continue
|
|
}
|
|
if m.Entity.Name != c.wantEnt {
|
|
t.Errorf("%s: got %q, want %q", c.call, m.Entity.Name, c.wantEnt)
|
|
}
|
|
}
|
|
|
|
// W1AW exact match has CQ override 5 and ITU override 7.
|
|
m, _ := db.Lookup("W1AW")
|
|
if m.CQZone != 5 || m.ITUZone != 7 {
|
|
t.Errorf("W1AW overrides: got CQ=%d ITU=%d, want 5/7", m.CQZone, m.ITUZone)
|
|
}
|
|
}
|
|
|
|
// A "/N" call-area suffix can change the DXCC entity: HD5MW/8 re-homes to the
|
|
// HD8 area (Galápagos), not the base call's Ecuador.
|
|
func TestCallAreaSuffix(t *testing.T) {
|
|
const cty = `Ecuador: 10: 12: SA: -1.40: 78.40: 5.0: HC:
|
|
HC,HD;
|
|
Galapagos Islands: 10: 12: SA: 0.00: 91.00: 6.0: HC8:
|
|
HC8,HD8;
|
|
`
|
|
db, err := Load(strings.NewReader(cty))
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
cases := map[string]string{
|
|
"HD5MW": "Ecuador",
|
|
"HD5MW/8": "Galapagos Islands",
|
|
"HC2AO": "Ecuador",
|
|
"HD8M": "Galapagos Islands",
|
|
"HC1WW/8": "Galapagos Islands",
|
|
}
|
|
for call, want := range cases {
|
|
m, ok := db.Lookup(call)
|
|
if !ok {
|
|
t.Errorf("%s: no match", call)
|
|
continue
|
|
}
|
|
if m.Entity.Name != want {
|
|
t.Errorf("%s: got %q, want %q", call, m.Entity.Name, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// US/VK call-district zone refinement (W6 = CQ3/ITU6, not the entity default).
|
|
func TestZoneByCallDistrict(t *testing.T) {
|
|
cases := []struct {
|
|
adif int
|
|
call string
|
|
cqz, ituz int
|
|
ok bool
|
|
}{
|
|
{291, "W6XYZ", 3, 6, true},
|
|
{291, "K7AB", 3, 6, true},
|
|
{291, "W4ABC", 5, 8, true},
|
|
{291, "N0CALL", 4, 7, true},
|
|
{291, "AA5XX", 4, 7, true},
|
|
{150, "VK6AA", 29, 58, true}, // West Australia
|
|
{150, "VK3XY", 30, 59, true}, // Victoria
|
|
{230, "DL1ABC", 0, 0, false}, // Germany: no district rule
|
|
{291, "WABC", 0, 0, false}, // no digit
|
|
}
|
|
for _, c := range cases {
|
|
cqz, ituz, ok := ZoneByCallDistrict(c.adif, c.call)
|
|
if ok != c.ok || (ok && (cqz != c.cqz || ituz != c.ituz)) {
|
|
t.Errorf("ZoneByCallDistrict(%d,%q) = %d/%d ok=%v, want %d/%d ok=%v",
|
|
c.adif, c.call, cqz, ituz, ok, c.cqz, c.ituz, c.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
// KG4 is Guantanamo Bay only with a 2-character suffix (KG4XX); 1- or 3-char
|
|
// suffixes (KG4W, KG4ABC) are continental USA. cty.dat carries a bare "KG4"
|
|
// prefix, so the resolver must apply the suffix-length rule.
|
|
func TestKG4SuffixRule(t *testing.T) {
|
|
const cty = `United States: 05: 08: NA: 37.53: 91.67: 5.0: K:
|
|
K,N,W;
|
|
Guantanamo Bay: 08: 11: NA: 19.92: 75.18: -5.0: KG4:
|
|
KG4;
|
|
`
|
|
db, err := Load(strings.NewReader(cty))
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
cases := map[string]string{
|
|
"KG4W": "United States", // 1-char suffix
|
|
"KG4AA": "Guantanamo Bay", // 2-char suffix
|
|
"KG4ABC": "United States", // 3-char suffix
|
|
"KG4": "United States", // no suffix
|
|
"KG4W/P": "United States", // modifier stripped, still 1-char
|
|
}
|
|
for call, want := range cases {
|
|
m, ok := db.Lookup(call)
|
|
if !ok {
|
|
t.Errorf("%s: no match", call)
|
|
continue
|
|
}
|
|
if m.Entity.Name != want {
|
|
t.Errorf("%s: got %q, want %q", call, m.Entity.Name, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// cty.dat marks non-DXCC entities (Sicily *IT9, African Italy *IG9) with a
|
|
// leading '*'; the parser must fold those into their parent DXCC entity
|
|
// "Italy" while leaving real DXCC entities — Sardinia (IS0), no '*' — alone.
|
|
func TestCanonicalEntityNames(t *testing.T) {
|
|
const cty = `Italy: 15: 28: EU: 42.82: -12.58: -1.0: I:
|
|
I,IK,IZ;
|
|
African Italy: 33: 37: AF: 35.67: -12.67: -1.0: *IG9:
|
|
IG9,IH9;
|
|
Sardinia: 15: 28: EU: 40.15: -9.27: -1.0: IS0:
|
|
IM0,IS,IW0U,IW0V;
|
|
Sicily: 15: 28: EU: 37.50: -14.00: -1.0: *IT9:
|
|
IT9,IW9;
|
|
`
|
|
db, err := Load(strings.NewReader(cty))
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
cases := map[string]string{
|
|
"IW9EZO": "Italy", // Sicily (*IT9) → Italy
|
|
"IT9CLY": "Italy",
|
|
"IG9A": "Italy", // African Italy (*IG9) → Italy
|
|
"IK0ABC": "Italy",
|
|
"IS0XYZ": "Sardinia", // real DXCC entity — must stay Sardinia
|
|
"IM0ABC": "Sardinia",
|
|
}
|
|
for call, want := range cases {
|
|
m, ok := db.Lookup(call)
|
|
if !ok {
|
|
t.Errorf("%s: no match", call)
|
|
continue
|
|
}
|
|
if m.Entity.Name != want {
|
|
t.Errorf("%s: country = %q, want %q", call, m.Entity.Name, want)
|
|
}
|
|
}
|
|
// African Italy keeps its own zones/continent even though it reports
|
|
// Italy as the entity.
|
|
if m, _ := db.Lookup("IG9A"); m.CQZone != 33 || m.Continent != "AF" {
|
|
t.Errorf("IG9A: got CQ=%d cont=%s, want 33/AF", m.CQZone, m.Continent)
|
|
}
|
|
if EntityDXCC("Sicily") != 248 {
|
|
t.Errorf("EntityDXCC(Sicily) = %d, want 248 (Italy)", EntityDXCC("Sicily"))
|
|
}
|
|
if EntityDXCC("Sardinia") != 225 {
|
|
t.Errorf("EntityDXCC(Sardinia) = %d, want 225", EntityDXCC("Sardinia"))
|
|
}
|
|
}
|
|
|
|
func TestNormalize(t *testing.T) {
|
|
cases := map[string]string{
|
|
"F4BPO": "F4BPO",
|
|
"f4bpo": "F4BPO",
|
|
" F4BPO ": "F4BPO",
|
|
"F4BPO/P": "F4BPO",
|
|
"F4BPO/MM": "", // maritime mobile → no DXCC entity
|
|
"F4BPO/AM": "", // aeronautical mobile → no DXCC entity
|
|
"F4BPO/M": "F4BPO", // plain mobile keeps the home entity
|
|
"F4BPO/5": "F5BPO", // "/5" re-homes to call area 5
|
|
"HD5MW/8": "HD8MW", // "/8" → Galápagos call area (HD8)
|
|
"DL/F4BPO": "DL",
|
|
"MM/KA9P": "MM", // leading MM = Scotland operating prefix
|
|
"MM/LY3X/P": "MM",
|
|
"F4BPO/W6": "W6",
|
|
"VK9/F4BPO": "VK9",
|
|
}
|
|
for in, want := range cases {
|
|
if got := normalizeCallsign(in); got != want {
|
|
t.Errorf("normalize(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|