golib/database/csvx/null_test.go
AJ ONeal 59fae2e7ae
fix(csvx): read human null words as absent, unrecognized values as false
BoolishTimestamp defaulted unrecognized non-empty values to true: any
word it does not understand (a misspelling, "disabled", "banana")
behaved as on. Unrecognized values are now valid+false with the error
retained so callers can apply their own policy, and "disabled"/"disable"
are no longer true-like.

Both bool types now read the human "not supplied" set through one shared
isBoolNull sentinel: empty/whitespace-only, the exact PG dump sentinel
\N (case-sensitive), and the case-insensitive words null, \null, none,
unknown. Bool previously reached absent only by ParseBool failing; it
now matches BoolishTimestamp explicitly. Free-text types must not reuse
this set: most of these words are legitimate values there.
2026-10-23 23:00:57 -06:00

55 lines
1.7 KiB
Go

package csvx
import "testing"
func TestIsBoolNull(t *testing.T) {
for _, null := range []string{"", " ", "null", "NULL", "Null", " null ", `\null`, `\NULL`, `\N`, "none", "None", "unknown", "UNKNOWN"} {
if !isBoolNull(null) {
t.Errorf("isBoolNull(%q) = false, want true", null)
}
}
// Everything else is a value, not a null.
for _, value := range []string{`\n`, `N`, `N\`, "none of the above", "nullable", "no", "off", "na", "disable", "disabled", "NULLITY"} {
if isBoolNull(value) {
t.Errorf("isBoolNull(%q) = true, want false", value)
}
}
}
func TestBoolCSVNullSentinels(t *testing.T) {
// Bool must read the same null sentinels as BoolishTimestamp (isBoolNull)
// explicitly rather than relying on ParseBool failing: the PG dump
// sentinel and a representative word.
for _, null := range []string{`\N`, "none"} {
var b Bool
if err := b.UnmarshalCSV([]byte(null)); err != nil {
t.Fatalf("UnmarshalCSV(%q): %v", null, err)
}
if b.Valid || b.Bool {
t.Errorf("UnmarshalCSV(%q) = %+v, want absent", null, b)
}
var b2 Bool
if err := b2.Scan(null); err != nil {
t.Fatalf("Scan(%q): %v", null, err)
}
if b2.Valid || b2.Bool {
t.Errorf("Scan(%q) = %+v, want absent", null, b2)
}
}
// `\n` (lowercase) is not the sentinel: it parses as unrecognized.
var b Bool
if err := b.Scan(`\n`); err != nil {
t.Fatalf("Scan: %v", err)
}
if b.Valid || b.Bool {
t.Errorf(`Scan(\n) = %+v, want absent`, b)
}
// Real values still parse; a non-sentinel word is unrecognized.
if err := b.UnmarshalCSV([]byte("true")); err != nil {
t.Fatalf("UnmarshalCSV(true): %v", err)
}
if !b.Valid || !b.Bool {
t.Errorf("UnmarshalCSV(true) = %+v", b)
}
}