chore: release v0.21.3
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package qso
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// openBulkTestDB builds the minimum of the qso table this needs. It does not go
|
||||
// through db.Open (that would pull the whole migration set and an import cycle);
|
||||
// the columns BulkSetExtra touches are extras_json and updated_at.
|
||||
func openBulkTestDB(t *testing.T) *sql.DB {
|
||||
t.Helper()
|
||||
conn, err := sql.Open("sqlite", "file:"+filepath.Join(t.TempDir(), "t.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { conn.Close() })
|
||||
if _, err := conn.Exec(`CREATE TABLE qso (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
callsign TEXT NOT NULL,
|
||||
extras_json TEXT,
|
||||
updated_at TEXT
|
||||
)`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func extras(t *testing.T, conn *sql.DB, id int64) map[string]any {
|
||||
t.Helper()
|
||||
var raw sql.NullString
|
||||
if err := conn.QueryRow(`SELECT extras_json FROM qso WHERE id = ?`, id).Scan(&raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !raw.Valid || raw.String == "" {
|
||||
return map[string]any{}
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(raw.String), &m); err != nil {
|
||||
t.Fatalf("extras_json is not valid JSON (%q): %v", raw.String, err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// OWNER_CALLSIGN has no promoted column, so bulk-editing it means merging a key
|
||||
// into extras_json. The thing that must not happen is collateral damage: the
|
||||
// other ADIF extras on the same QSO have to survive.
|
||||
func TestBulkSetExtraPreservesOtherExtras(t *testing.T) {
|
||||
conn := openBulkTestDB(t)
|
||||
r := &Repo{db: conn}
|
||||
ctx := context.Background()
|
||||
|
||||
// Two QSOs with existing extras, one with none at all (NULL column).
|
||||
conn.Exec(`INSERT INTO qso (callsign, extras_json) VALUES ('F5LIT', '{"SILENT_KEY":"Y","ANT_PATH":"S"}')`)
|
||||
conn.Exec(`INSERT INTO qso (callsign, extras_json) VALUES ('PA3EYF', '{"ANT_PATH":"L"}')`)
|
||||
conn.Exec(`INSERT INTO qso (callsign, extras_json) VALUES ('F4BPO', NULL)`)
|
||||
|
||||
n, err := r.BulkSetExtra(ctx, []int64{1, 2, 3}, "OWNER_CALLSIGN", "TM2Q")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("updated %d rows, want 3", n)
|
||||
}
|
||||
|
||||
e1 := extras(t, conn, 1)
|
||||
if e1["OWNER_CALLSIGN"] != "TM2Q" {
|
||||
t.Errorf("row 1 OWNER_CALLSIGN = %v, want TM2Q", e1["OWNER_CALLSIGN"])
|
||||
}
|
||||
if e1["SILENT_KEY"] != "Y" || e1["ANT_PATH"] != "S" {
|
||||
t.Errorf("row 1 lost its other extras: %v", e1)
|
||||
}
|
||||
// A NULL extras_json must become a valid object, not stay null or hold "null".
|
||||
if e3 := extras(t, conn, 3); e3["OWNER_CALLSIGN"] != "TM2Q" {
|
||||
t.Errorf("row 3 (extras_json was NULL) = %v, want OWNER_CALLSIGN=TM2Q", e3)
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing the field must REMOVE the key: a blank extra would otherwise be
|
||||
// carried into every ADIF export from then on.
|
||||
func TestBulkSetExtraEmptyRemovesKey(t *testing.T) {
|
||||
conn := openBulkTestDB(t)
|
||||
r := &Repo{db: conn}
|
||||
conn.Exec(`INSERT INTO qso (callsign, extras_json) VALUES ('F5LIT', '{"OWNER_CALLSIGN":"TM2Q","ANT_PATH":"S"}')`)
|
||||
|
||||
if _, err := r.BulkSetExtra(context.Background(), []int64{1}, "OWNER_CALLSIGN", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
e := extras(t, conn, 1)
|
||||
if _, present := e["OWNER_CALLSIGN"]; present {
|
||||
t.Errorf("OWNER_CALLSIGN should be gone, got %v", e)
|
||||
}
|
||||
if e["ANT_PATH"] != "S" {
|
||||
t.Errorf("clearing one extra removed another: %v", e)
|
||||
}
|
||||
}
|
||||
|
||||
// The frontend field id must resolve to the ADIF key, and nothing else must slip
|
||||
// through — this map is the whitelist guarding a spliced JSON path.
|
||||
func TestBulkExtraKeyWhitelist(t *testing.T) {
|
||||
if got := BulkExtraKey("owner_callsign"); got != "OWNER_CALLSIGN" {
|
||||
t.Errorf(`BulkExtraKey("owner_callsign") = %q, want "OWNER_CALLSIGN"`, got)
|
||||
}
|
||||
for _, bad := range []string{"", "callsign", "notes", "OWNER_CALLSIGN", "owner_callsign'"} {
|
||||
if got := BulkExtraKey(bad); got != "" {
|
||||
t.Errorf("BulkExtraKey(%q) = %q, want empty", bad, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user