forked from go/pgm
perf enhancement
This commit is contained in:
706
pgm_test.go
Normal file
706
pgm_test.go
Normal file
@@ -0,0 +1,706 @@
|
||||
// Patial Tech.
|
||||
// Author, Ankit Patial
|
||||
|
||||
package pgm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// TestGetPoolNotInitialized verifies that GetPool panics when pool is not initialized
|
||||
func TestGetPoolNotInitialized(t *testing.T) {
|
||||
// Save current pool state
|
||||
currentPool := poolPGX.Load()
|
||||
|
||||
// Clear the pool to simulate uninitialized state
|
||||
poolPGX.Store(nil)
|
||||
|
||||
// Restore pool after test
|
||||
defer func() {
|
||||
poolPGX.Store(currentPool)
|
||||
}()
|
||||
|
||||
// Verify panic occurs
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("GetPool should panic when pool not initialized")
|
||||
} else {
|
||||
// Check panic message
|
||||
msg, ok := r.(string)
|
||||
if !ok || !strings.Contains(msg, "not initialized") {
|
||||
t.Errorf("Expected panic message about initialization, got: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
GetPool() // Should panic
|
||||
}
|
||||
|
||||
// TestConfigValidation tests that InitPool validates configuration
|
||||
func TestConfigValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config Config
|
||||
wantPanic bool
|
||||
panicMsg string
|
||||
}{
|
||||
{
|
||||
name: "empty connection string",
|
||||
config: Config{
|
||||
ConnString: "",
|
||||
},
|
||||
wantPanic: true,
|
||||
panicMsg: "connection string is empty",
|
||||
},
|
||||
{
|
||||
name: "MinConns greater than MaxConns",
|
||||
config: Config{
|
||||
ConnString: "postgres://localhost/test",
|
||||
MaxConns: 5,
|
||||
MinConns: 10,
|
||||
},
|
||||
wantPanic: true,
|
||||
panicMsg: "MinConns",
|
||||
},
|
||||
{
|
||||
name: "negative MaxConns",
|
||||
config: Config{
|
||||
ConnString: "postgres://localhost/test",
|
||||
MaxConns: -1,
|
||||
},
|
||||
wantPanic: true,
|
||||
panicMsg: "negative",
|
||||
},
|
||||
{
|
||||
name: "negative MinConns",
|
||||
config: Config{
|
||||
ConnString: "postgres://localhost/test",
|
||||
MinConns: -1,
|
||||
},
|
||||
wantPanic: true,
|
||||
panicMsg: "negative",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if tt.wantPanic && r == nil {
|
||||
t.Errorf("InitPool should panic for %s", tt.name)
|
||||
}
|
||||
if !tt.wantPanic && r != nil {
|
||||
t.Errorf("InitPool should not panic for %s, got: %v", tt.name, r)
|
||||
}
|
||||
if tt.wantPanic && r != nil {
|
||||
msg := ""
|
||||
switch v := r.(type) {
|
||||
case string:
|
||||
msg = v
|
||||
case error:
|
||||
msg = v.Error()
|
||||
}
|
||||
if !strings.Contains(msg, tt.panicMsg) {
|
||||
t.Errorf("Expected panic message to contain %q, got: %q", tt.panicMsg, msg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
InitPool(tt.config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestClosePoolIdempotent verifies ClosePool can be called multiple times safely
|
||||
func TestClosePoolIdempotent(t *testing.T) {
|
||||
// Save current pool
|
||||
currentPool := poolPGX.Load()
|
||||
defer func() {
|
||||
poolPGX.Store(currentPool)
|
||||
}()
|
||||
|
||||
// Set to nil
|
||||
poolPGX.Store(nil)
|
||||
|
||||
// Should not panic when called on nil pool
|
||||
ClosePool()
|
||||
ClosePool()
|
||||
ClosePool()
|
||||
|
||||
// Should work fine - no panic expected
|
||||
}
|
||||
|
||||
// TestIsNotFound tests the error checking utility
|
||||
func TestIsNotFound(t *testing.T) {
|
||||
// Test with pgx.ErrNoRows
|
||||
if !IsNotFound(pgx.ErrNoRows) {
|
||||
t.Error("IsNotFound should return true for pgx.ErrNoRows")
|
||||
}
|
||||
|
||||
// Test with other errors
|
||||
otherErr := errors.New("some other error")
|
||||
if IsNotFound(otherErr) {
|
||||
t.Error("IsNotFound should return false for non-ErrNoRows errors")
|
||||
}
|
||||
|
||||
// Test with nil
|
||||
if IsNotFound(nil) {
|
||||
t.Error("IsNotFound should return false for nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPgTime tests PostgreSQL timestamp conversion
|
||||
func TestPgTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
pgTime := PgTime(now)
|
||||
|
||||
if !pgTime.Valid {
|
||||
t.Error("PgTime should return valid timestamp")
|
||||
}
|
||||
|
||||
if !pgTime.Time.Equal(now) {
|
||||
t.Errorf("PgTime time mismatch: got %v, want %v", pgTime.Time, now)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPgTimeNow tests current time conversion
|
||||
func TestPgTimeNow(t *testing.T) {
|
||||
before := time.Now()
|
||||
pgTime := PgTimeNow()
|
||||
after := time.Now()
|
||||
|
||||
if !pgTime.Valid {
|
||||
t.Error("PgTimeNow should return valid timestamp")
|
||||
}
|
||||
|
||||
// Check that time is between before and after
|
||||
if pgTime.Time.Before(before) || pgTime.Time.After(after) {
|
||||
t.Error("PgTimeNow should return current time")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTsQueryFunctions tests full-text search query builders
|
||||
func TestTsQueryFunctions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(string) string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "TsAndQuery",
|
||||
fn: TsAndQuery,
|
||||
input: "hello world",
|
||||
expected: "hello & world",
|
||||
},
|
||||
{
|
||||
name: "TsPrefixAndQuery",
|
||||
fn: TsPrefixAndQuery,
|
||||
input: "hello world",
|
||||
expected: "hello:* & world:*",
|
||||
},
|
||||
{
|
||||
name: "TsOrQuery",
|
||||
fn: TsOrQuery,
|
||||
input: "hello world",
|
||||
expected: "hello | world",
|
||||
},
|
||||
{
|
||||
name: "TsPrefixOrQuery",
|
||||
fn: TsPrefixOrQuery,
|
||||
input: "hello world",
|
||||
expected: "hello:* | world:*",
|
||||
},
|
||||
{
|
||||
name: "TsAndQuery with multiple words",
|
||||
fn: TsAndQuery,
|
||||
input: "one two three",
|
||||
expected: "one & two & three",
|
||||
},
|
||||
{
|
||||
name: "TsOrQuery with multiple words",
|
||||
fn: TsOrQuery,
|
||||
input: "one two three",
|
||||
expected: "one | two | three",
|
||||
},
|
||||
{
|
||||
name: "TsAndQuery with extra spaces",
|
||||
fn: TsAndQuery,
|
||||
input: "hello world",
|
||||
expected: "hello & world",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.fn(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("%s(%q) = %q, want %q", tt.name, tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFieldsWithSuffix tests the internal suffix function
|
||||
func TestFieldsWithSuffix(t *testing.T) {
|
||||
result := fieldsWithSufix("hello world", ":*")
|
||||
expected := []string{"hello:*", "world:*"}
|
||||
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("fieldsWithSufix length mismatch: got %d, want %d", len(result), len(expected))
|
||||
}
|
||||
|
||||
for i, v := range result {
|
||||
if v != expected[i] {
|
||||
t.Errorf("fieldsWithSufix[%d] = %q, want %q", i, v, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBeginTxErrorWrapping tests that BeginTx preserves error context
|
||||
func TestBeginTxErrorWrapping(t *testing.T) {
|
||||
// This test verifies basic behavior without requiring a database connection
|
||||
// Actual error wrapping would require a failing database connection
|
||||
|
||||
// Skip if no pool initialized (unit test environment)
|
||||
if poolPGX.Load() == nil {
|
||||
t.Skip("Skipping BeginTx test - requires initialized pool")
|
||||
}
|
||||
|
||||
// Just verify the function accepts a context
|
||||
// In a real scenario with a cancelled context, it would return an error
|
||||
ctx := context.Background()
|
||||
|
||||
// We can't fully test without a real DB connection, so just document behavior
|
||||
_, err := BeginTx(ctx)
|
||||
if err != nil {
|
||||
// Error is expected in test environment without valid DB
|
||||
t.Logf("BeginTx returned error (expected in test env): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContextCancellation tests that cancelled context is handled
|
||||
func TestContextCancellation(t *testing.T) {
|
||||
// Create an already-cancelled context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // Cancel immediately
|
||||
|
||||
// Note: This test would require an actual database connection to verify
|
||||
// that the context cancellation is properly propagated. We're testing
|
||||
// that the context is accepted by the function signature.
|
||||
|
||||
// The actual behavior would be tested in integration tests
|
||||
// For now, we just verify the function doesn't panic with cancelled context
|
||||
|
||||
// Skip if no pool initialized (unit test environment)
|
||||
if poolPGX.Load() == nil {
|
||||
t.Skip("Skipping context cancellation test - requires initialized pool")
|
||||
}
|
||||
|
||||
// This would return an error about cancelled context in real scenario
|
||||
_, err := BeginTx(ctx)
|
||||
if err == nil {
|
||||
t.Log("BeginTx with cancelled context - error expected in real scenario")
|
||||
}
|
||||
}
|
||||
|
||||
// TestErrorTypes verifies exported error variables
|
||||
func TestErrorTypes(t *testing.T) {
|
||||
if ErrConnStringMissing == nil {
|
||||
t.Error("ErrConnStringMissing should be defined")
|
||||
}
|
||||
|
||||
if ErrInitTX == nil {
|
||||
t.Error("ErrInitTX should be defined")
|
||||
}
|
||||
|
||||
if ErrCommitTX == nil {
|
||||
t.Error("ErrCommitTX should be defined")
|
||||
}
|
||||
|
||||
if ErrNoRows == nil {
|
||||
t.Error("ErrNoRows should be defined")
|
||||
}
|
||||
|
||||
// Check error messages are descriptive
|
||||
if !strings.Contains(ErrConnStringMissing.Error(), "connection string") {
|
||||
t.Error("ErrConnStringMissing should mention connection string")
|
||||
}
|
||||
}
|
||||
|
||||
// TestStringPoolGetPut tests the string builder pool
|
||||
func TestStringPoolGetPut(t *testing.T) {
|
||||
// Get a string builder from pool
|
||||
sb1 := getSB()
|
||||
if sb1 == nil {
|
||||
t.Fatal("getSB should return non-nil string builder")
|
||||
}
|
||||
|
||||
// Use it
|
||||
sb1.WriteString("test")
|
||||
if sb1.String() != "test" {
|
||||
t.Error("String builder should work normally")
|
||||
}
|
||||
|
||||
// Put it back
|
||||
putSB(sb1)
|
||||
|
||||
// Get another one - should be reset
|
||||
sb2 := getSB()
|
||||
if sb2 == nil {
|
||||
t.Fatal("getSB should return non-nil string builder")
|
||||
}
|
||||
|
||||
// Should be empty after reset
|
||||
if sb2.Len() != 0 {
|
||||
t.Error("String builder from pool should be reset")
|
||||
}
|
||||
|
||||
putSB(sb2)
|
||||
}
|
||||
|
||||
// TestConditionActionTypes tests condition action enum
|
||||
func TestConditionActionTypes(t *testing.T) {
|
||||
// Verify enum values are distinct
|
||||
actions := []CondAction{
|
||||
CondActionNothing,
|
||||
CondActionNeedToClose,
|
||||
CondActionSubQuery,
|
||||
}
|
||||
|
||||
seen := make(map[CondAction]bool)
|
||||
for _, action := range actions {
|
||||
if seen[action] {
|
||||
t.Errorf("Duplicate CondAction value: %v", action)
|
||||
}
|
||||
seen[action] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TestQueryBuilderReuse documents query builder reuse behavior
|
||||
// This test verifies that query builders accumulate state and should NOT be reused
|
||||
func TestQueryBuilderReuse(t *testing.T) {
|
||||
t.Skip("Query builders are not designed for reuse - this test documents the limitation")
|
||||
|
||||
// This test would require actual database tables to demonstrate the issue
|
||||
// The behavior is:
|
||||
// 1. Creating a base query builder
|
||||
// 2. Adding a WHERE clause
|
||||
// 3. Reusing the same builder with another WHERE clause
|
||||
// 4. The second query would have BOTH WHERE clauses
|
||||
//
|
||||
// Example:
|
||||
// baseQuery := users.User.Select(user.ID, user.Email)
|
||||
// baseQuery.Where(user.ID.Eq(1)) // First query
|
||||
// baseQuery.Where(user.Status.Eq(2)) // Second query has BOTH conditions!
|
||||
//
|
||||
// This is by design - query builders are mutable and single-use.
|
||||
// Each query should create a new builder instance.
|
||||
}
|
||||
|
||||
// TestQueryBuilderThreadSafety documents that query builders are not thread-safe
|
||||
func TestQueryBuilderThreadSafety(t *testing.T) {
|
||||
t.Skip("Query builders are not thread-safe by design - this test documents the limitation")
|
||||
|
||||
// Query builders accumulate state in their internal fields (where, args, etc.)
|
||||
// and do not use any synchronization primitives.
|
||||
//
|
||||
// Sharing a query builder across goroutines will cause race conditions.
|
||||
//
|
||||
// CORRECT usage: Create a new query builder in each goroutine
|
||||
// INCORRECT usage: Share a query builder variable across goroutines
|
||||
//
|
||||
// The connection pool itself IS thread-safe and can be used concurrently.
|
||||
}
|
||||
|
||||
// TestSelectQueryBuilderBasics tests basic query building
|
||||
func TestSelectQueryBuilderBasics(t *testing.T) {
|
||||
// Create a test table
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
idField := Field("users.id")
|
||||
emailField := Field("users.email")
|
||||
|
||||
// Test basic select
|
||||
qry := testTable.Select(idField, emailField)
|
||||
sql := qry.String()
|
||||
|
||||
if !strings.Contains(sql, "SELECT") {
|
||||
t.Error("Query should contain SELECT")
|
||||
}
|
||||
if !strings.Contains(sql, "users.id") {
|
||||
t.Error("Query should contain users.id field")
|
||||
}
|
||||
if !strings.Contains(sql, "users.email") {
|
||||
t.Error("Query should contain users.email field")
|
||||
}
|
||||
if !strings.Contains(sql, "FROM users") {
|
||||
t.Error("Query should contain FROM users")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWhereConditionAccumulation tests that WHERE conditions accumulate
|
||||
func TestWhereConditionAccumulation(t *testing.T) {
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
idField := Field("users.id")
|
||||
statusField := Field("users.status")
|
||||
|
||||
// Create a query and add multiple WHERE conditions
|
||||
qry := testTable.Select(idField).
|
||||
Where(idField.Eq(1)).
|
||||
Where(statusField.Eq("active"))
|
||||
|
||||
sql := qry.String()
|
||||
|
||||
// Both conditions should be present
|
||||
if !strings.Contains(sql, "WHERE") {
|
||||
t.Error("Query should contain WHERE clause")
|
||||
}
|
||||
|
||||
// Should have multiple conditions (this demonstrates accumulation)
|
||||
if strings.Count(sql, "$") < 2 {
|
||||
t.Error("Query should have multiple parameterized values")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInsertQueryBuilder tests insert query building
|
||||
func TestInsertQueryBuilder(t *testing.T) {
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
|
||||
qry := testTable.Insert()
|
||||
|
||||
sql := qry.String()
|
||||
|
||||
if !strings.Contains(sql, "INSERT INTO users") {
|
||||
t.Error("Query should contain INSERT INTO users")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateQueryBuilder tests update query building
|
||||
func TestUpdateQueryBuilder(t *testing.T) {
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
idField := Field("users.id")
|
||||
|
||||
qry := testTable.Update().
|
||||
Where(idField.Eq(1))
|
||||
|
||||
sql := qry.String()
|
||||
|
||||
if !strings.Contains(sql, "UPDATE users") {
|
||||
t.Error("Query should contain UPDATE users")
|
||||
}
|
||||
if !strings.Contains(sql, "WHERE") {
|
||||
t.Error("Query should contain WHERE clause")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteQueryBuilder tests delete query building
|
||||
func TestDeleteQueryBuilder(t *testing.T) {
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
idField := Field("users.id")
|
||||
|
||||
qry := testTable.Delete().Where(idField.Eq(1))
|
||||
|
||||
sql := qry.String()
|
||||
|
||||
if !strings.Contains(sql, "DELETE FROM users") {
|
||||
t.Error("Query should contain DELETE FROM users")
|
||||
}
|
||||
if !strings.Contains(sql, "WHERE") {
|
||||
t.Error("Query should contain WHERE clause")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFieldConditioners tests various field condition methods
|
||||
func TestFieldConditioners(t *testing.T) {
|
||||
field := Field("users.age")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition Conditioner
|
||||
checkFunc func(string) bool
|
||||
}{
|
||||
{
|
||||
name: "Eq",
|
||||
condition: field.Eq(25),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " = $") },
|
||||
},
|
||||
{
|
||||
name: "NotEq",
|
||||
condition: field.NotEq(25),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " != $") },
|
||||
},
|
||||
{
|
||||
name: "Gt",
|
||||
condition: field.Gt(25),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " > $") },
|
||||
},
|
||||
{
|
||||
name: "Lt",
|
||||
condition: field.Lt(25),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " < $") },
|
||||
},
|
||||
{
|
||||
name: "Gte",
|
||||
condition: field.Gte(25),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " >= $") },
|
||||
},
|
||||
{
|
||||
name: "Lte",
|
||||
condition: field.Lte(25),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " <= $") },
|
||||
},
|
||||
{
|
||||
name: "IsNull",
|
||||
condition: field.IsNull(),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " IS NULL") },
|
||||
},
|
||||
{
|
||||
name: "IsNotNull",
|
||||
condition: field.IsNotNull(),
|
||||
checkFunc: func(s string) bool { return strings.Contains(s, " IS NOT NULL") },
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Build a simple query to see the condition
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
qry := testTable.Select(field).Where(tt.condition)
|
||||
sql := qry.String()
|
||||
|
||||
if !tt.checkFunc(sql) {
|
||||
t.Errorf("%s condition not properly rendered in SQL: %s", tt.name, sql)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFieldFunctions tests SQL function wrappers
|
||||
func TestFieldFunctions(t *testing.T) {
|
||||
field := Field("users.count")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
result Field
|
||||
contains string
|
||||
}{
|
||||
{
|
||||
name: "Count",
|
||||
result: field.Count(),
|
||||
contains: "COUNT(",
|
||||
},
|
||||
{
|
||||
name: "Sum",
|
||||
result: field.Sum(),
|
||||
contains: "SUM(",
|
||||
},
|
||||
{
|
||||
name: "Avg",
|
||||
result: field.Avg(),
|
||||
contains: "AVG(",
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
result: field.Max(),
|
||||
contains: "MAX(",
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
result: field.Min(),
|
||||
contains: "Min(",
|
||||
},
|
||||
{
|
||||
name: "Lower",
|
||||
result: field.Lower(),
|
||||
contains: "LOWER(",
|
||||
},
|
||||
{
|
||||
name: "Upper",
|
||||
result: field.Upper(),
|
||||
contains: "UPPER(",
|
||||
},
|
||||
{
|
||||
name: "Trim",
|
||||
result: field.Trim(),
|
||||
contains: "TRIM(",
|
||||
},
|
||||
{
|
||||
name: "Asc",
|
||||
result: field.Asc(),
|
||||
contains: " ASC",
|
||||
},
|
||||
{
|
||||
name: "Desc",
|
||||
result: field.Desc(),
|
||||
contains: " DESC",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resultStr := string(tt.result)
|
||||
if !strings.Contains(resultStr, tt.contains) {
|
||||
t.Errorf("%s should contain %q, got: %s", tt.name, tt.contains, resultStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestInsertQueryValidation tests that Insert queries validate fields are set
|
||||
func TestInsertQueryValidation(t *testing.T) {
|
||||
testTable := &Table{Name: "users", FieldCount: 10}
|
||||
idField := Field("users.id")
|
||||
|
||||
t.Run("Exec without fields", func(t *testing.T) {
|
||||
qry := testTable.Insert()
|
||||
err := qry.Exec(context.Background())
|
||||
if err == nil {
|
||||
t.Error("Insert.Exec() should return error when no fields set")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no fields to insert") {
|
||||
t.Errorf("Expected error about no fields, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ExecTx without fields", func(t *testing.T) {
|
||||
qry := testTable.Insert()
|
||||
// We can't test ExecTx without a real transaction, but we can test the error is defined
|
||||
err := qry.ExecTx(context.Background(), nil)
|
||||
if err == nil {
|
||||
t.Error("Insert.ExecTx() should return error when no fields set")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no fields to insert") {
|
||||
t.Errorf("Expected error about no fields, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("First without fields", func(t *testing.T) {
|
||||
qry := testTable.Insert().Returning(idField)
|
||||
var id int
|
||||
err := qry.First(context.Background(), &id)
|
||||
if err == nil {
|
||||
t.Error("Insert.First() should return error when no fields set")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no fields to insert") {
|
||||
t.Errorf("Expected error about no fields, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FirstTx without fields", func(t *testing.T) {
|
||||
qry := testTable.Insert().Returning(idField)
|
||||
var id int
|
||||
err := qry.FirstTx(context.Background(), nil, &id)
|
||||
if err == nil {
|
||||
t.Error("Insert.FirstTx() should return error when no fields set")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "no fields to insert") {
|
||||
t.Errorf("Expected error about no fields, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user