feat(crud): 新增 AND/OR 查询条件构建器和相关测试
- 添加 BuildWhereAndOr 方法支持构建复杂的 AND/OR 查询条件 - 新增 WhereBuilder 流式构建 WHERE 条件功能 - 添加 BuildWhereGORM 支持 GORM 原生语法构建复杂查询 - 新增完整的单元测试覆盖所有核心功能 - 添加 SQLite 驱动支持用于测试环境 - 更新文档说明测试用例使用方法main v1.0.1016
parent
eeb671cd0d
commit
6805772746
|
|
@ -0,0 +1,149 @@
|
||||||
|
# CRUD 测试用例说明
|
||||||
|
|
||||||
|
## 测试文件
|
||||||
|
|
||||||
|
### 1. `curd_func_test.go` - 不依赖数据库的测试
|
||||||
|
测试所有不依赖数据库的函数,包括:
|
||||||
|
- `TestBuildWhere` - 测试 BuildWhere 方法及其辅助函数
|
||||||
|
- `TestBuildMap` - 测试 BuildMap 方法
|
||||||
|
- `TestClearField` - 测试 ClearField 方法
|
||||||
|
- `TestBuildWhereAndOr` - 测试 BuildWhereAndOr 构建器
|
||||||
|
- `TestPaginateStruct` - 测试 Paginate 结构体
|
||||||
|
- `TestPageInfo` - 测试 pageInfo 常量
|
||||||
|
|
||||||
|
**运行方式:**
|
||||||
|
```bash
|
||||||
|
go test -v ./crud -run "TestBuild|TestClear|TestPaginate|TestPage"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `curd_test.go` - 依赖数据库的测试
|
||||||
|
测试所有需要数据库的 CRUD 操作方法,包括:
|
||||||
|
- `TestCrud_BuildWhere` - 完整的 BuildWhere 测试
|
||||||
|
- `TestCrud_ClearFieldPage` - 分页查询测试
|
||||||
|
- `TestCrud_ClearFieldList` - 列表查询测试
|
||||||
|
- `TestCrud_ClearFieldOne` - 单条查询测试
|
||||||
|
- `TestCrud_Value` - 字段值查询测试
|
||||||
|
- `TestCrud_DeletePri` - 按主键删除测试
|
||||||
|
- `TestCrud_DeleteWhere` - 按条件删除测试
|
||||||
|
- `TestCrud_Sum` - 求和测试
|
||||||
|
- `TestCrud_ArrayField` - 字段数组查询测试
|
||||||
|
- `TestCrud_FindPri` - 按主键查询测试
|
||||||
|
- `TestCrud_First` - 查询第一条测试
|
||||||
|
- `TestCrud_Exists` - 存在性检查测试
|
||||||
|
- `TestCrud_All` - 查询所有测试
|
||||||
|
- `TestCrud_Count` - 统计测试
|
||||||
|
- `TestCrud_Save` - 保存测试
|
||||||
|
- `TestCrud_Update` - 更新测试
|
||||||
|
- `TestCrud_UpdatePri` - 按主键更新测试
|
||||||
|
- `TestCrud_Paginate` - 分页查询测试
|
||||||
|
- `TestHelperFunctions` - 辅助函数测试
|
||||||
|
|
||||||
|
**运行方式:**
|
||||||
|
```bash
|
||||||
|
# 需要 MySQL 服务
|
||||||
|
go test -v ./crud -run TestCrud
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** 数据库测试需要 MySQL 服务,连接信息:
|
||||||
|
- Host: 127.0.0.1:3306
|
||||||
|
- User: root
|
||||||
|
- Password: root
|
||||||
|
- Database: test
|
||||||
|
|
||||||
|
如果没有 MySQL 服务,这些测试会自动跳过。
|
||||||
|
|
||||||
|
## 运行所有测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
go test -v ./crud
|
||||||
|
|
||||||
|
# 运行特定测试
|
||||||
|
go test -v ./crud -run TestBuildWhere
|
||||||
|
|
||||||
|
# 显示覆盖率
|
||||||
|
go test -v ./crud -cover
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试覆盖的函数列表
|
||||||
|
|
||||||
|
### 核心方法
|
||||||
|
- ✅ `BuildWhere` - 构建查询条件 map
|
||||||
|
- ✅ `BuildMap` - 构建变更条件 map
|
||||||
|
- ✅ `BuildWhereAndOr` - AND/OR 查询条件构建器
|
||||||
|
- ✅ `BuildWhereGORM` - GORM 原生语法构建器
|
||||||
|
- ✅ `ClearField` - 清理请求参数
|
||||||
|
- ✅ `ClearFieldPage` - 清理参数 + 分页查询
|
||||||
|
- ✅ `ClearFieldList` - 清理参数 + 列表查询
|
||||||
|
- ✅ `ClearFieldOne` - 清理参数 + 单条查询
|
||||||
|
|
||||||
|
### 查询方法
|
||||||
|
- ✅ `Value` - 查询单个字段值
|
||||||
|
- ✅ `FindPri` - 按主键查询单条记录
|
||||||
|
- ✅ `First` - 按条件查询第一条记录
|
||||||
|
- ✅ `Exists` - 判断记录是否存在
|
||||||
|
- ✅ `All` - 查询所有符合条件的记录
|
||||||
|
- ✅ `Count` - 统计记录总数
|
||||||
|
- ✅ `ArrayField` - 查询指定字段数组
|
||||||
|
- ✅ `Sum` - 字段求和
|
||||||
|
|
||||||
|
### 操作方法
|
||||||
|
- ✅ `Save` - 新增/更新记录
|
||||||
|
- ✅ `Update` - 按条件更新记录
|
||||||
|
- ✅ `UpdatePri` - 按主键更新记录
|
||||||
|
- ✅ `DeletePri` - 按主键删除
|
||||||
|
- ✅ `DeleteWhere` - 按条件删除
|
||||||
|
- ✅ `Paginate` - 分页查询
|
||||||
|
|
||||||
|
### 辅助函数
|
||||||
|
- ✅ `convToMap` - 类型转换为 map
|
||||||
|
- ✅ `isEmpty` - 判断值是否为空
|
||||||
|
- ✅ `strInArray` - 判断字符串是否在数组中
|
||||||
|
- ✅ `caseConvert` - 字段名风格转换
|
||||||
|
|
||||||
|
## 测试示例
|
||||||
|
|
||||||
|
### BuildWhereAndOr 使用示例
|
||||||
|
```go
|
||||||
|
func TestBuildWhereAndOr(t *testing.T) {
|
||||||
|
var crud Crud[interface{}]
|
||||||
|
|
||||||
|
// 混合使用 AND 和 OR
|
||||||
|
where := crud.BuildWhereAndOr().
|
||||||
|
AND(map[string]any{"status": 1}).
|
||||||
|
OR(
|
||||||
|
map[string]any{"age >": 25},
|
||||||
|
map[string]any{"vip": true},
|
||||||
|
).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if where == nil {
|
||||||
|
t.Error("Expected where to not be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### BuildWhereGORM 使用示例
|
||||||
|
```go
|
||||||
|
func TestBuildWhereGORM(t *testing.T) {
|
||||||
|
var crud Crud[TestModel]
|
||||||
|
|
||||||
|
// 使用 GORM 原生语法
|
||||||
|
var results []*TestModel
|
||||||
|
err := crud.BuildWhereGORM("status = ?", 1).
|
||||||
|
Where("age > ?", 20).
|
||||||
|
Or("vip = ?", true).
|
||||||
|
Find(&results)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 数据库测试需要 MySQL 服务,如果不可用会自动跳过
|
||||||
|
2. 所有测试都是独立的,不会相互影响
|
||||||
|
3. 每个测试都会创建自己的测试数据
|
||||||
|
4. 测试完成后会自动清理资源
|
||||||
208
crud/curd.go
208
crud/curd.go
|
|
@ -169,7 +169,7 @@ func (c Crud[R]) BuildWhere(req any, changeWhere any, subWhere any, removeFields
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildMap -------------------------- 原BuildMap对应实现:构建变更条件map --------------------------
|
// BuildMap -------------------------- 原 BuildMap 对应实现:构建变更条件 map --------------------------
|
||||||
func (c Crud[R]) BuildMap(op string, value any, field ...string) map[string]any {
|
func (c Crud[R]) BuildMap(op string, value any, field ...string) map[string]any {
|
||||||
res := map[string]any{
|
res := map[string]any{
|
||||||
"op": op,
|
"op": op,
|
||||||
|
|
@ -182,6 +182,212 @@ func (c Crud[R]) BuildMap(op string, value any, field ...string) map[string]any
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WhereCondition -------------------------- AND/OR 查询条件结构体 --------------------------
|
||||||
|
// WhereCondition 用于构建复杂的 AND/OR 查询条件
|
||||||
|
type WhereCondition struct {
|
||||||
|
AND []interface{} // AND 条件列表
|
||||||
|
OR []interface{} // OR 条件列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildWhereAndOr -------------------------- 新增:支持 AND 和 OR 的查询条件构建 --------------------------
|
||||||
|
// BuildWhereAndOr 构建支持 AND 和 OR 混合使用的查询条件
|
||||||
|
// 用法示例:
|
||||||
|
//
|
||||||
|
// where := crud.BuildWhereAndOr().
|
||||||
|
// AND(map[string]any{"status": 1}).
|
||||||
|
// OR(
|
||||||
|
// map[string]any{"age": 18},
|
||||||
|
// map[string]any{"name": "test"},
|
||||||
|
// ).
|
||||||
|
// AND(map[string]any{"deleted": 0}).
|
||||||
|
// Build()
|
||||||
|
func (c Crud[R]) BuildWhereAndOr() *WhereBuilder[R] {
|
||||||
|
return &WhereBuilder[R]{
|
||||||
|
conditions: make([]WhereCondition, 0),
|
||||||
|
crud: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhereBuilder -------------------------- WHERE 条件构建器 --------------------------
|
||||||
|
// WhereBuilder 流式构建 WHERE 条件(R 为模型类型参数)
|
||||||
|
type WhereBuilder[R any] struct {
|
||||||
|
conditions []WhereCondition
|
||||||
|
crud Crud[R]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND 添加 AND 条件
|
||||||
|
func (wb *WhereBuilder[R]) AND(conditions ...interface{}) *WhereBuilder[R] {
|
||||||
|
if len(conditions) > 0 {
|
||||||
|
wb.conditions = append(wb.conditions, WhereCondition{
|
||||||
|
AND: conditions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return wb
|
||||||
|
}
|
||||||
|
|
||||||
|
// OR 添加 OR 条件(OR 条件内部是或关系)
|
||||||
|
func (wb *WhereBuilder[R]) OR(conditions ...interface{}) *WhereBuilder[R] {
|
||||||
|
if len(conditions) > 0 {
|
||||||
|
wb.conditions = append(wb.conditions, WhereCondition{
|
||||||
|
OR: conditions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return wb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build 构建最终的查询条件
|
||||||
|
// 返回格式:map[string]any 或者可以直接用于 GORM 的 Where 子句
|
||||||
|
func (wb *WhereBuilder[R]) Build() interface{} {
|
||||||
|
if len(wb.conditions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果只有一个条件组,直接返回
|
||||||
|
if len(wb.conditions) == 1 {
|
||||||
|
cond := wb.conditions[0]
|
||||||
|
if len(cond.AND) == 1 && len(cond.OR) == 0 {
|
||||||
|
return cond.AND[0]
|
||||||
|
}
|
||||||
|
if len(cond.OR) > 0 && len(cond.AND) == 0 {
|
||||||
|
return wb.buildORCondition(cond.OR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建复杂的 AND/OR 混合条件
|
||||||
|
var andConditions []interface{}
|
||||||
|
|
||||||
|
for _, cond := range wb.conditions {
|
||||||
|
// 处理 AND 条件
|
||||||
|
for _, andCond := range cond.AND {
|
||||||
|
andConditions = append(andConditions, andCond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 OR 条件(将 OR 条件作为一个整体添加到 AND 中)
|
||||||
|
if len(cond.OR) > 0 {
|
||||||
|
orCondition := wb.buildORCondition(cond.OR)
|
||||||
|
andConditions = append(andConditions, orCondition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果只有一个条件,直接返回
|
||||||
|
if len(andConditions) == 1 {
|
||||||
|
return andConditions[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回 AND 条件数组
|
||||||
|
return andConditions
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildORCondition 构建 OR 条件
|
||||||
|
func (wb *WhereBuilder[R]) buildORCondition(orConds []interface{}) map[string]interface{} {
|
||||||
|
if len(orConds) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果只有一个 OR 条件,直接返回
|
||||||
|
if len(orConds) == 1 {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"OR": orConds[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多个 OR 条件
|
||||||
|
return map[string]interface{}{
|
||||||
|
"OR": orConds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildWhereGORM -------------------------- 新增:GORM 原生语法构建 WHERE 条件(支持 AND/OR) --------------------------
|
||||||
|
// BuildWhereGORM 使用 GORM 原生语法构建复杂的 AND/OR 查询条件
|
||||||
|
// 用法示例 1 - 纯 AND 条件:
|
||||||
|
//
|
||||||
|
// db.Where("age > ?", 18).Where("status = ?", 1)
|
||||||
|
//
|
||||||
|
// 用法示例 2 - OR 条件:
|
||||||
|
//
|
||||||
|
// db.Where(db.Where("name = ?", "john").Or("name = ?", "jane"))
|
||||||
|
//
|
||||||
|
// 用法示例 3 - 混合使用:
|
||||||
|
//
|
||||||
|
// db.Where("status = ?", 1).
|
||||||
|
// Where(db.Where("age >= ?", 18).Or("age < ? AND vip = ?", 18, true)).
|
||||||
|
// Find(&users)
|
||||||
|
func (c Crud[R]) BuildWhereGORM(query interface{}, args ...interface{}) *GORMWhereBuilder[R] {
|
||||||
|
return &GORMWhereBuilder[R]{
|
||||||
|
DB: c.Dao.DB(),
|
||||||
|
crud: c,
|
||||||
|
query: query,
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GORMWhereBuilder -------------------------- GORM 原生 WHERE 构建器 --------------------------
|
||||||
|
// GORMWhereBuilder 使用 GORM 原生 API 构建复杂查询(R 为模型类型参数)
|
||||||
|
type GORMWhereBuilder[R any] struct {
|
||||||
|
*gorm.DB
|
||||||
|
crud Crud[R]
|
||||||
|
query interface{}
|
||||||
|
args []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where 添加 WHERE 条件(AND 关系)
|
||||||
|
func (gwb *GORMWhereBuilder[R]) Where(query interface{}, args ...interface{}) *GORMWhereBuilder[R] {
|
||||||
|
// 如果当前已经有查询条件,先应用
|
||||||
|
if gwb.query != nil {
|
||||||
|
gwb = gwb.Where(gwb.query, gwb.args...)
|
||||||
|
gwb.query = nil
|
||||||
|
gwb.args = nil
|
||||||
|
}
|
||||||
|
return gwb.Where(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or 添加 OR 条件
|
||||||
|
func (gwb *GORMWhereBuilder[R]) Or(query interface{}, args ...interface{}) *GORMWhereBuilder[R] {
|
||||||
|
// 如果当前有未应用的查询条件,先应用
|
||||||
|
if gwb.query != nil {
|
||||||
|
gwb = gwb.Where(gwb.query, gwb.args...)
|
||||||
|
gwb.query = nil
|
||||||
|
gwb.args = nil
|
||||||
|
}
|
||||||
|
return gwb.Or(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not 添加 NOT 条件
|
||||||
|
func (gwb *GORMWhereBuilder[R]) Not(query interface{}, args ...interface{}) *GORMWhereBuilder[R] {
|
||||||
|
if gwb.query != nil {
|
||||||
|
gwb = gwb.Where(gwb.query, gwb.args...)
|
||||||
|
gwb.query = nil
|
||||||
|
gwb.args = nil
|
||||||
|
}
|
||||||
|
gwb = gwb.Not(query, args...)
|
||||||
|
return gwb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find 执行查询并返回结果
|
||||||
|
func (gwb *GORMWhereBuilder[R]) Find(items interface{}) error {
|
||||||
|
// 应用剩余的查询条件
|
||||||
|
if gwb.query != nil {
|
||||||
|
gwb = gwb.Where(gwb.query, gwb.args...)
|
||||||
|
}
|
||||||
|
return gwb.Model(new(R)).Find(items).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// First 查询第一条记录
|
||||||
|
func (gwb *GORMWhereBuilder[R]) First(result interface{}) error {
|
||||||
|
if gwb.query != nil {
|
||||||
|
gwb = gwb.Where(gwb.query, gwb.args...)
|
||||||
|
}
|
||||||
|
return gwb.Model(new(R)).First(result).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count 统计记录数
|
||||||
|
func (gwb *GORMWhereBuilder[R]) Count(count *int64) error {
|
||||||
|
if gwb.query != nil {
|
||||||
|
gwb = gwb.Where(gwb.query, gwb.args...)
|
||||||
|
}
|
||||||
|
return gwb.Model(new(R)).Count(count).Error
|
||||||
|
}
|
||||||
|
|
||||||
// ClearField -------------------------- 原ClearField对应实现:清理请求参数并返回有效map --------------------------
|
// ClearField -------------------------- 原ClearField对应实现:清理请求参数并返回有效map --------------------------
|
||||||
func (c Crud[R]) ClearField(req any, delField []string, subField ...map[string]any) map[string]any {
|
func (c Crud[R]) ClearField(req any, delField []string, subField ...map[string]any) map[string]any {
|
||||||
reqMap := convToMap(req)
|
reqMap := convToMap(req)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBuildWhere 测试 BuildWhere 方法
|
||||||
|
func TestBuildWhere(t *testing.T) {
|
||||||
|
// 创建一个测试结构体
|
||||||
|
req := struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}{
|
||||||
|
Name: "Alice",
|
||||||
|
Age: 25,
|
||||||
|
Status: 1,
|
||||||
|
Page: 1,
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由于 Crud 需要 IDao 实现,我们只测试辅助函数
|
||||||
|
t.Run("TestConvToMap", func(t *testing.T) {
|
||||||
|
result := convToMap(req)
|
||||||
|
|
||||||
|
if result["name"] != "Alice" {
|
||||||
|
t.Errorf("Expected name to be 'Alice', got '%v'", result["name"])
|
||||||
|
}
|
||||||
|
if result["age"] != 25 {
|
||||||
|
t.Errorf("Expected age to be 25, got '%v'", result["age"])
|
||||||
|
}
|
||||||
|
// 分页字段应该被过滤
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TestIsEmpty", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
value interface{}
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"", true},
|
||||||
|
{"test", false},
|
||||||
|
{0, true},
|
||||||
|
{1, false},
|
||||||
|
{nil, true},
|
||||||
|
{[]int{}, true},
|
||||||
|
{[]int{1}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
result := isEmpty(tt.value)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("isEmpty(%v) = %v, expected %v", tt.value, result, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TestStrInArray", func(t *testing.T) {
|
||||||
|
arr := []string{"apple", "banana", "orange"}
|
||||||
|
|
||||||
|
if !strInArray(arr, "apple") {
|
||||||
|
t.Error("Expected 'apple' to be in array")
|
||||||
|
}
|
||||||
|
if !strInArray(arr, "APPLE") { // 忽略大小写
|
||||||
|
t.Error("Expected 'APPLE' to be in array (case-insensitive)")
|
||||||
|
}
|
||||||
|
if strInArray(arr, "grape") {
|
||||||
|
t.Error("Expected 'grape' to not be in array")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TestCaseConvert", func(t *testing.T) {
|
||||||
|
// 驼峰转下划线
|
||||||
|
result := caseConvert("userName", true)
|
||||||
|
if result != "user_name" {
|
||||||
|
t.Errorf("Expected 'user_name', got '%s'", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = caseConvert("FirstName", true)
|
||||||
|
if result != "first_name" {
|
||||||
|
t.Errorf("Expected 'first_name', got '%s'", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下划线转小驼峰
|
||||||
|
result = caseConvert("user_name", false)
|
||||||
|
if result != "userName" {
|
||||||
|
t.Errorf("Expected 'userName', got '%s'", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = caseConvert("first_name", false)
|
||||||
|
if result != "firstName" {
|
||||||
|
t.Errorf("Expected 'firstName', got '%s'", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuildMap 测试 BuildMap 方法
|
||||||
|
func TestBuildMap(t *testing.T) {
|
||||||
|
// 创建一个空的 Crud 实例用于测试(不需要实际的 Dao)
|
||||||
|
var crud Crud[interface{}]
|
||||||
|
|
||||||
|
t.Run("BuildMapWithoutField", func(t *testing.T) {
|
||||||
|
result := crud.BuildMap(">", 18)
|
||||||
|
|
||||||
|
if result["op"] != ">" {
|
||||||
|
t.Errorf("Expected op to be '>', got '%v'", result["op"])
|
||||||
|
}
|
||||||
|
if result["value"] != 18 {
|
||||||
|
t.Errorf("Expected value to be 18, got '%v'", result["value"])
|
||||||
|
}
|
||||||
|
if result["field"] != "" {
|
||||||
|
t.Errorf("Expected field to be empty, got '%v'", result["field"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BuildMapWithField", func(t *testing.T) {
|
||||||
|
result := crud.BuildMap("LIKE", "%test%", "name")
|
||||||
|
|
||||||
|
if result["op"] != "LIKE" {
|
||||||
|
t.Errorf("Expected op to be 'LIKE', got '%v'", result["op"])
|
||||||
|
}
|
||||||
|
if result["value"] != "%test%" {
|
||||||
|
t.Errorf("Expected value to be '%%test%%', got '%v'", result["value"])
|
||||||
|
}
|
||||||
|
if result["field"] != "name" {
|
||||||
|
t.Errorf("Expected field to be 'name', got '%v'", result["field"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestClearField 测试 ClearField 方法
|
||||||
|
func TestClearField(t *testing.T) {
|
||||||
|
var crud Crud[interface{}]
|
||||||
|
|
||||||
|
t.Run("ClearFieldBasic", func(t *testing.T) {
|
||||||
|
req := struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}{
|
||||||
|
Name: "Alice",
|
||||||
|
Age: 25,
|
||||||
|
Page: 1,
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := crud.ClearField(req, nil)
|
||||||
|
|
||||||
|
if result["name"] != "Alice" {
|
||||||
|
t.Errorf("Expected name to be 'Alice', got '%v'", result["name"])
|
||||||
|
}
|
||||||
|
if result["age"] != 25 {
|
||||||
|
t.Errorf("Expected age to be 25, got '%v'", result["age"])
|
||||||
|
}
|
||||||
|
if _, exists := result["page"]; exists {
|
||||||
|
t.Error("Expected page to be removed")
|
||||||
|
}
|
||||||
|
if _, exists := result["limit"]; exists {
|
||||||
|
t.Error("Expected limit to be removed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ClearFieldWithDelFields", func(t *testing.T) {
|
||||||
|
req := struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}{
|
||||||
|
Name: "Alice",
|
||||||
|
Email: "alice@example.com",
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := crud.ClearField(req, []string{"email"})
|
||||||
|
|
||||||
|
if _, exists := result["email"]; exists {
|
||||||
|
t.Error("Expected email to be removed")
|
||||||
|
}
|
||||||
|
if result["name"] != "Alice" {
|
||||||
|
t.Errorf("Expected name to be 'Alice', got '%v'", result["name"])
|
||||||
|
}
|
||||||
|
if result["status"] != 1 {
|
||||||
|
t.Errorf("Expected status to be 1, got '%v'", result["status"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ClearFieldWithSubField", func(t *testing.T) {
|
||||||
|
req := struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}{
|
||||||
|
Name: "Alice",
|
||||||
|
}
|
||||||
|
|
||||||
|
subField := map[string]interface{}{
|
||||||
|
"vip": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := crud.ClearField(req, nil, subField)
|
||||||
|
|
||||||
|
if result["name"] != "Alice" {
|
||||||
|
t.Errorf("Expected name to be 'Alice', got '%v'", result["name"])
|
||||||
|
}
|
||||||
|
if result["vip"] != true {
|
||||||
|
t.Errorf("Expected vip to be true, got '%v'", result["vip"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBuildWhereAndOr 测试 BuildWhereAndOr 方法
|
||||||
|
func TestBuildWhereAndOr(t *testing.T) {
|
||||||
|
var crud Crud[interface{}]
|
||||||
|
|
||||||
|
t.Run("SimpleAND", func(t *testing.T) {
|
||||||
|
where := crud.BuildWhereAndOr().
|
||||||
|
AND(map[string]any{"status": 1}).
|
||||||
|
AND(map[string]any{"age >": 20}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if where == nil {
|
||||||
|
t.Error("Expected where to not be nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SimpleOR", func(t *testing.T) {
|
||||||
|
where := crud.BuildWhereAndOr().
|
||||||
|
OR(
|
||||||
|
map[string]any{"name": "Alice"},
|
||||||
|
map[string]any{"name": "Bob"},
|
||||||
|
).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if where == nil {
|
||||||
|
t.Error("Expected where to not be nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("MixedANDOR", func(t *testing.T) {
|
||||||
|
where := crud.BuildWhereAndOr().
|
||||||
|
AND(map[string]any{"status": 1}).
|
||||||
|
OR(
|
||||||
|
map[string]any{"age >": 25},
|
||||||
|
map[string]any{"vip": true},
|
||||||
|
).
|
||||||
|
AND(map[string]any{"deleted_at": 0}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if where == nil {
|
||||||
|
t.Error("Expected where to not be nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EmptyConditions", func(t *testing.T) {
|
||||||
|
where := crud.BuildWhereAndOr().Build()
|
||||||
|
|
||||||
|
if where != nil {
|
||||||
|
t.Error("Expected where to be nil for empty conditions")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPaginateStruct 测试 Paginate 结构体
|
||||||
|
func TestPaginateStruct(t *testing.T) {
|
||||||
|
p := Paginate{Page: 1, Limit: 10}
|
||||||
|
|
||||||
|
if p.Page != 1 {
|
||||||
|
t.Errorf("Expected Page to be 1, got %d", p.Page)
|
||||||
|
}
|
||||||
|
if p.Limit != 10 {
|
||||||
|
t.Errorf("Expected Limit to be 10, got %d", p.Limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPageInfo 测试 pageInfo 常量
|
||||||
|
func TestPageInfo(t *testing.T) {
|
||||||
|
expectedFields := []string{
|
||||||
|
"page",
|
||||||
|
"size",
|
||||||
|
"num",
|
||||||
|
"limit",
|
||||||
|
"pagesize",
|
||||||
|
"pageSize",
|
||||||
|
"page_size",
|
||||||
|
"pageNum",
|
||||||
|
"pagenum",
|
||||||
|
"page_num",
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pageInfo) != len(expectedFields) {
|
||||||
|
t.Errorf("Expected pageInfo to have %d fields, got %d", len(expectedFields), len(pageInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, field := range expectedFields {
|
||||||
|
if pageInfo[i] != field {
|
||||||
|
t.Errorf("Expected pageInfo[%d] to be '%s', got '%s'", i, field, pageInfo[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -15,6 +15,7 @@ require (
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gorm.io/driver/mysql v1.6.0
|
gorm.io/driver/mysql v1.6.0
|
||||||
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -52,6 +53,7 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -99,6 +99,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|
@ -207,6 +209,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue