test(pgsql, reflectutil): ✨ add comprehensive test coverage
All checks were successful
All checks were successful
* Introduce tests for PostgreSQL data types and keywords. * Implement tests for reflect utility functions. * Ensure consistency and correctness of type conversions and keyword mappings. * Validate behavior for various edge cases and input types.
This commit is contained in:
339
pkg/pgsql/datatypes_test.go
Normal file
339
pkg/pgsql/datatypes_test.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidSQLType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sqltype string
|
||||
want bool
|
||||
}{
|
||||
// PostgreSQL types
|
||||
{"Valid PGSQL bigint", "bigint", true},
|
||||
{"Valid PGSQL integer", "integer", true},
|
||||
{"Valid PGSQL text", "text", true},
|
||||
{"Valid PGSQL boolean", "boolean", true},
|
||||
{"Valid PGSQL double precision", "double precision", true},
|
||||
{"Valid PGSQL bytea", "bytea", true},
|
||||
{"Valid PGSQL uuid", "uuid", true},
|
||||
{"Valid PGSQL jsonb", "jsonb", true},
|
||||
{"Valid PGSQL json", "json", true},
|
||||
{"Valid PGSQL timestamp", "timestamp", true},
|
||||
{"Valid PGSQL date", "date", true},
|
||||
{"Valid PGSQL time", "time", true},
|
||||
{"Valid PGSQL citext", "citext", true},
|
||||
|
||||
// Standard types
|
||||
{"Valid std double", "double", true},
|
||||
{"Valid std blob", "blob", true},
|
||||
|
||||
// Case insensitive
|
||||
{"Case insensitive BIGINT", "BIGINT", true},
|
||||
{"Case insensitive TeXt", "TeXt", true},
|
||||
{"Case insensitive BoOlEaN", "BoOlEaN", true},
|
||||
|
||||
// Invalid types
|
||||
{"Invalid type", "invalidtype", false},
|
||||
{"Invalid type varchar", "varchar", false},
|
||||
{"Empty string", "", false},
|
||||
{"Random string", "foobar", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidSQLType(tt.sqltype)
|
||||
if got != tt.want {
|
||||
t.Errorf("ValidSQLType(%q) = %v, want %v", tt.sqltype, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSQLType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
anytype string
|
||||
want string
|
||||
}{
|
||||
// Go types to PostgreSQL types
|
||||
{"Go bool to boolean", "bool", "boolean"},
|
||||
{"Go int64 to bigint", "int64", "bigint"},
|
||||
{"Go int to integer", "int", "integer"},
|
||||
{"Go string to text", "string", "text"},
|
||||
{"Go float64 to double precision", "float64", "double precision"},
|
||||
{"Go float32 to real", "float32", "real"},
|
||||
{"Go []byte to bytea", "[]byte", "bytea"},
|
||||
|
||||
// SQL types remain SQL types
|
||||
{"SQL bigint", "bigint", "bigint"},
|
||||
{"SQL integer", "integer", "integer"},
|
||||
{"SQL text", "text", "text"},
|
||||
{"SQL boolean", "boolean", "boolean"},
|
||||
{"SQL uuid", "uuid", "uuid"},
|
||||
{"SQL jsonb", "jsonb", "jsonb"},
|
||||
|
||||
// Case insensitive Go types
|
||||
{"Case insensitive BOOL", "BOOL", "boolean"},
|
||||
{"Case insensitive InT64", "InT64", "bigint"},
|
||||
{"Case insensitive STRING", "STRING", "text"},
|
||||
|
||||
// Case insensitive SQL types
|
||||
{"Case insensitive BIGINT", "BIGINT", "bigint"},
|
||||
{"Case insensitive TEXT", "TEXT", "text"},
|
||||
|
||||
// Custom types
|
||||
{"Custom sqluuid", "sqluuid", "uuid"},
|
||||
{"Custom sqljsonb", "sqljsonb", "jsonb"},
|
||||
{"Custom sqlint64", "sqlint64", "bigint"},
|
||||
|
||||
// Unknown types default to text
|
||||
{"Unknown type varchar", "varchar", "text"},
|
||||
{"Unknown type foobar", "foobar", "text"},
|
||||
{"Empty string", "", "text"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetSQLType(tt.anytype)
|
||||
if got != tt.want {
|
||||
t.Errorf("GetSQLType(%q) = %q, want %q", tt.anytype, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertSQLType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
anytype string
|
||||
want string
|
||||
}{
|
||||
// Go types to PostgreSQL types
|
||||
{"Go bool to boolean", "bool", "boolean"},
|
||||
{"Go int64 to bigint", "int64", "bigint"},
|
||||
{"Go int to integer", "int", "integer"},
|
||||
{"Go string to text", "string", "text"},
|
||||
{"Go float64 to double precision", "float64", "double precision"},
|
||||
{"Go float32 to real", "float32", "real"},
|
||||
{"Go []byte to bytea", "[]byte", "bytea"},
|
||||
|
||||
// SQL types remain SQL types
|
||||
{"SQL bigint", "bigint", "bigint"},
|
||||
{"SQL integer", "integer", "integer"},
|
||||
{"SQL text", "text", "text"},
|
||||
{"SQL boolean", "boolean", "boolean"},
|
||||
|
||||
// Case insensitive
|
||||
{"Case insensitive BOOL", "BOOL", "boolean"},
|
||||
{"Case insensitive InT64", "InT64", "bigint"},
|
||||
|
||||
// Unknown types remain unchanged (difference from GetSQLType)
|
||||
{"Unknown type varchar", "varchar", "varchar"},
|
||||
{"Unknown type foobar", "foobar", "foobar"},
|
||||
{"Empty string", "", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertSQLType(tt.anytype)
|
||||
if got != tt.want {
|
||||
t.Errorf("ConvertSQLType(%q) = %q, want %q", tt.anytype, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGoType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
typeName string
|
||||
want bool
|
||||
}{
|
||||
// Go basic types
|
||||
{"Go bool", "bool", true},
|
||||
{"Go int64", "int64", true},
|
||||
{"Go int", "int", true},
|
||||
{"Go int32", "int32", true},
|
||||
{"Go int16", "int16", true},
|
||||
{"Go int8", "int8", true},
|
||||
{"Go uint", "uint", true},
|
||||
{"Go uint64", "uint64", true},
|
||||
{"Go uint32", "uint32", true},
|
||||
{"Go uint16", "uint16", true},
|
||||
{"Go uint8", "uint8", true},
|
||||
{"Go float64", "float64", true},
|
||||
{"Go float32", "float32", true},
|
||||
{"Go string", "string", true},
|
||||
{"Go []byte", "[]byte", true},
|
||||
|
||||
// Go custom types
|
||||
{"Go complex64", "complex64", true},
|
||||
{"Go complex128", "complex128", true},
|
||||
{"Go uintptr", "uintptr", true},
|
||||
{"Go Pointer", "Pointer", true},
|
||||
|
||||
// Custom SQL types
|
||||
{"Custom sqluuid", "sqluuid", true},
|
||||
{"Custom sqljsonb", "sqljsonb", true},
|
||||
{"Custom sqlint64", "sqlint64", true},
|
||||
{"Custom customdate", "customdate", true},
|
||||
{"Custom customtime", "customtime", true},
|
||||
|
||||
// Case insensitive
|
||||
{"Case insensitive BOOL", "BOOL", true},
|
||||
{"Case insensitive InT64", "InT64", true},
|
||||
{"Case insensitive STRING", "STRING", true},
|
||||
|
||||
// SQL types (not Go types)
|
||||
{"SQL bigint", "bigint", false},
|
||||
{"SQL integer", "integer", false},
|
||||
{"SQL text", "text", false},
|
||||
{"SQL boolean", "boolean", false},
|
||||
|
||||
// Invalid types
|
||||
{"Invalid type", "invalidtype", false},
|
||||
{"Empty string", "", false},
|
||||
{"Random string", "foobar", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := IsGoType(tt.typeName)
|
||||
if got != tt.want {
|
||||
t.Errorf("IsGoType(%q) = %v, want %v", tt.typeName, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStdTypeFromGo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
typeName string
|
||||
want string
|
||||
}{
|
||||
// Go types to standard SQL types
|
||||
{"Go bool to boolean", "bool", "boolean"},
|
||||
{"Go int64 to bigint", "int64", "bigint"},
|
||||
{"Go int to integer", "int", "integer"},
|
||||
{"Go string to text", "string", "text"},
|
||||
{"Go float64 to double", "float64", "double"},
|
||||
{"Go float32 to double", "float32", "double"},
|
||||
{"Go []byte to blob", "[]byte", "blob"},
|
||||
{"Go int32 to integer", "int32", "integer"},
|
||||
{"Go int16 to smallint", "int16", "smallint"},
|
||||
|
||||
// Custom types
|
||||
{"Custom sqluuid to uuid", "sqluuid", "uuid"},
|
||||
{"Custom sqljsonb to jsonb", "sqljsonb", "jsonb"},
|
||||
{"Custom sqlint64 to bigint", "sqlint64", "bigint"},
|
||||
{"Custom customdate to date", "customdate", "date"},
|
||||
|
||||
// Case insensitive
|
||||
{"Case insensitive BOOL", "BOOL", "boolean"},
|
||||
{"Case insensitive InT64", "InT64", "bigint"},
|
||||
{"Case insensitive STRING", "STRING", "text"},
|
||||
|
||||
// Non-Go types remain unchanged
|
||||
{"SQL bigint unchanged", "bigint", "bigint"},
|
||||
{"SQL integer unchanged", "integer", "integer"},
|
||||
{"Invalid type unchanged", "invalidtype", "invalidtype"},
|
||||
{"Empty string unchanged", "", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetStdTypeFromGo(tt.typeName)
|
||||
if got != tt.want {
|
||||
t.Errorf("GetStdTypeFromGo(%q) = %q, want %q", tt.typeName, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoToStdTypesMap(t *testing.T) {
|
||||
// Test that the map contains expected entries
|
||||
expectedMappings := map[string]string{
|
||||
"bool": "boolean",
|
||||
"int64": "bigint",
|
||||
"int": "integer",
|
||||
"string": "text",
|
||||
"float64": "double",
|
||||
"[]byte": "blob",
|
||||
}
|
||||
|
||||
for goType, expectedStd := range expectedMappings {
|
||||
if stdType, ok := GoToStdTypes[goType]; !ok {
|
||||
t.Errorf("GoToStdTypes missing entry for %q", goType)
|
||||
} else if stdType != expectedStd {
|
||||
t.Errorf("GoToStdTypes[%q] = %q, want %q", goType, stdType, expectedStd)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the map is not empty
|
||||
if len(GoToStdTypes) == 0 {
|
||||
t.Error("GoToStdTypes map is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoToPGSQLTypesMap(t *testing.T) {
|
||||
// Test that the map contains expected entries
|
||||
expectedMappings := map[string]string{
|
||||
"bool": "boolean",
|
||||
"int64": "bigint",
|
||||
"int": "integer",
|
||||
"string": "text",
|
||||
"float64": "double precision",
|
||||
"float32": "real",
|
||||
"[]byte": "bytea",
|
||||
}
|
||||
|
||||
for goType, expectedPG := range expectedMappings {
|
||||
if pgType, ok := GoToPGSQLTypes[goType]; !ok {
|
||||
t.Errorf("GoToPGSQLTypes missing entry for %q", goType)
|
||||
} else if pgType != expectedPG {
|
||||
t.Errorf("GoToPGSQLTypes[%q] = %q, want %q", goType, pgType, expectedPG)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the map is not empty
|
||||
if len(GoToPGSQLTypes) == 0 {
|
||||
t.Error("GoToPGSQLTypes map is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeConversionConsistency(t *testing.T) {
|
||||
// Test that GetSQLType and ConvertSQLType are consistent for known types
|
||||
knownGoTypes := []string{"bool", "int64", "int", "string", "float64", "[]byte"}
|
||||
|
||||
for _, goType := range knownGoTypes {
|
||||
getSQLResult := GetSQLType(goType)
|
||||
convertResult := ConvertSQLType(goType)
|
||||
|
||||
if getSQLResult != convertResult {
|
||||
t.Errorf("Inconsistent results for %q: GetSQLType=%q, ConvertSQLType=%q",
|
||||
goType, getSQLResult, convertResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSQLTypeVsConvertSQLTypeDifference(t *testing.T) {
|
||||
// Test that GetSQLType returns "text" for unknown types
|
||||
// while ConvertSQLType returns the input unchanged
|
||||
unknownTypes := []string{"varchar", "char", "customtype", "unknowntype"}
|
||||
|
||||
for _, unknown := range unknownTypes {
|
||||
getSQLResult := GetSQLType(unknown)
|
||||
convertResult := ConvertSQLType(unknown)
|
||||
|
||||
if getSQLResult != "text" {
|
||||
t.Errorf("GetSQLType(%q) = %q, want %q", unknown, getSQLResult, "text")
|
||||
}
|
||||
|
||||
if convertResult != unknown {
|
||||
t.Errorf("ConvertSQLType(%q) = %q, want %q", unknown, convertResult, unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
136
pkg/pgsql/keywords_test.go
Normal file
136
pkg/pgsql/keywords_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package pgsql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetPostgresKeywords(t *testing.T) {
|
||||
keywords := GetPostgresKeywords()
|
||||
|
||||
// Test that keywords are returned
|
||||
if len(keywords) == 0 {
|
||||
t.Fatal("Expected non-empty list of keywords")
|
||||
}
|
||||
|
||||
// Test that we get all keywords from the map
|
||||
expectedCount := len(postgresKeywords)
|
||||
if len(keywords) != expectedCount {
|
||||
t.Errorf("Expected %d keywords, got %d", expectedCount, len(keywords))
|
||||
}
|
||||
|
||||
// Test that all returned keywords exist in the map
|
||||
for _, keyword := range keywords {
|
||||
if !postgresKeywords[keyword] {
|
||||
t.Errorf("Keyword %q not found in postgresKeywords map", keyword)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that no duplicate keywords are returned
|
||||
seen := make(map[string]bool)
|
||||
for _, keyword := range keywords {
|
||||
if seen[keyword] {
|
||||
t.Errorf("Duplicate keyword found: %q", keyword)
|
||||
}
|
||||
seen[keyword] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresKeywordsMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyword string
|
||||
want bool
|
||||
}{
|
||||
{"SELECT keyword", "select", true},
|
||||
{"FROM keyword", "from", true},
|
||||
{"WHERE keyword", "where", true},
|
||||
{"TABLE keyword", "table", true},
|
||||
{"PRIMARY keyword", "primary", true},
|
||||
{"FOREIGN keyword", "foreign", true},
|
||||
{"CREATE keyword", "create", true},
|
||||
{"DROP keyword", "drop", true},
|
||||
{"ALTER keyword", "alter", true},
|
||||
{"INDEX keyword", "index", true},
|
||||
{"NOT keyword", "not", true},
|
||||
{"NULL keyword", "null", true},
|
||||
{"TRUE keyword", "true", true},
|
||||
{"FALSE keyword", "false", true},
|
||||
{"Non-keyword lowercase", "notakeyword", false},
|
||||
{"Non-keyword uppercase", "NOTAKEYWORD", false},
|
||||
{"Empty string", "", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := postgresKeywords[tt.keyword]
|
||||
if got != tt.want {
|
||||
t.Errorf("postgresKeywords[%q] = %v, want %v", tt.keyword, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresKeywordsMapContent(t *testing.T) {
|
||||
// Test that the map contains expected common keywords
|
||||
commonKeywords := []string{
|
||||
"select", "insert", "update", "delete", "create", "drop", "alter",
|
||||
"table", "index", "view", "schema", "function", "procedure",
|
||||
"primary", "foreign", "key", "constraint", "unique", "check",
|
||||
"null", "not", "and", "or", "like", "in", "between",
|
||||
"join", "inner", "left", "right", "cross", "full", "outer",
|
||||
"where", "having", "group", "order", "limit", "offset",
|
||||
"union", "intersect", "except",
|
||||
"begin", "commit", "rollback", "transaction",
|
||||
}
|
||||
|
||||
for _, keyword := range commonKeywords {
|
||||
if !postgresKeywords[keyword] {
|
||||
t.Errorf("Expected common keyword %q to be in postgresKeywords map", keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresKeywordsMapSize(t *testing.T) {
|
||||
// PostgreSQL has a substantial list of reserved keywords
|
||||
// This test ensures the map has a reasonable number of entries
|
||||
minExpectedKeywords := 200 // PostgreSQL 13+ has 400+ reserved words
|
||||
|
||||
if len(postgresKeywords) < minExpectedKeywords {
|
||||
t.Errorf("Expected at least %d keywords, got %d. The map may be incomplete.",
|
||||
minExpectedKeywords, len(postgresKeywords))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPostgresKeywordsConsistency(t *testing.T) {
|
||||
// Test that calling GetPostgresKeywords multiple times returns consistent results
|
||||
keywords1 := GetPostgresKeywords()
|
||||
keywords2 := GetPostgresKeywords()
|
||||
|
||||
if len(keywords1) != len(keywords2) {
|
||||
t.Errorf("Inconsistent results: first call returned %d keywords, second call returned %d",
|
||||
len(keywords1), len(keywords2))
|
||||
}
|
||||
|
||||
// Create a map from both results to compare
|
||||
map1 := make(map[string]bool)
|
||||
map2 := make(map[string]bool)
|
||||
|
||||
for _, k := range keywords1 {
|
||||
map1[k] = true
|
||||
}
|
||||
for _, k := range keywords2 {
|
||||
map2[k] = true
|
||||
}
|
||||
|
||||
// Check that both contain the same keywords
|
||||
for k := range map1 {
|
||||
if !map2[k] {
|
||||
t.Errorf("Keyword %q present in first call but not in second", k)
|
||||
}
|
||||
}
|
||||
for k := range map2 {
|
||||
if !map1[k] {
|
||||
t.Errorf("Keyword %q present in second call but not in first", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user