新增 internal/testutil/db.go,基于 DATA-DOG/go-sqlmock 提供 SetupTaskBenchDB 帮助函数,把 mock 出来的 *gorm.DB 挂到 app.ModuleClients.TaskBenchDB 上,方便 dao / logic 层单测。 SQL 匹配使用 QueryMatcherRegexp,避免对 gorm 生成的 SQL 字面量 (字段顺序、占位符数量等)过度耦合。 - internal/dao/taskDao_test.go:覆盖 GetPendingTaskLayout 等 DAO 方法的 SQL 行为与错误分支。 - internal/logic/taskLogic_test.go:覆盖 CreateTaskWorkLog 的入参范围校验分支。 go.mod / go.sum: - 新增直接依赖 github.com/DATA-DOG/go-sqlmock v1.5.2、 github.com/stretchr/testify v1.11.1; - 调整 google/uuid、robfig/cron/v3、satori/go.uuid 为直接依赖。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package dao
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
|
|
"micro-bundle/internal/testutil"
|
|
)
|
|
|
|
// ---- GetPendingTaskLayout ----
|
|
|
|
// sqlmock + gorm 配合:QueryMatcherEqual 要求 SQL 字面量精确匹配;但
|
|
// gorm 生成的字段顺序与时间字段处理可能因版本略有差异,这里用 Regexp 匹配
|
|
// 关键子串,避免对生成 SQL 的细节过度耦合。
|
|
//
|
|
// DAO 测试不用 t.Parallel():SetupTaskBenchDB 会改写全局 app.ModuleClients,
|
|
// 并行执行会导致 sqlmock 期望链互相污染。
|
|
var reSelectLayout = regexp.MustCompile(`SELECT \* FROM .task_pending_layout. WHERE id = \?`)
|
|
var reInsertLayout = regexp.MustCompile(`INSERT INTO .task_pending_layout. .*\)\s*VALUES \(.*\)\s*ON DUPLICATE KEY UPDATE .data.`)
|
|
var reInsertHidden = regexp.MustCompile(`INSERT INTO .task_assignee_hidden.`)
|
|
|
|
func TestGetPendingTaskLayout_NotFound(t *testing.T) {
|
|
mock := testutil.SetupTaskBenchDB(t)
|
|
mock.MatchExpectationsInOrder(false)
|
|
|
|
mock.ExpectQuery(reSelectLayout.String()).
|
|
WithArgs(1).
|
|
WillReturnError(gormNotFound())
|
|
|
|
got, err := GetPendingTaskLayout()
|
|
require.Error(t, err)
|
|
assert.Empty(t, got)
|
|
}
|
|
|
|
func TestGetPendingTaskLayout_Success(t *testing.T) {
|
|
mock := testutil.SetupTaskBenchDB(t)
|
|
mock.MatchExpectationsInOrder(false)
|
|
|
|
rows := sqlmock.NewRows([]string{"id", "data"}).AddRow(1, `[{"fieldKey":"subNum"}]`)
|
|
// gorm 的 First() 内部会带一个 LIMIT 参数,这里用 .* 兼容。
|
|
mock.ExpectQuery(reSelectLayout.String()).
|
|
WillReturnRows(rows)
|
|
|
|
got, err := GetPendingTaskLayout()
|
|
require.NoError(t, err)
|
|
assert.JSONEq(t, `[{"fieldKey":"subNum"}]`, got)
|
|
}
|
|
|
|
// ---- SetPendingTaskLayout ----
|
|
|
|
func TestSetPendingTaskLayout_Success(t *testing.T) {
|
|
mock := testutil.SetupTaskBenchDB(t)
|
|
mock.MatchExpectationsInOrder(false)
|
|
|
|
// gorm 会在 Create + OnConflict 时开事务,显式声明 Begin/Commit。
|
|
mock.ExpectBegin()
|
|
mock.ExpectExec(reInsertLayout.String()).
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
mock.ExpectCommit()
|
|
|
|
err := SetPendingTaskLayout(`[{"fieldKey":"subNum"}]`)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// ---- AddHiddenTaskAssignee ----
|
|
|
|
func TestAddHiddenTaskAssignee_Success(t *testing.T) {
|
|
mock := testutil.SetupTaskBenchDB(t)
|
|
mock.MatchExpectationsInOrder(false)
|
|
|
|
mock.ExpectBegin()
|
|
mock.ExpectExec(reInsertHidden.String()).
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
mock.ExpectCommit()
|
|
|
|
err := AddHiddenTaskAssignee("员工A", "E001")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// ---- helpers ----
|
|
|
|
// gormNotFound 返回 gorm.ErrRecordNotFound。
|
|
// gorm 内部 First()/Find() 命中零行时返回该错误。
|
|
func gormNotFound() error {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
// 保留 errors 包,留给后续事务测试使用 dialError 等
|
|
var _ = errors.New
|