micro-bundle/internal/dao/taskDao_test.go
cjy 590f0dedd6 test: 为 taskDao 与 taskLogic 添加单元测试
新增 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>
2026-06-10 15:21:03 +08:00

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