Files
gosqltypes/struct_json_test.go

165 lines
5.3 KiB
Go

package sqltypes
import (
"encoding/json"
"testing"
"time"
"github.com/google/uuid"
)
// record mimics a typical DB model composed of sqltypes fields, exercising
// marshalling/unmarshalling of the whole set together as encoding/json would
// when used on a real struct (not just the individual types in isolation).
type record struct {
ID SqlUUID `json:"id"`
Name SqlString `json:"name"`
Bio SqlString `json:"bio"`
Age SqlInt32 `json:"age"`
Score SqlFloat64 `json:"score"`
Active SqlBool `json:"active"`
CreatedAt SqlTimeStamp `json:"created_at"`
BirthDate SqlDate `json:"birth_date"`
Avatar SqlByteArray `json:"avatar"`
Tags SqlStringArray `json:"tags"`
Scores SqlInt32Array `json:"scores"`
Metadata SqlJSONB `json:"metadata"`
Embedding SqlVector `json:"embedding"`
Extras []uuid.UUID `json:"-"`
}
func TestStruct_JSON_RoundTrip_AllFieldsPresent(t *testing.T) {
id := uuid.New()
createdAt := time.Date(2024, 3, 10, 12, 30, 0, 0, time.UTC)
birthDate := time.Date(1990, 5, 20, 0, 0, 0, 0, time.UTC)
original := record{
ID: NewSqlUUID(id),
Name: NewSqlString("Ada Lovelace"),
Bio: SqlString{}, // intentionally null
Age: NewSqlInt32(36),
Score: NewSqlFloat64(98.6),
Active: NewSqlBool(true),
CreatedAt: NewSqlTimeStamp(createdAt),
BirthDate: NewSqlDate(birthDate),
Avatar: NewSqlByteArray([]byte{0xDE, 0xAD, 0xBE, 0xEF}),
Tags: NewSqlStringArray([]string{"engineer", "mathematician"}),
Scores: NewSqlInt32Array([]int32{10, 20, 30}),
Metadata: SqlJSONB(`{"role":"admin"}`),
Embedding: NewSqlVector([]float32{0.1, 0.2, 0.3}),
}
data, err := json.Marshal(original)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded record
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.ID.UUID() != id {
t.Errorf("ID: expected %v, got %v", id, decoded.ID.UUID())
}
if decoded.Name.String() != "Ada Lovelace" {
t.Errorf("Name: expected Ada Lovelace, got %q", decoded.Name.String())
}
if decoded.Bio.Valid {
t.Errorf("Bio: expected invalid/null, got %v", decoded.Bio)
}
if decoded.Age.Int64() != 36 {
t.Errorf("Age: expected 36, got %d", decoded.Age.Int64())
}
if decoded.Score.Float64() != 98.6 {
t.Errorf("Score: expected 98.6, got %v", decoded.Score.Float64())
}
if !decoded.Active.Bool() {
t.Errorf("Active: expected true")
}
if decoded.CreatedAt.Time().Format("2006-01-02T15:04:05") != createdAt.Format("2006-01-02T15:04:05") {
t.Errorf("CreatedAt: expected %v, got %v", createdAt, decoded.CreatedAt.Time())
}
if decoded.BirthDate.String() != "1990-05-20" {
t.Errorf("BirthDate: expected 1990-05-20, got %q", decoded.BirthDate.String())
}
if string(decoded.Avatar.Val) != string(original.Avatar.Val) {
t.Errorf("Avatar: expected %v, got %v", original.Avatar.Val, decoded.Avatar.Val)
}
if len(decoded.Tags.Val) != 2 || decoded.Tags.Val[0] != "engineer" {
t.Errorf("Tags: unexpected value %v", decoded.Tags.Val)
}
if len(decoded.Scores.Val) != 3 || decoded.Scores.Val[2] != 30 {
t.Errorf("Scores: unexpected value %v", decoded.Scores.Val)
}
m, err := decoded.Metadata.AsMap()
if err != nil || m["role"] != "admin" {
t.Errorf("Metadata: expected role=admin, got %v (err=%v)", m, err)
}
if len(decoded.Embedding.Val) != 3 || decoded.Embedding.Val[1] != 0.2 {
t.Errorf("Embedding: unexpected value %v", decoded.Embedding.Val)
}
}
func TestStruct_JSON_RoundTrip_AllNull(t *testing.T) {
var original record
data, err := json.Marshal(original)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var decoded record
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.ID.Valid || decoded.Name.Valid || decoded.Age.Valid || decoded.Score.Valid ||
decoded.Active.Valid || decoded.CreatedAt.Valid || decoded.BirthDate.Valid ||
decoded.Avatar.Valid || decoded.Tags.Valid || decoded.Scores.Valid || decoded.Embedding.Valid {
t.Errorf("expected all fields invalid/null after round-trip, got %+v", decoded)
}
if decoded.Metadata != nil {
t.Errorf("expected nil Metadata, got %v", decoded.Metadata)
}
}
func TestStruct_JSON_UnmarshalFromRawJSON(t *testing.T) {
raw := `{
"id": "3e843d4e-6b3c-4f2e-9a1e-6f0b2f3c9d10",
"name": "Grace Hopper",
"bio": null,
"age": 85,
"score": 100,
"active": false,
"created_at": "2023-12-01T08:00:00",
"birth_date": "1906-12-09",
"avatar": "AQIDBA==",
"tags": ["navy", "compiler"],
"scores": [1, 2, 3],
"metadata": {"key": "value"},
"embedding": [1.0, 2.0]
}`
var decoded record
if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if decoded.Name.String() != "Grace Hopper" {
t.Errorf("expected Grace Hopper, got %q", decoded.Name.String())
}
if decoded.Age.Int64() != 85 {
t.Errorf("expected age 85, got %d", decoded.Age.Int64())
}
if decoded.Active.Valid && decoded.Active.Bool() {
t.Errorf("expected active=false")
}
if string(decoded.Avatar.Val) != string([]byte{1, 2, 3, 4}) {
t.Errorf("expected avatar bytes [1 2 3 4], got %v", decoded.Avatar.Val)
}
if len(decoded.Tags.Val) != 2 || decoded.Tags.Val[1] != "compiler" {
t.Errorf("unexpected tags %v", decoded.Tags.Val)
}
}