Files
OpsLog/internal/adif/import_test.go
T
rouggy 4d5c5c3eb3 feat: move ADIF fields on import, for contest exports that misplace the exchange
An operator worked the RSGB IOTA contest in another logger and got back records
carrying <STATE:5>EU005: N1MM-class software stores the received exchange in the
column its contest module uses, not the one ADIF reserves. Imported verbatim,
every QSO gains a US-state field reading "EU005" and the IOTA award sees
nothing — the reference is in the log, just not where anything looks for it.

The import dialog gains optional source → destination rows, prefilled with
STATE → IOTA since that is the case that prompted it. Applied to the RAW record
before conversion, so it works for promoted columns and Extras alike and the
destination is parsed exactly as if the file had carried it there.

Two rules, both from the same principle that the file outranks our guess:
  - the source is CLEARED, so a reference does not linger in STATE where it
    would show as the contacted station's US state in the grid and every export;
  - a destination the file already filled is kept, judged against the record as
    it ARRIVED — otherwise the result would depend on Go's map iteration order,
    which is deliberately random.

The swap case in the test is what forced that second rule to be stated: "never
overwrite" and "exchange two fields" cannot both hold silently, so a destination
that is itself a mapping source is treated as an explicit swap and nothing else
is overwritten.
2026-07-30 14:57:05 +02:00

56 lines
2.1 KiB
Go

package adif
import "testing"
// Field mapping, from the RSGB IOTA contest case.
//
// N1MM and similar export the island reference in STATE, because that is the
// column their contest module uses for the received exchange. Imported
// verbatim, every QSO gets a US-state field reading "EU005" and the IOTA award
// sees nothing — the reference is in the log but not where anything looks.
func TestApplyFieldMap(t *testing.T) {
// The real record, from an operator's TM2Q log.
rec := Record{
"call": "2E0CVN", "band": "20m", "mode": "CW",
"state": "EU005", "srx_string": "001EU005",
}
applyFieldMap(rec, map[string]string{"state": "iota"})
if rec["iota"] != "EU005" {
t.Errorf("iota = %q, want EU005", rec["iota"])
}
// Cleared, not copied: a reference left behind in STATE would show up as the
// contacted station's US state in the grid and in every export.
if _, ok := rec["state"]; ok {
t.Errorf("state survived the move as %q", rec["state"])
}
// A destination the file already filled is never overwritten — the file's own
// tag outranks a guess about where the reference might be.
rec2 := Record{"call": "UT0RM", "state": "EU005", "iota": "EU030"}
applyFieldMap(rec2, map[string]string{"state": "iota"})
if rec2["iota"] != "EU030" {
t.Errorf("iota = %q — the file's own value must win", rec2["iota"])
}
// An empty source moves nothing (the second QSO of that log has no STATE).
rec3 := Record{"call": "UT0RM", "state": " "}
applyFieldMap(rec3, map[string]string{"state": "iota"})
if v, ok := rec3["iota"]; ok {
t.Errorf("an empty source created iota=%q", v)
}
// A swap reads both sources before writing either.
rec4 := Record{"state": "EU005", "iota": "NA001"}
applyFieldMap(rec4, map[string]string{"state": "iota", "iota": "state"})
if rec4["iota"] != "EU005" || rec4["state"] != "NA001" {
t.Errorf("swap gave iota=%q state=%q, want EU005 / NA001", rec4["iota"], rec4["state"])
}
// No mapping, no change.
rec5 := Record{"call": "F4BPO", "state": "EU005"}
applyFieldMap(rec5, nil)
if rec5["state"] != "EU005" {
t.Error("a nil mapping must leave the record alone")
}
}