mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
test(contrib/drivers/mysql): add pagination and error handling tests (#4703)
## Summary - Add comprehensive pagination tests (Limit, Offset, Page, ForPage) - Add error handling tests for invalid operations - Add tests for edge cases and boundary conditions **Test coverage added:** - Pagination: ~28 test functions - Error handling: ~20 test functions Ref #4689 ## Test plan ```bash cd contrib/drivers/mysql go test -v -run "TestModel_Pagination|TestModel_Error|TestModel_InvalidOperation" ```
This commit is contained in:
@ -0,0 +1,489 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// Test_Model_Insert_NilData tests Insert with nil data
|
||||
func Test_Model_Insert_NilData(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(nil).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Insert_EmptyMap tests Insert with empty map
|
||||
func Test_Model_Insert_EmptyMap(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Map{}).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Insert_EmptySlice tests Insert with empty slice
|
||||
func Test_Model_Insert_EmptySlice(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Slice{}).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Update_NilData tests Update with nil data
|
||||
func Test_Model_Update_NilData(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(nil).Where("id", 1).Update()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Update_EmptyData tests Update with empty data
|
||||
func Test_Model_Update_EmptyData(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Data(g.Map{}).Where("id", 1).Update()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Update_NoWhere tests Update without WHERE clause is rejected by framework
|
||||
func Test_Model_Update_NoWhere(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Framework safety check: Update without WHERE should return error
|
||||
_, err := db.Model(table).Data(g.Map{"nickname": "updated"}).Update()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Delete_NoWhere tests Delete without WHERE clause is rejected by framework
|
||||
func Test_Model_Delete_NoWhere(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Framework safety check: Delete without WHERE should return error
|
||||
_, err := db.Model(table).Delete()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Scan_NilPointer tests Scan with nil pointer
|
||||
func Test_Model_Scan_NilPointer(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Model(table).Where("id", 1).Scan(nil)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Scan_InvalidPointer tests Scan with invalid pointer type
|
||||
func Test_Model_Scan_InvalidPointer(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var str string
|
||||
err := db.Model(table).Where("id", 1).Scan(&str)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Scan_EmptyResult tests Scan with empty result
|
||||
func Test_Model_Scan_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
}
|
||||
|
||||
// Scan initialized struct with empty result returns sql.ErrNoRows
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user User
|
||||
err := db.Model(table).Where("id > ?", 1000).Scan(&user)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
|
||||
// Scan nil pointer with empty result returns nil error
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user *User
|
||||
err := db.Model(table).Where("id > ?", 1000).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Where_InvalidOperator tests Where with invalid operator
|
||||
func Test_Model_Where_InvalidOperator(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Invalid SQL should cause error at query time
|
||||
_, err := db.Model(table).Where("id INVALID_OP ?", 1).All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Where_EmptyString tests Where with empty string
|
||||
func Test_Model_Where_EmptyString(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Where("").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize) // Empty WHERE returns all
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Fields_InvalidField tests Fields with non-existent field
|
||||
func Test_Model_Fields_InvalidField(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Fields("non_existent_field").All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Fields_Empty tests Fields with empty string
|
||||
// Regression test for #4697: Fields("") should handle empty string gracefully
|
||||
// https://github.com/gogf/gf/issues/4697
|
||||
func Test_Model_Fields_Empty(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Fields("").Limit(1).All()
|
||||
t.AssertNil(err)
|
||||
t.AssertLE(len(result), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Order_InvalidSyntax tests Order with invalid syntax
|
||||
func Test_Model_Order_InvalidSyntax(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Invalid ORDER BY syntax
|
||||
_, err := db.Model(table).Order("id INVALID").All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Group_UnknownColumn tests Group with non-existent column
|
||||
func Test_Model_Group_UnknownColumn(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model(table).Group("non_existent_field").All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_TableNotExist tests querying non-existent table
|
||||
func Test_Model_TableNotExist(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model("non_existent_table_xyz").All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_InvalidTableName tests invalid table name
|
||||
func Test_Model_InvalidTableName(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Empty table name
|
||||
_, err := db.Model("").All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SQLInjection_Where tests SQL injection prevention in Where
|
||||
func Test_Model_SQLInjection_Where(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Attempt SQL injection through string column parameter.
|
||||
// Using string column `nickname` instead of int column `id`,
|
||||
// because MySQL coerces "1 OR 1=1" to 1 for int columns.
|
||||
maliciousInput := "1 OR 1=1"
|
||||
result, err := db.Model(table).Where("nickname = ?", maliciousInput).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 0) // Should not return all records
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Attempt SQL injection with quotes, using string column to avoid
|
||||
// MySQL implicit int conversion (which would coerce "1'..." to 1)
|
||||
maliciousInput := "1'; DROP TABLE " + table + "; --"
|
||||
result, err := db.Model(table).Where("nickname = ?", maliciousInput).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 0)
|
||||
// Table should still exist
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SQLInjection_Insert tests SQL injection prevention in Insert
|
||||
func Test_Model_SQLInjection_Insert(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
maliciousData := g.Map{
|
||||
"id": 1,
|
||||
"passport": "'; DROP TABLE " + table + "; --",
|
||||
"password": "pwd",
|
||||
"nickname": "test",
|
||||
}
|
||||
_, err := db.Model(table).Data(maliciousData).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify data was inserted correctly and table still exists
|
||||
one, err := db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.AssertNE(one, nil)
|
||||
t.Assert(one["passport"].String(), "'; DROP TABLE "+table+"; --")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_SQLInjection_Update tests SQL injection prevention in Update
|
||||
func Test_Model_SQLInjection_Update(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Use shorter malicious string to fit in nickname column
|
||||
maliciousData := g.Map{
|
||||
"nickname": "'; DELETE FROM users; --",
|
||||
}
|
||||
_, err := db.Model(table).Data(maliciousData).Where("id", 1).Update()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Verify only one record was updated (parameterized query prevents injection)
|
||||
one, err := db.Model(table).Where("id", 1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["nickname"].String(), "'; DELETE FROM users; --")
|
||||
|
||||
// Other records should still exist (injection was prevented)
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Context_Cancelled tests query with cancelled context
|
||||
func Test_Model_Context_Cancelled(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // Cancel immediately
|
||||
|
||||
_, err := db.Model(table).Ctx(ctx).All()
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(gerror.Is(err, context.Canceled), true)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Value_EmptyResult tests Value with empty result
|
||||
func Test_Model_Value_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id > ?", 1000).Value()
|
||||
t.AssertNil(err)
|
||||
t.Assert(value.IsEmpty(), true)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Array_EmptyResult tests Array with empty result
|
||||
func Test_Model_Array_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array, err := db.Model(table).Where("id > ?", 1000).Array()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(array), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Count_InvalidTable tests Count on invalid table
|
||||
func Test_Model_Count_InvalidTable(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Model("non_existent_table").Count()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Max_EmptyResult tests Max with empty result
|
||||
func Test_Model_Max_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id > ?", 1000).Max("id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, 0) // Returns 0 for empty result
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Min_EmptyResult tests Min with empty result
|
||||
func Test_Model_Min_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id > ?", 1000).Min("id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, 0) // Returns 0 for empty result
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Avg_EmptyResult tests Avg with empty result
|
||||
func Test_Model_Avg_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id > ?", 1000).Avg("id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, 0) // Returns 0 for empty result
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Sum_EmptyResult tests Sum with empty result
|
||||
func Test_Model_Sum_EmptyResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Model(table).Where("id > ?", 1000).Sum("id")
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, 0) // Returns 0 for empty result
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_One_NilResult tests One returning nil
|
||||
func Test_Model_One_NilResult(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Model(table).Where("id > ?", 1000).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_TX_Rollback_AfterError tests transaction rollback after error
|
||||
func Test_TX_Rollback_AfterError(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// Insert valid record
|
||||
_, err := tx.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "pass1",
|
||||
"password": "pwd1",
|
||||
"nickname": "name1",
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert duplicate id (should fail)
|
||||
_, err = tx.Model(table).Data(g.Map{
|
||||
"id": 1, // Duplicate
|
||||
"passport": "pass2",
|
||||
"password": "pwd2",
|
||||
"nickname": "name2",
|
||||
}).Insert()
|
||||
|
||||
return err // Return error to trigger rollback
|
||||
})
|
||||
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
// Verify rollback - table should be empty
|
||||
count, err := db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Insert_DuplicateKey tests handling of duplicate key error
|
||||
func Test_Model_Insert_DuplicateKey(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "pass",
|
||||
"password": "pwd",
|
||||
"nickname": "name",
|
||||
}
|
||||
|
||||
// First insert should succeed
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
|
||||
// Second insert with same id should fail
|
||||
_, err = db.Model(table).Data(data).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_All_InvalidConnection tests query with invalid connection
|
||||
func Test_Model_All_InvalidConnection(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
if dbInvalid == nil {
|
||||
t.Skip("dbInvalid not configured")
|
||||
}
|
||||
_, err := dbInvalid.Model("test_table").All()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
545
contrib/drivers/mysql/mysql_z_unit_feature_pagination_test.go
Normal file
545
contrib/drivers/mysql/mysql_z_unit_feature_pagination_test.go
Normal file
@ -0,0 +1,545 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package mysql_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Test_Model_AllAndCount_Basic tests basic AllAndCount functionality
|
||||
func Test_Model_AllAndCount_Basic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).AllAndCount(true)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_AllAndCount_WithWhere tests AllAndCount with WHERE conditions
|
||||
func Test_Model_AllAndCount_WithWhere(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Where("id > ?", 5).AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 5)
|
||||
t.Assert(count, 5)
|
||||
t.Assert(result[0]["id"], 6)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Where("id", g.Slice{1, 2, 3}).AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(count, 3)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_AllAndCount_WithPage tests AllAndCount with pagination
|
||||
func Test_Model_AllAndCount_WithPage(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Page(1, 3).Order("id").AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(count, TableSize) // Count should be total, not page size
|
||||
t.Assert(result[0]["id"], 1)
|
||||
t.Assert(result[2]["id"], 3)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Page(2, 3).Order("id").AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(count, TableSize)
|
||||
t.Assert(result[0]["id"], 4)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_AllAndCount_WithFields tests AllAndCount with specific fields
|
||||
// Related: https://github.com/gogf/gf/issues/4698
|
||||
func Test_Model_AllAndCount_WithFields(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Fields("id, nickname").AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
t.Assert(len(result[0]), 2) // Only 2 fields
|
||||
})
|
||||
|
||||
// Regression test for #4698: AllAndCount(true) with multiple fields should work correctly
|
||||
// https://github.com/gogf/gf/issues/4698
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Fields("id, nickname").AllAndCount(true)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
t.Assert(len(result[0]), 2)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_AllAndCount_Empty tests AllAndCount with no results
|
||||
func Test_Model_AllAndCount_Empty(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Where("id > ?", 1000).AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 0)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Where("id < ?", 0).AllAndCount(true)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 0)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_AllAndCount_WithCache tests AllAndCount with cache
|
||||
func Test_Model_AllAndCount_WithCache(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result1, count1, err := db.Model(table).PageCache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}, gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Page(1, 5).AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result1), 5)
|
||||
t.Assert(count1, TableSize)
|
||||
|
||||
// Second call should use cache
|
||||
result2, count2, err := db.Model(table).PageCache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}, gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Page(1, 5).AllAndCount(false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result2), 5)
|
||||
t.Assert(count2, count1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_AllAndCount_Distinct tests AllAndCount with DISTINCT
|
||||
func Test_Model_AllAndCount_Distinct(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// Insert duplicate nicknames
|
||||
for i := 1; i <= 10; i++ {
|
||||
nickname := "name_" + gconv.String((i-1)/2) // Creates duplicates
|
||||
db.Model(table).Data(g.Map{
|
||||
"id": i,
|
||||
"passport": "pass_" + gconv.String(i),
|
||||
"password": "pwd",
|
||||
"nickname": nickname,
|
||||
}).Insert()
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, count, err := db.Model(table).Fields("DISTINCT nickname").AllAndCount(true)
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 5) // 10 records / 2 = 5 distinct nicknames
|
||||
t.Assert(len(result), 5)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_Basic tests basic ScanAndCount functionality
|
||||
func Test_Model_ScanAndCount_Basic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
var count int
|
||||
err := db.Model(table).ScanAndCount(&users, &count, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
var count int
|
||||
err := db.Model(table).ScanAndCount(&users, &count, true)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_WithWhere tests ScanAndCount with WHERE conditions
|
||||
func Test_Model_ScanAndCount_WithWhere(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Nickname string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
var count int
|
||||
err := db.Model(table).Where("id <= ?", 5).ScanAndCount(&users, &count, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 5)
|
||||
t.Assert(count, 5)
|
||||
t.Assert(users[0].Id, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_WithPage tests ScanAndCount with pagination
|
||||
func Test_Model_ScanAndCount_WithPage(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Nickname string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
var count int
|
||||
err := db.Model(table).Page(2, 3).Order("id").ScanAndCount(&users, &count, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 3)
|
||||
t.Assert(count, TableSize) // Total count, not page count
|
||||
t.Assert(users[0].Id, 4)
|
||||
t.Assert(users[2].Id, 6)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_Single tests ScanAndCount for single record
|
||||
func Test_Model_ScanAndCount_Single(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user User
|
||||
var count int
|
||||
err := db.Model(table).Where("id", 1).ScanAndCount(&user, &count, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_Empty tests ScanAndCount with no results
|
||||
func Test_Model_ScanAndCount_Empty(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
var count int
|
||||
err := db.Model(table).Where("id > ?", 1000).ScanAndCount(&users, &count, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), 0)
|
||||
t.Assert(count, 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_WithFields tests ScanAndCount with specific fields
|
||||
func Test_Model_ScanAndCount_WithFields(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Nickname string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []User
|
||||
var count int
|
||||
err := db.Model(table).Fields("id, nickname").ScanAndCount(&users, &count, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users), TableSize)
|
||||
t.Assert(count, TableSize)
|
||||
t.Assert(users[0].Id > 0, true)
|
||||
t.AssertNE(users[0].Nickname, "")
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_ScanAndCount_WithCache tests ScanAndCount with cache
|
||||
func Test_Model_ScanAndCount_WithCache(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users1 []User
|
||||
var count1 int
|
||||
err := db.Model(table).PageCache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}, gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Page(1, 5).ScanAndCount(&users1, &count1, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users1), 5)
|
||||
t.Assert(count1, TableSize)
|
||||
|
||||
// Second call should use cache
|
||||
var users2 []User
|
||||
var count2 int
|
||||
err = db.Model(table).PageCache(gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}, gdb.CacheOption{
|
||||
Duration: time.Second * 10,
|
||||
Force: false,
|
||||
}).Page(1, 5).ScanAndCount(&users2, &count2, false)
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(users2), 5)
|
||||
t.Assert(count2, count1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Chunk_Basic tests basic Chunk functionality
|
||||
func Test_Model_Chunk_Basic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
total int
|
||||
chunks int
|
||||
)
|
||||
db.Model(table).Order("id").Chunk(3, func(result gdb.Result, err error) bool {
|
||||
t.AssertNil(err)
|
||||
chunks++
|
||||
total += len(result)
|
||||
return true
|
||||
})
|
||||
t.Assert(chunks, 4) // 10 records / 3 = 4 chunks (3+3+3+1)
|
||||
t.Assert(total, TableSize)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Chunk_StopEarly tests Chunk with early stop
|
||||
func Test_Model_Chunk_StopEarly(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var chunks int
|
||||
db.Model(table).Order("id").Chunk(3, func(result gdb.Result, err error) bool {
|
||||
t.AssertNil(err)
|
||||
chunks++
|
||||
return chunks < 2 // Stop after 2nd chunk
|
||||
})
|
||||
t.Assert(chunks, 2)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Chunk_WithWhere tests Chunk with WHERE conditions
|
||||
func Test_Model_Chunk_WithWhere(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
total int
|
||||
chunks int
|
||||
)
|
||||
db.Model(table).Where("id <= ?", 5).Order("id").Chunk(2, func(result gdb.Result, err error) bool {
|
||||
t.AssertNil(err)
|
||||
chunks++
|
||||
total += len(result)
|
||||
return true
|
||||
})
|
||||
t.Assert(chunks, 3) // 5 records / 2 = 3 chunks (2+2+1)
|
||||
t.Assert(total, 5)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Chunk_ErrorHandling tests Chunk error handling
|
||||
func Test_Model_Chunk_ErrorHandling(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var errorReceived bool
|
||||
db.Model("non_existent_table").Chunk(10, func(result gdb.Result, err error) bool {
|
||||
if err != nil {
|
||||
errorReceived = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
t.Assert(errorReceived, true)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Chunk_Empty tests Chunk with no results
|
||||
func Test_Model_Chunk_Empty(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var chunks int
|
||||
db.Model(table).Where("id > ?", 1000).Chunk(10, func(result gdb.Result, err error) bool {
|
||||
chunks++
|
||||
return true
|
||||
})
|
||||
t.Assert(chunks, 0) // No chunks for empty result
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Page_Boundary tests Page with boundary values
|
||||
// Related: https://github.com/gogf/gf/issues/4699
|
||||
func Test_Model_Page_Boundary(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// Page 0 should be treated as page 1
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Page(0, 3).Order("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
})
|
||||
|
||||
// Negative page should be treated as page 1
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Page(-1, 3).Order("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
})
|
||||
|
||||
// Size 0: framework treats limit=0 as "no limit", returns all records
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Page(1, 0).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
})
|
||||
|
||||
// Negative size: normalized to 0, same as Page(1, 0)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Page(1, -1).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
})
|
||||
|
||||
// Very large page number (beyond available data)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Page(100, 3).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Limit_Boundary tests Limit with boundary values
|
||||
// Related: https://github.com/gogf/gf/issues/4699
|
||||
func Test_Model_Limit_Boundary(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// Limit 0: framework treats limit=0 as "no limit", returns all records
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Limit(0).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
})
|
||||
|
||||
// Negative limit: normalized to 0, same as Limit(0)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Limit(-1).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
})
|
||||
|
||||
// Limit larger than available data
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Limit(1000).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize)
|
||||
})
|
||||
|
||||
// Limit(offset, size): offset=5 skips 5 rows, size=100 takes up to 100
|
||||
// With 10 rows total, skipping 5 returns remaining 5 rows
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Limit(5, 100).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), TableSize-5)
|
||||
})
|
||||
|
||||
// Offset beyond data: returns empty result
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Limit(100, 5).All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_Model_Page_Limit_Combination tests Page and Limit used together
|
||||
func Test_Model_Page_Limit_Combination(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Page should override Limit
|
||||
result, err := db.Model(table).Limit(5).Page(1, 3).Order("id").All()
|
||||
t.AssertNil(err)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"], 1)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user