package sqltypes import ( "database/sql/driver" "encoding/json" "testing" "time" "github.com/google/uuid" ) // TestNewSqlInt16 tests NewSqlInt16 type func TestNewSqlInt16(t *testing.T) { tests := []struct { name string input interface{} expected SqlInt16 }{ {"int", 42, Null(int16(42), true)}, {"int32", int32(100), NewSqlInt16(100)}, {"int64", int64(200), NewSqlInt16(200)}, {"string", "123", NewSqlInt16(123)}, {"nil", nil, Null(int16(0), false)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var n SqlInt16 if err := n.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if n != tt.expected { t.Errorf("expected %v, got %v", tt.expected, n) } }) } } func TestNewSqlInt16_Value(t *testing.T) { tests := []struct { name string input SqlInt16 expected driver.Value }{ {"zero", Null(int16(0), false), nil}, {"positive", NewSqlInt16(42), int16(42)}, {"negative", NewSqlInt16(-10), int16(-10)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { val, err := tt.input.Value() if err != nil { t.Fatalf("Value failed: %v", err) } if val != tt.expected { t.Errorf("expected %v, got %v", tt.expected, val) } }) } } func TestNewSqlInt16_JSON(t *testing.T) { n := NewSqlInt16(42) // Marshal data, err := json.Marshal(n) if err != nil { t.Fatalf("Marshal failed: %v", err) } expected := "42" if string(data) != expected { t.Errorf("expected %s, got %s", expected, string(data)) } // Unmarshal var n2 SqlInt16 if err := json.Unmarshal([]byte("123"), &n2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if n2.Int64() != 123 { t.Errorf("expected 123, got %d", n2.Int64()) } } // TestNewSqlInt64 tests NewSqlInt64 type func TestNewSqlInt64(t *testing.T) { tests := []struct { name string input interface{} expected SqlInt64 }{ {"int", 42, NewSqlInt64(42)}, {"int32", int32(100), NewSqlInt64(100)}, {"int64", int64(9223372036854775807), NewSqlInt64(9223372036854775807)}, {"uint32", uint32(100), NewSqlInt64(100)}, {"uint64", uint64(200), NewSqlInt64(200)}, {"nil", nil, SqlInt64{}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var n SqlInt64 if err := n.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if n != tt.expected { t.Errorf("expected %v, got %v", tt.expected, n) } }) } } // TestSqlFloat64 tests SqlFloat64 type func TestSqlFloat64(t *testing.T) { tests := []struct { name string input interface{} expected float64 valid bool }{ {"float64", float64(3.14), 3.14, true}, {"float32", float32(2.5), 2.5, true}, {"int", 42, 42.0, true}, {"int64", int64(100), 100.0, true}, {"nil", nil, 0, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var n SqlFloat64 if err := n.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if n.Valid != tt.valid { t.Errorf("expected valid=%v, got valid=%v", tt.valid, n.Valid) } if tt.valid && n.Float64() != tt.expected { t.Errorf("expected %v, got %v", tt.expected, n.Float64()) } }) } } // TestSqlTimeStamp tests SqlTimeStamp type func TestSqlTimeStamp(t *testing.T) { now := time.Now() tests := []struct { name string input interface{} }{ {"time.Time", now}, {"string RFC3339", now.Format(time.RFC3339)}, {"string date", "2024-01-15"}, {"string datetime", "2024-01-15T10:30:00"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var ts SqlTimeStamp if err := ts.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if ts.Time().IsZero() { t.Error("expected non-zero time") } }) } } func TestSqlTimeStamp_JSON(t *testing.T) { now := time.Date(2024, 1, 15, 10, 30, 45, 0, time.UTC) ts := NewSqlTimeStamp(now) // Marshal data, err := json.Marshal(ts) if err != nil { t.Fatalf("Marshal failed: %v", err) } expected := `"2024-01-15T10:30:45Z"` if string(data) != expected { t.Errorf("expected %s, got %s", expected, string(data)) } // Unmarshal var ts2 SqlTimeStamp if err := json.Unmarshal([]byte(`"2024-01-15T10:30:45"`), &ts2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if ts2.Time().Year() != 2024 { t.Errorf("expected year 2024, got %d", ts2.Time().Year()) } // Test null var ts3 SqlTimeStamp if err := json.Unmarshal([]byte("null"), &ts3); err != nil { t.Fatalf("Unmarshal null failed: %v", err) } } // TestSqlDate tests SqlDate type func TestSqlDate(t *testing.T) { now := time.Now() tests := []struct { name string input interface{} }{ {"time.Time", now}, {"string date", "2024-01-15"}, {"string UK format", "15/01/2024"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var d SqlDate if err := d.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if d.String() == "0" { t.Error("expected non-zero date") } }) } } func TestSqlDate_JSON(t *testing.T) { date := NewSqlDate(time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)) // Marshal data, err := json.Marshal(date) if err != nil { t.Fatalf("Marshal failed: %v", err) } expected := `"2024-01-15"` if string(data) != expected { t.Errorf("expected %s, got %s", expected, string(data)) } // Unmarshal var d2 SqlDate if err := json.Unmarshal([]byte(`"2024-01-15"`), &d2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } } // TestSqlTime tests SqlTime type func TestSqlTime(t *testing.T) { now := time.Now() tests := []struct { name string input interface{} expected string }{ {"time.Time", now, now.Format("15:04:05")}, {"string time", "10:30:45", "10:30:45"}, {"string short time", "10:30", "10:30:00"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var tm SqlTime if err := tm.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if tm.String() != tt.expected { t.Errorf("expected %s, got %s", tt.expected, tm.String()) } }) } } // TestSqlJSONB tests SqlJSONB type func TestSqlJSONB_Scan(t *testing.T) { tests := []struct { name string input interface{} expected string }{ {"string JSON object", `{"key":"value"}`, `{"key":"value"}`}, {"string JSON array", `[1,2,3]`, `[1,2,3]`}, {"bytes", []byte(`{"test":true}`), `{"test":true}`}, {"nil", nil, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var j SqlJSONB if err := j.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if tt.expected == "" && j == nil { return // nil case } if string(j) != tt.expected { t.Errorf("expected %s, got %s", tt.expected, string(j)) } }) } } func TestSqlJSONB_Value(t *testing.T) { tests := []struct { name string input SqlJSONB expected string wantErr bool }{ {"valid object", SqlJSONB(`{"key":"value"}`), `{"key":"value"}`, false}, {"valid array", SqlJSONB(`[1,2,3]`), `[1,2,3]`, false}, {"empty", SqlJSONB{}, "", false}, {"nil", nil, "", false}, {"invalid JSON", SqlJSONB(`{invalid`), "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { val, err := tt.input.Value() if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("Value failed: %v", err) } if tt.expected == "" && val == nil { return // nil case } if val.(string) != tt.expected { t.Errorf("expected %s, got %s", tt.expected, val) } }) } } func TestSqlJSONB_JSON(t *testing.T) { // Marshal j := SqlJSONB(`{"name":"test","count":42}`) data, err := json.Marshal(j) if err != nil { t.Fatalf("Marshal failed: %v", err) } var result map[string]interface{} if err := json.Unmarshal(data, &result); err != nil { t.Fatalf("Unmarshal result failed: %v", err) } if result["name"] != "test" { t.Errorf("expected name=test, got %v", result["name"]) } // Unmarshal var j2 SqlJSONB if err := json.Unmarshal([]byte(`{"key":"value"}`), &j2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if string(j2) != `{"key":"value"}` { t.Errorf("expected {\"key\":\"value\"}, got %s", string(j2)) } // Test null var j3 SqlJSONB if err := json.Unmarshal([]byte("null"), &j3); err != nil { t.Fatalf("Unmarshal null failed: %v", err) } } func TestSqlJSONB_AsMap(t *testing.T) { tests := []struct { name string input SqlJSONB wantErr bool wantNil bool }{ {"valid object", SqlJSONB(`{"name":"test","age":30}`), false, false}, {"empty", SqlJSONB{}, false, true}, {"nil", nil, false, true}, {"invalid JSON", SqlJSONB(`{invalid`), true, false}, {"array not object", SqlJSONB(`[1,2,3]`), true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m, err := tt.input.AsMap() if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("AsMap failed: %v", err) } if tt.wantNil { if m != nil { t.Errorf("expected nil, got %v", m) } return } if m == nil { t.Error("expected non-nil map") } }) } } func TestSqlJSONB_AsSlice(t *testing.T) { tests := []struct { name string input SqlJSONB wantErr bool wantNil bool }{ {"valid array", SqlJSONB(`[1,2,3]`), false, false}, {"empty", SqlJSONB{}, false, true}, {"nil", nil, false, true}, {"invalid JSON", SqlJSONB(`[invalid`), true, false}, {"object not array", SqlJSONB(`{"key":"value"}`), true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s, err := tt.input.AsSlice() if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("AsSlice failed: %v", err) } if tt.wantNil { if s != nil { t.Errorf("expected nil, got %v", s) } return } if s == nil { t.Error("expected non-nil slice") } }) } } // TestSqlUUID tests SqlUUID type func TestSqlUUID_Scan(t *testing.T) { testUUID := uuid.New() testUUIDStr := testUUID.String() tests := []struct { name string input interface{} expected string valid bool }{ {"string UUID", testUUIDStr, testUUIDStr, true}, {"bytes UUID", []byte(testUUIDStr), testUUIDStr, true}, {"nil", nil, "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var u SqlUUID if err := u.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if u.Valid != tt.valid { t.Errorf("expected valid=%v, got valid=%v", tt.valid, u.Valid) } if tt.valid && u.String() != tt.expected { t.Errorf("expected %s, got %s", tt.expected, u.String()) } }) } } func TestSqlUUID_Value(t *testing.T) { testUUID := uuid.New() u := NewSqlUUID(testUUID) val, err := u.Value() if err != nil { t.Fatalf("Value failed: %v", err) } // Value() should return a string for driver compatibility if val != testUUID.String() { t.Errorf("expected %s, got %s", testUUID.String(), val) } // Test invalid UUID u2 := SqlUUID{Valid: false} val2, err := u2.Value() if err != nil { t.Fatalf("Value failed: %v", err) } if val2 != nil { t.Errorf("expected nil, got %v", val2) } } func TestSqlUUID_JSON(t *testing.T) { testUUID := uuid.New() u := NewSqlUUID(testUUID) // Marshal data, err := json.Marshal(u) if err != nil { t.Fatalf("Marshal failed: %v", err) } expected := `"` + testUUID.String() + `"` if string(data) != expected { t.Errorf("expected %s, got %s", expected, string(data)) } // Unmarshal var u2 SqlUUID if err := json.Unmarshal([]byte(`"`+testUUID.String()+`"`), &u2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if u2.String() != testUUID.String() { t.Errorf("expected %s, got %s", testUUID.String(), u2.String()) } // Test null var u3 SqlUUID if err := json.Unmarshal([]byte("null"), &u3); err != nil { t.Fatalf("Unmarshal null failed: %v", err) } if u3.Valid { t.Error("expected invalid UUID") } } // TestTryIfInt64 tests the TryIfInt64 helper function func TestTryIfInt64(t *testing.T) { tests := []struct { name string input interface{} def int64 expected int64 }{ {"string valid", "123", 0, 123}, {"string invalid", "abc", 99, 99}, {"int", 42, 0, 42}, {"int32", int32(100), 0, 100}, {"int64", int64(200), 0, 200}, {"uint32", uint32(50), 0, 50}, {"uint64", uint64(75), 0, 75}, {"float32", float32(3.14), 0, 3}, {"float64", float64(2.71), 0, 2}, {"bytes", []byte("456"), 0, 456}, {"unknown type", struct{}{}, 999, 999}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := TryIfInt64(tt.input, tt.def) if result != tt.expected { t.Errorf("expected %d, got %d", tt.expected, result) } }) } } // TestSqlString tests SqlString without base64 (plain text) func TestSqlString_Scan(t *testing.T) { tests := []struct { name string input interface{} expected string valid bool }{ { name: "plain string", input: "hello world", expected: "hello world", valid: true, }, { name: "plain text", input: "plain text", expected: "plain text", valid: true, }, { name: "bytes as string", input: []byte("raw bytes"), expected: "raw bytes", valid: true, }, { name: "nil value", input: nil, expected: "", valid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s SqlString if err := s.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if s.Valid != tt.valid { t.Errorf("expected valid=%v, got valid=%v", tt.valid, s.Valid) } if tt.valid && s.String() != tt.expected { t.Errorf("expected %q, got %q", tt.expected, s.String()) } }) } } func TestSqlString_JSON(t *testing.T) { tests := []struct { name string inputValue string expectedJSON string expectedDecode string }{ { name: "simple string", inputValue: "hello world", expectedJSON: `"hello world"`, // plain text, not base64 expectedDecode: "hello world", }, { name: "special characters", inputValue: "test@#$%", expectedJSON: `"test@#$%"`, // plain text, not base64 expectedDecode: "test@#$%", }, { name: "unicode string", inputValue: "Hello 世界", expectedJSON: `"Hello 世界"`, // plain text, not base64 expectedDecode: "Hello 世界", }, { name: "empty string", inputValue: "", expectedJSON: `""`, expectedDecode: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test MarshalJSON s := NewSqlString(tt.inputValue) data, err := json.Marshal(s) if err != nil { t.Fatalf("Marshal failed: %v", err) } if string(data) != tt.expectedJSON { t.Errorf("Marshal: expected %s, got %s", tt.expectedJSON, string(data)) } // Test UnmarshalJSON var s2 SqlString if err := json.Unmarshal(data, &s2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if !s2.Valid { t.Error("expected valid=true after unmarshal") } if s2.String() != tt.expectedDecode { t.Errorf("Unmarshal: expected %q, got %q", tt.expectedDecode, s2.String()) } }) } } func TestSqlString_JSON_Null(t *testing.T) { // Test null handling var s SqlString if err := json.Unmarshal([]byte("null"), &s); err != nil { t.Fatalf("Unmarshal null failed: %v", err) } if s.Valid { t.Error("expected invalid after unmarshaling null") } // Test marshal null data, err := json.Marshal(s) if err != nil { t.Fatalf("Marshal failed: %v", err) } if string(data) != "null" { t.Errorf("expected null, got %s", string(data)) } } // TestSqlByteArray_Base64 tests SqlByteArray with base64 encoding/decoding func TestSqlByteArray_Base64_Scan(t *testing.T) { tests := []struct { name string input interface{} expected []byte valid bool }{ { name: "base64 encoded bytes from SQL", input: "aGVsbG8gd29ybGQ=", // "hello world" in base64 expected: []byte("hello world"), valid: true, }, { name: "plain bytes fallback", input: "plain text", expected: []byte("plain text"), valid: true, }, { name: "bytes base64 encoded", input: []byte("SGVsbG8gR29waGVy"), // "Hello Gopher" in base64 expected: []byte("Hello Gopher"), valid: true, }, { name: "bytes plain fallback", input: []byte("raw bytes"), expected: []byte("raw bytes"), valid: true, }, { name: "binary data", input: "AQIDBA==", // []byte{1, 2, 3, 4} in base64 expected: []byte{1, 2, 3, 4}, valid: true, }, { name: "nil value", input: nil, expected: nil, valid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b SqlByteArray if err := b.Scan(tt.input); err != nil { t.Fatalf("Scan failed: %v", err) } if b.Valid != tt.valid { t.Errorf("expected valid=%v, got valid=%v", tt.valid, b.Valid) } if tt.valid { if string(b.Val) != string(tt.expected) { t.Errorf("expected %q, got %q", tt.expected, b.Val) } } }) } } func TestSqlByteArray_Base64_JSON(t *testing.T) { tests := []struct { name string inputValue []byte expectedJSON string expectedDecode []byte }{ { name: "text bytes", inputValue: []byte("hello world"), expectedJSON: `"aGVsbG8gd29ybGQ="`, // base64 encoded expectedDecode: []byte("hello world"), }, { name: "binary data", inputValue: []byte{0x01, 0x02, 0x03, 0x04, 0xFF}, expectedJSON: `"AQIDBP8="`, // base64 encoded expectedDecode: []byte{0x01, 0x02, 0x03, 0x04, 0xFF}, }, { name: "empty bytes", inputValue: []byte{}, expectedJSON: `""`, // base64 of empty bytes expectedDecode: []byte{}, }, { name: "unicode bytes", inputValue: []byte("Hello 世界"), expectedJSON: `"SGVsbG8g5LiW55WM"`, // base64 encoded expectedDecode: []byte("Hello 世界"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test MarshalJSON b := NewSqlByteArray(tt.inputValue) data, err := json.Marshal(b) if err != nil { t.Fatalf("Marshal failed: %v", err) } if string(data) != tt.expectedJSON { t.Errorf("Marshal: expected %s, got %s", tt.expectedJSON, string(data)) } // Test UnmarshalJSON var b2 SqlByteArray if err := json.Unmarshal(data, &b2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } if !b2.Valid { t.Error("expected valid=true after unmarshal") } if string(b2.Val) != string(tt.expectedDecode) { t.Errorf("Unmarshal: expected %v, got %v", tt.expectedDecode, b2.Val) } }) } } func TestSqlByteArray_Base64_JSON_Null(t *testing.T) { // Test null handling var b SqlByteArray if err := json.Unmarshal([]byte("null"), &b); err != nil { t.Fatalf("Unmarshal null failed: %v", err) } if b.Valid { t.Error("expected invalid after unmarshaling null") } // Test marshal null data, err := json.Marshal(b) if err != nil { t.Fatalf("Marshal failed: %v", err) } if string(data) != "null" { t.Errorf("expected null, got %s", string(data)) } } func TestSqlByteArray_Value(t *testing.T) { tests := []struct { name string input SqlByteArray expected interface{} }{ { name: "valid bytes", input: NewSqlByteArray([]byte("test data")), expected: []byte("test data"), }, { name: "empty bytes", input: NewSqlByteArray([]byte{}), expected: []byte{}, }, { name: "invalid", input: SqlByteArray{Valid: false}, expected: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { val, err := tt.input.Value() if err != nil { t.Fatalf("Value failed: %v", err) } if tt.expected == nil && val != nil { t.Errorf("expected nil, got %v", val) } if tt.expected != nil && val == nil { t.Errorf("expected %v, got nil", tt.expected) } if tt.expected != nil && val != nil { if string(val.([]byte)) != string(tt.expected.([]byte)) { t.Errorf("expected %v, got %v", tt.expected, val) } } }) } } // TestSqlString_RoundTrip tests complete round-trip: Go -> JSON -> Go -> SQL -> Go func TestSqlString_RoundTrip(t *testing.T) { original := "Test String with Special Chars: @#$%^&*()" // Go -> JSON s1 := NewSqlString(original) jsonData, err := json.Marshal(s1) if err != nil { t.Fatalf("Marshal failed: %v", err) } // JSON -> Go var s2 SqlString if err := json.Unmarshal(jsonData, &s2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } // Go -> SQL (Value) _, err = s2.Value() if err != nil { t.Fatalf("Value failed: %v", err) } // SQL -> Go (Scan plain text) var s3 SqlString // Simulate SQL driver returning plain text value if err := s3.Scan(original); err != nil { t.Fatalf("Scan failed: %v", err) } // Verify round-trip if s3.String() != original { t.Errorf("Round-trip failed: expected %q, got %q", original, s3.String()) } } // TestSqlByteArray_Base64_RoundTrip tests complete round-trip: Go -> JSON -> Go -> SQL -> Go func TestSqlByteArray_Base64_RoundTrip(t *testing.T) { original := []byte{0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0xFF, 0xFE} // "Hello " + binary data // Go -> JSON b1 := NewSqlByteArray(original) jsonData, err := json.Marshal(b1) if err != nil { t.Fatalf("Marshal failed: %v", err) } // JSON -> Go var b2 SqlByteArray if err := json.Unmarshal(jsonData, &b2); err != nil { t.Fatalf("Unmarshal failed: %v", err) } // Go -> SQL (Value) _, err = b2.Value() if err != nil { t.Fatalf("Value failed: %v", err) } // SQL -> Go (Scan with base64) var b3 SqlByteArray // Simulate SQL driver returning base64 encoded value if err := b3.Scan("SGVsbG8g//4="); err != nil { t.Fatalf("Scan failed: %v", err) } // Verify round-trip if string(b3.Val) != string(original) { t.Errorf("Round-trip failed: expected %v, got %v", original, b3.Val) } }