package adif import ( "bufio" "bytes" "strings" "testing" "time" "hamlog/internal/qso" ) // TestPromotedFieldsRoundTrip writes a QSO carrying the ADIF 3.1.7 promoted // fields, parses it back, and checks they survive — guarding the export // writeRecord ↔ import recordToQSO field-name mapping against typos. func TestPromotedFieldsRoundTrip(t *testing.T) { dist := 1234.5 rxp := 5.0 a := 12.0 in := qso.QSO{ Callsign: "EA8ABC", Band: "20m", Mode: "SSB", QSODate: time.Date(2026, 6, 6, 12, 0, 0, 0, time.UTC), SIG: "POTA", SIGInfo: "US-0001", MySIG: "WWFF", MySIGInfo: "ONFF-0001", WWFFRef: "ONFF-0001", MyWWFFRef: "F-FFF-0001", Distance: &dist, RXPower: &rxp, AIndex: &a, SKCC: "12345S", FISTS: "999", TenTen: "55555", ContactedOp: "EA8XYZ", EqCall: "EA8OLD", PFX: "EA8", MyName: "Greg", Class: "1A", DarcDOK: "A01", MyDarcDOK: "B02", Region: "IV", SilentKey: "N", SWL: "N", QSOComplete: "Y", QSORandom: "Y", CreditGranted: "DXCC", CreditSubmitted: "WAS", MyARRLSect: "EMA", MyVUCCGrids: "FN20,FN21", } var buf bytes.Buffer bw := bufio.NewWriter(&buf) bw.WriteString("\n") writeRecord(bw, in, true) bw.Flush() var rec Record if err := Parse(strings.NewReader(buf.String()), func(r Record) error { rec = r; return nil }); err != nil { t.Fatalf("parse: %v", err) } out, ok := recordToQSO(rec) if !ok { t.Fatal("recordToQSO returned !ok") } checks := map[string]struct{ got, want string }{ "SIG": {out.SIG, in.SIG}, "SIG_INFO": {out.SIGInfo, in.SIGInfo}, "MY_SIG": {out.MySIG, in.MySIG}, "MY_SIG_INFO": {out.MySIGInfo, in.MySIGInfo}, "WWFF_REF": {out.WWFFRef, in.WWFFRef}, "MY_WWFF_REF": {out.MyWWFFRef, in.MyWWFFRef}, "SKCC": {out.SKCC, in.SKCC}, "FISTS": {out.FISTS, in.FISTS}, "TEN_TEN": {out.TenTen, in.TenTen}, "CONTACTED_OP": {out.ContactedOp, in.ContactedOp}, "EQ_CALL": {out.EqCall, in.EqCall}, "PFX": {out.PFX, in.PFX}, "MY_NAME": {out.MyName, in.MyName}, "CLASS": {out.Class, in.Class}, "DARC_DOK": {out.DarcDOK, in.DarcDOK}, "MY_DARC_DOK": {out.MyDarcDOK, in.MyDarcDOK}, "REGION": {out.Region, in.Region}, "SILENT_KEY": {out.SilentKey, in.SilentKey}, "SWL": {out.SWL, in.SWL}, "QSO_COMPLETE": {out.QSOComplete, in.QSOComplete}, "QSO_RANDOM": {out.QSORandom, in.QSORandom}, "CREDIT_GRANTED": {out.CreditGranted, in.CreditGranted}, "CREDIT_SUBMITTED": {out.CreditSubmitted, in.CreditSubmitted}, "MY_ARRL_SECT": {out.MyARRLSect, in.MyARRLSect}, "MY_VUCC_GRIDS": {out.MyVUCCGrids, in.MyVUCCGrids}, } for tag, c := range checks { if c.got != c.want { t.Errorf("%s round-trip = %q, want %q", tag, c.got, c.want) } } if out.Distance == nil || *out.Distance != dist { t.Errorf("DISTANCE round-trip = %v, want %v", out.Distance, dist) } if out.RXPower == nil || *out.RXPower != rxp { t.Errorf("RX_PWR round-trip = %v, want %v", out.RXPower, rxp) } if out.AIndex == nil || *out.AIndex != a { t.Errorf("A_INDEX round-trip = %v, want %v", out.AIndex, a) } } // TestStandardExportDropsNonStandard verifies that standard mode strips // vendor/APP tags while full mode keeps them. func TestStandardExportDropsNonStandard(t *testing.T) { q := qso.QSO{ Callsign: "F4BPO", Band: "20m", Mode: "CW", Extras: map[string]string{ "APP_LOG4OM_FOO": "x", "DARC_DOK": "A01", // standard → kept in both "MY_VENDOR_TAG": "y", // non-standard → dropped in standard mode }, } standard := renderRecord(q, false) if strings.Contains(standard, "APP_LOG4OM_FOO") || strings.Contains(standard, "MY_VENDOR_TAG") { t.Errorf("standard export should drop non-standard tags:\n%s", standard) } full := renderRecord(q, true) if !strings.Contains(full, "APP_LOG4OM_FOO") || !strings.Contains(full, "MY_VENDOR_TAG") { t.Errorf("full export should keep all extras:\n%s", full) } } func renderRecord(q qso.QSO, includeApp bool) string { var buf bytes.Buffer bw := bufio.NewWriter(&buf) writeRecord(bw, q, includeApp) bw.Flush() return buf.String() }