diff --git a/app.go b/app.go index 7945997..ec3c0d4 100644 --- a/app.go +++ b/app.go @@ -6162,6 +6162,12 @@ var bulkFieldColumns = map[string]string{ "iota": "iota", "sig": "sig", "sig_info": "sig_info", + // The contact itself — repair fields. Setting mode also clears submode, in + // qso.BulkSetField: a submode left over from the old mode contradicts the new. + "mode": "mode", + "submode": "submode", + "rst_sent": "rst_sent", + "rst_rcvd": "rst_rcvd", // Misc text "comment": "comment", "notes": "notes", diff --git a/bulkfields_test.go b/bulkfields_test.go new file mode 100644 index 0000000..7537fac --- /dev/null +++ b/bulkfields_test.go @@ -0,0 +1,35 @@ +package main + +import ( + "testing" + + "hamlog/internal/qso" +) + +// A bulk-editable field passes through THREE tables: the field list in the UI, +// bulkFieldColumns here, and bulkEditableCols in internal/qso. Mode and RST were +// added to the first and the third and not the second, so the dialog offered +// them and the save failed with "unknown field" — reported from the field. +// +// Nothing warns about that: each table is valid on its own. This is the check. +func TestEveryMappedBulkFieldIsWhitelisted(t *testing.T) { + for id, col := range bulkFieldColumns { + if !qso.BulkEditable(col) { + t.Errorf("bulk field %q maps to column %q, which internal/qso refuses to write", id, col) + } + } +} + +// And the reverse: a column the qso layer allows but nothing maps to is dead +// weight — it looks supported from the inside and cannot be reached from outside. +func TestNoUnreachableBulkColumn(t *testing.T) { + mapped := map[string]bool{} + for _, col := range bulkFieldColumns { + mapped[col] = true + } + for _, col := range qso.BulkEditableColumns() { + if !mapped[col] { + t.Errorf("column %q is bulk-writable but no field maps to it — unreachable", col) + } + } +} diff --git a/internal/qso/qso.go b/internal/qso/qso.go index e552db1..9dfe84d 100644 --- a/internal/qso/qso.go +++ b/internal/qso/qso.go @@ -896,6 +896,23 @@ var bulkEditableExtras = map[string]string{ // BulkExtraKey maps a frontend field id to its ADIF key in extras_json, or "". func BulkExtraKey(field string) string { return bulkEditableExtras[field] } +// BulkEditable reports whether a COLUMN may be bulk-written. Exported so the +// app layer can check its own field mapping against this whitelist: the two +// lists are separate, valid on their own, and a field present in one and absent +// from the other fails only when an operator tries to use it. +func BulkEditable(column string) bool { return bulkEditableCols[column] } + +// BulkEditableColumns lists every bulk-writable column, for the same check from +// the other side: a column nothing maps to looks supported and cannot be used. +func BulkEditableColumns() []string { + out := make([]string, 0, len(bulkEditableCols)) + for c := range bulkEditableCols { + out = append(out, c) + } + sort.Strings(out) + return out +} + // BulkSetExtra sets one whitelisted extras_json field on every listed QSO, // leaving the other extras untouched. An empty value REMOVES the key rather than // storing a blank — an empty extra would otherwise be carried into every export.