165 lines
5.3 KiB
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)
|
|
}
|
|
}
|