Audit of every field a lookup returns against what OpsLog can store, prompted by a report that HamQTH was not fetching the email. The email was never broken: pinned now against a captured HamQTH answer, it parses and reaches the QSO. The reported callsign simply has no public address on HamQTH, which the empty field could not distinguish from a fault — so the "[email protected]" placeholder is gone. A greyed-out sample address in the one field an operator checks to see whether the lookup found one reads as a value. Real gaps found and closed: - web: the qso table has had the column since migration 0003 and no provider mapping ever read it. HamQTH sends <web>. - picture: Result.ImageURL was documented "QRZ only" because HamQTH's element is <picture>, not <image>. It was there all along. - zip: sent by both providers, read by neither. - adr_name: used only as a fallback when a record carries neither nick nor name. Deliberately NOT preferred over <nick> — a log wants the name the operator goes by on the air, "Igor", not "Igor Vladimirovich Getman". The parse tests use a real captured payload, including the stray <div> advert the server injects into its own XML.
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package lookup
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"testing"
|
|
)
|
|
|
|
// A real HamQTH answer, captured from the live API.
|
|
//
|
|
// Two things it settles. The email IS returned and must reach the QSO. And the
|
|
// element names are not the ones a reading of the documentation suggests:
|
|
// there is no <name> here at all, the full name is <adr_name>, and the picture
|
|
// is <picture> where QRZ calls it <image>. The stray <div> is an advert the
|
|
// server injects into its own XML; the parser has to shrug it off.
|
|
const hamqthEU1EU = `<?xml version="1.0"?>
|
|
<HamQTH xmlns="https://www.hamqth.com" version="2.8">
|
|
<div id="in-page-channel-node-id" data-channel-name="in_page_channel_Sux7_K"/>
|
|
<search>
|
|
<callsign>eu1eu</callsign>
|
|
<nick>Igor</nick>
|
|
<qth>Minsk-5</qth>
|
|
<country>Belarus</country>
|
|
<adif>27</adif>
|
|
<itu>29</itu>
|
|
<cq>16</cq>
|
|
<grid>KO33SV</grid>
|
|
<adr_name>Igor Vladimirovich Getman</adr_name>
|
|
<adr_street1>A/ya 143</adr_street1>
|
|
<adr_city>Minsk-5</adr_city>
|
|
<adr_zip>220005</adr_zip>
|
|
<adr_country>Belarus</adr_country>
|
|
<adr_adif>27</adr_adif>
|
|
<district>WAARB-LE</district>
|
|
<lotw>?</lotw>
|
|
<qsldirect>?</qsldirect>
|
|
<qsl>?</qsl>
|
|
<eqsl>Y</eqsl>
|
|
<email>[email protected]</email>
|
|
<latitude>53.88999938964844</latitude>
|
|
<longitude>27.59000015258789</longitude>
|
|
<continent>EU</continent>
|
|
<utc_offset>-2</utc_offset>
|
|
<picture>https://www.hamqth.com/images/default/ts-930_qith_old_radios.jpg</picture>
|
|
</search>
|
|
</HamQTH>`
|
|
|
|
func TestHamQTHParsesEveryUsefulField(t *testing.T) {
|
|
var root hamqthRoot
|
|
if err := xml.Unmarshal([]byte(hamqthEU1EU), &root); err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
s := root.Search
|
|
|
|
for _, c := range []struct{ name, got, want string }{
|
|
{"callsign", s.Callsign, "eu1eu"},
|
|
{"email", s.Email, "[email protected]"},
|
|
{"grid", s.Grid, "KO33SV"},
|
|
{"qth", s.QTH, "Minsk-5"},
|
|
{"street", s.AdrStreet1, "A/ya 143"},
|
|
{"city", s.AdrCity, "Minsk-5"},
|
|
{"country", s.AdrCountry, "Belarus"},
|
|
{"continent", s.Continent, "EU"},
|
|
{"dxcc", s.DXCC, "27"},
|
|
{"cq", s.CQ, "16"},
|
|
{"itu", s.ITU, "29"},
|
|
} {
|
|
if c.got != c.want {
|
|
t.Errorf("%s = %q, want %q", c.name, c.got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The fields the parser was blind to. Each is data OpsLog has somewhere to put.
|
|
func TestHamQTHParsesTheFieldsThatWereMissing(t *testing.T) {
|
|
var root hamqthRoot
|
|
if err := xml.Unmarshal([]byte(hamqthEU1EU), &root); err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
s := root.Search
|
|
|
|
// The postal name, the fallback when a record carries no nick and no name.
|
|
if s.AdrName != "Igor Vladimirovich Getman" {
|
|
t.Errorf("adr_name = %q", s.AdrName)
|
|
}
|
|
if s.LastName != "" {
|
|
t.Errorf("this record has no <name>; got %q", s.LastName)
|
|
}
|
|
// The profile picture. Result.ImageURL existed and said "QRZ only" — HamQTH
|
|
// sends one too, under a different element name.
|
|
if s.Picture == "" {
|
|
t.Error("picture not parsed")
|
|
}
|
|
if s.AdrZip != "220005" {
|
|
t.Errorf("adr_zip = %q", s.AdrZip)
|
|
}
|
|
}
|
|
|
|
// End to end: what the provider hands back must carry the email, the full name
|
|
// and the picture.
|
|
func TestHamQTHResultCarriesTheLot(t *testing.T) {
|
|
r, err := parseHamQTHSearch([]byte(hamqthEU1EU))
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
if r.Email != "[email protected]" {
|
|
t.Errorf("Email = %q — this is what was reported missing", r.Email)
|
|
}
|
|
// The nick, not the postal name: a log wants the name the operator goes by
|
|
// on the air. adr_name is only the fallback when there is nothing else.
|
|
if r.Name != "Igor" {
|
|
t.Errorf("Name = %q, want the on-air nick", r.Name)
|
|
}
|
|
if r.ImageURL == "" {
|
|
t.Error("ImageURL empty — HamQTH sent a picture")
|
|
}
|
|
if r.Grid != "KO33SV" {
|
|
t.Errorf("Grid = %q", r.Grid)
|
|
}
|
|
if r.Lat < 53.8 || r.Lat > 53.9 {
|
|
t.Errorf("Lat = %v", r.Lat)
|
|
}
|
|
if r.DXCC != 27 || r.CQZ != 16 || r.ITUZ != 29 {
|
|
t.Errorf("dxcc/cq/itu = %d/%d/%d", r.DXCC, r.CQZ, r.ITUZ)
|
|
}
|
|
if r.Address != "A/ya 143" {
|
|
t.Errorf("Address = %q", r.Address)
|
|
}
|
|
}
|