feat(05-02): add lagoon.Jsonable TEXT cast
- Scanner/Valuer JSON-text column preserving SQL NULL vs empty array - Generic struct with gorm-ignored inner fields so GORM does not walk Data
This commit is contained in:
106
lagoon/jsonable_test.go
Normal file
106
lagoon/jsonable_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package lagoon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonableStringSliceNullAndEmpty(t *testing.T) {
|
||||
var nullCol Jsonable[[]string]
|
||||
if err := nullCol.Scan(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if nullCol.Valid || nullCol.Data != nil {
|
||||
t.Fatalf("NULL scan: valid=%v data=%v", nullCol.Valid, nullCol.Data)
|
||||
}
|
||||
got, err := nullCol.Value()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != nil {
|
||||
t.Fatalf("NULL value = %#v, want nil", got)
|
||||
}
|
||||
|
||||
var emptyCol Jsonable[[]string]
|
||||
if err := emptyCol.Scan("[]"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !emptyCol.Valid {
|
||||
t.Fatal("[] must be valid")
|
||||
}
|
||||
if emptyCol.Data == nil || len(emptyCol.Data) != 0 {
|
||||
t.Fatalf("[] scan = %#v, want empty non-nil slice", emptyCol.Data)
|
||||
}
|
||||
got, err = emptyCol.Value()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "[]" {
|
||||
t.Fatalf("[] value = %#v, want %q", got, "[]")
|
||||
}
|
||||
|
||||
var populated Jsonable[[]string]
|
||||
if err := populated.Scan(`["https://img.discogs.com/a.jpg"]`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !populated.Valid || len(populated.Data) != 1 || populated.Data[0] != "https://img.discogs.com/a.jpg" {
|
||||
t.Fatalf("populated scan = %#v", populated.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonableTrackEntryRoundTrip(t *testing.T) {
|
||||
type TrackEntry = map[string]any
|
||||
src := `[{"title":"Smells Like Teen Spirit","position":"A1"}]`
|
||||
var col Jsonable[[]TrackEntry]
|
||||
if err := col.Scan(src); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !col.Valid || len(col.Data) != 1 {
|
||||
t.Fatalf("scan = %#v", col.Data)
|
||||
}
|
||||
title, _ := col.Data[0]["title"].(string)
|
||||
if title != "Smells Like Teen Spirit" {
|
||||
t.Fatalf("title = %#v", col.Data[0]["title"])
|
||||
}
|
||||
if _, isStringSlice := any(col.Data).([]string); isStringSlice {
|
||||
t.Fatal("tracklist must not collapse to []string")
|
||||
}
|
||||
got, err := col.Value()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, ok := got.(string)
|
||||
if !ok {
|
||||
t.Fatalf("Value() type %T, want string", got)
|
||||
}
|
||||
var round Jsonable[[]TrackEntry]
|
||||
if err := round.Scan(s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
title, _ = round.Data[0]["title"].(string)
|
||||
if title != "Smells Like Teen Spirit" {
|
||||
t.Fatalf("round-trip title = %#v", round.Data[0]["title"])
|
||||
}
|
||||
|
||||
var nilCol Jsonable[[]TrackEntry]
|
||||
if err := nilCol.Scan(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = nilCol.Value()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != nil {
|
||||
t.Fatalf("NULL tracklist value = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonableValueNeverNumeric(t *testing.T) {
|
||||
col := Jsonable[[]string]{Data: []string{"a"}, Valid: true}
|
||||
got, err := col.Value()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := got.(string); !ok && got != nil {
|
||||
t.Fatalf("Value() type %T, want string or nil", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user