新增 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>
233 lines
6.7 KiB
Go
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 覆盖。
|