micro-bundle/internal/logic/taskLogic_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

233 lines
6.7 KiB
Go

package logic
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"micro-bundle/internal/dto"
)
// ---- CreateTaskWorkLog 范围校验 ----
func TestCreateTaskWorkLog_Validation(t *testing.T) {
t.Parallel()
base := func() *dto.CreateTaskWorkLogRequest {
return &dto.CreateTaskWorkLogRequest{
AssignRecordsUUID: "uuid-1",
WorkUUID: "work-1",
OperationType: 1, // 1-加
TaskType: 1, // 1-视频
TaskCount: 1,
}
}
tests := []struct {
name string
mutate func(r *dto.CreateTaskWorkLogRequest)
wantContain string
}{
// ReturnError(nil, "参数错误", ...) 实际返回的 msg 统一是 "参数错误",
// 详细说明只在 print 字段(不入错误链),所以断言统一为 "参数错误",
// 区分点放在测试名上。
{"OperationType为0", func(r *dto.CreateTaskWorkLogRequest) { r.OperationType = 0 }, "参数错误"},
{"OperationType为5", func(r *dto.CreateTaskWorkLogRequest) { r.OperationType = 5 }, "参数错误"},
{"TaskType为0", func(r *dto.CreateTaskWorkLogRequest) { r.TaskType = 0 }, "参数错误"},
{"TaskType为7", func(r *dto.CreateTaskWorkLogRequest) { r.TaskType = 7 }, "参数错误"},
{"TaskCount为负", func(r *dto.CreateTaskWorkLogRequest) { r.TaskCount = -1 }, "参数错误"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := base()
tc.mutate(r)
err := CreateTaskWorkLog(r)
require.Error(t, err)
assert.Contains(t, err.Error(), tc.wantContain)
})
}
}
// ---- BatchAssignTask 校验 ----
func TestBatchAssignTask_EmptyItems(t *testing.T) {
t.Parallel()
// nil 和 空 slice 都走 `ReturnError(nil, "参数错误", "批量指派项不能为空")`,
// 实际返回的 msg 是 "参数错误"。
// nil slice
err := BatchAssignTask(nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
// 空 slice
err = BatchAssignTask([]*dto.BatchAssignItem{})
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
}
func TestBatchAssignTask_NilItem(t *testing.T) {
t.Parallel()
// 同样 `ReturnError(nil, "参数错误", "存在空的指派项")`。
err := BatchAssignTask([]*dto.BatchAssignItem{nil})
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
}
func TestBatchAssignTask_NegativeCount(t *testing.T) {
t.Parallel()
make := func(mutate func(*dto.BatchAssignItem)) *dto.BatchAssignItem {
it := &dto.BatchAssignItem{
SubNum: "S001",
TaskAssignee: "员工A",
TaskAssigneeNum: "E001",
AssignVideoCount: 1,
}
mutate(it)
return it
}
tests := []struct {
name string
mutate func(*dto.BatchAssignItem)
}{
{"Video为负", func(it *dto.BatchAssignItem) { it.AssignVideoCount = -1 }},
{"Post为负", func(it *dto.BatchAssignItem) { it.AssignPostCount = -1 }},
{"Data为负", func(it *dto.BatchAssignItem) { it.AssignDataCount = -1 }},
{"VideoScript为负", func(it *dto.BatchAssignItem) { it.AssignVideoScriptCount = -1 }},
{"Report为负", func(it *dto.BatchAssignItem) { it.AssignReportCount = -1 }},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := BatchAssignTask([]*dto.BatchAssignItem{make(tc.mutate)})
require.Error(t, err)
assert.Contains(t, err.Error(), "指派数量不能小于0")
})
}
}
func TestBatchAssignTask_SingleItemAllZero(t *testing.T) {
t.Parallel()
it := &dto.BatchAssignItem{SubNum: "S001", TaskAssignee: "员工A", TaskAssigneeNum: "E001"}
err := BatchAssignTask([]*dto.BatchAssignItem{it})
require.Error(t, err)
assert.Contains(t, err.Error(), "指派数量不能全部为0")
}
// 注:logic 中"全部指派项数量都为 0"的外层兜底(`if !hasPositive`)实际不可达:
// 循环中每项的"单项全 0"检查会先返回,所以该分支永远走不到。
// 保留这条注释供后人参考,不写不可达测试。
// ---- TerminateTaskByUUID / BatchTerminateTaskByUUIDs ----
func TestTerminateTaskByUUID_Empty(t *testing.T) {
t.Parallel()
err := TerminateTaskByUUID("")
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
}
func TestBatchTerminateTaskByUUIDs_Empty(t *testing.T) {
t.Parallel()
success, failed, failedUUIDs, err := BatchTerminateTaskByUUIDs(nil)
require.Error(t, err)
assert.Equal(t, 0, success)
assert.Equal(t, 0, failed)
assert.Nil(t, failedUUIDs)
assert.Contains(t, err.Error(), "参数错误")
success, failed, failedUUIDs, err = BatchTerminateTaskByUUIDs([]string{})
require.Error(t, err)
assert.Equal(t, 0, success)
assert.Equal(t, 0, failed)
assert.Nil(t, failedUUIDs)
}
// ---- RevertTaskCompletionByUUIDItem ----
func TestRevertTaskCompletionByUUIDItem_Empty(t *testing.T) {
t.Parallel()
for _, in := range []string{"", " ", "\t\n"} {
err := RevertTaskCompletionByUUIDItem(in)
require.Error(t, err, "input=%q", in)
assert.Contains(t, err.Error(), "参数错误")
}
}
// ---- GetTaskActualStatusByUUID ----
func TestGetTaskActualStatusByUUID_Empty(t *testing.T) {
t.Parallel()
for _, in := range []string{"", " "} {
status, err := GetTaskActualStatusByUUID(in)
require.Error(t, err, "input=%q", in)
assert.Equal(t, 0, status)
assert.Contains(t, err.Error(), "查询参数错误")
}
}
// ---- AddHiddenTaskAssignee ----
func TestAddHiddenTaskAssignee_Empty(t *testing.T) {
t.Parallel()
tests := []struct {
name, assignee, num string
}{
{"都为空", "", ""},
{"名称空", "", "E001"},
{"账号空", "员工A", ""},
{"全空格", " ", " "},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := AddHiddenTaskAssignee(tc.assignee, tc.num)
require.Error(t, err)
assert.Contains(t, err.Error(), "参数错误")
})
}
}
// ---- buildDefaultPendingLayout (私有函数,同包内可直接测) ----
func TestBuildDefaultPendingLayout(t *testing.T) {
t.Parallel()
got := buildDefaultPendingLayout()
require.NotEmpty(t, got)
var rows []map[string]any
err := json.Unmarshal([]byte(got), &rows)
require.NoError(t, err, "应当是合法 JSON")
// 33 个字段是当前硬编码的默认值;如未来调整,只改这一个数字。
assert.Len(t, rows, 33)
// 每个字段必有 fieldKey / fieldValue / sortOrNot / status
for i, row := range rows {
assert.Contains(t, row, "fieldKey", "第 %d 行缺 fieldKey", i)
assert.Contains(t, row, "fieldValue", "第 %d 行缺 fieldValue", i)
assert.Contains(t, row, "sortOrNot", "第 %d 行缺 sortOrNot", i)
assert.Contains(t, row, "status", "第 %d 行缺 status", i)
}
}
// GetPendingTaskLayout 的 dao 降级路径(失败时回退默认布局)留作下一阶段,
// 那里需要 sqlmock 配合 dao 验证。纯函数 buildDefaultPendingLayout 已被
// TestBuildDefaultPendingLayout 覆盖。