Files
pig-farm-controller/internal/infra/repository/plan_repository_test.go
2025-09-14 14:45:56 +08:00

1426 lines
53 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository_test
import (
"errors"
"fmt"
"testing"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)
// createTestPlan 是一个辅助函数,用于创建测试计划。
func createTestPlan(name, description string, execType models.PlanExecutionType, contentType models.PlanContentType) models.Plan {
return models.Plan{
Name: name,
Description: description,
ExecutionType: execType,
ContentType: contentType,
}
}
// TestListBasicPlans 测试 ListBasicPlans 方法,确保它能正确返回所有计划的基本信息。
func TestListBasicPlans(t *testing.T) {
tests := []struct {
name string
setupPlans []models.Plan
expectedCount int
expectedError error
}{
{
name: "数据库中没有计划",
setupPlans: []models.Plan{},
expectedCount: 0,
expectedError: nil,
},
{
name: "数据库中有多个计划",
setupPlans: []models.Plan{
createTestPlan("计划 A", "描述 A", models.PlanExecutionTypeAutomatic, models.PlanContentTypeTasks),
createTestPlan("计划 B", "描述 B", models.PlanExecutionTypeManual, models.PlanContentTypeSubPlans),
},
expectedCount: 2,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := setupTestDB(t)
repo := repository.NewGormPlanRepository(db)
for i := range tt.setupPlans {
err := db.Create(&tt.setupPlans[i]).Error
assert.NoError(t, err, "插入设置计划失败")
}
plans, err := repo.ListBasicPlans()
if tt.expectedError != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.expectedError))
} else {
assert.NoError(t, err)
assert.Len(t, plans, tt.expectedCount)
if tt.expectedCount > 0 {
// 验证返回的计划是否包含预期的ID
var actualIDs []uint
for _, p := range plans {
actualIDs = append(actualIDs, p.ID)
assert.Empty(t, p.SubPlans, "ListBasicPlans 不应加载子计划")
assert.Empty(t, p.Tasks, "ListBasicPlans 不应加载任务")
}
for _, setupPlan := range tt.setupPlans {
assert.Contains(t, actualIDs, setupPlan.ID, "返回的计划应包含设置计划的ID")
}
}
}
})
}
}
// TestGetBasicPlanByID 测试 GetBasicPlanByID 方法确保它能根据ID正确返回计划的基本信息。
func TestGetBasicPlanByID(t *testing.T) {
tests := []struct {
name string
setupPlan models.Plan
idToFetch uint
expectFound bool
expectedError error
}{
{
name: "通过ID找到计划",
setupPlan: createTestPlan("计划 C", "描述 C", models.PlanExecutionTypeAutomatic, models.PlanContentTypeTasks),
idToFetch: 0, // 创建后设置
expectFound: true,
},
{
name: "通过ID未找到计划",
setupPlan: models.Plan{}, // 此情况下无需设置计划
idToFetch: 999,
expectFound: false,
expectedError: gorm.ErrRecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := setupTestDB(t)
repo := repository.NewGormPlanRepository(db)
if tt.setupPlan.Name != "" { // 仅在有效设置时创建计划
err := db.Create(&tt.setupPlan).Error
assert.NoError(t, err, "插入设置计划失败")
tt.idToFetch = tt.setupPlan.ID // 使用数据库生成的ID
}
fetchedPlan, err := repo.GetBasicPlanByID(tt.idToFetch)
if tt.expectedError != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.expectedError), "预期错误类型不匹配")
assert.Nil(t, fetchedPlan)
} else {
assert.NoError(t, err)
assert.NotNil(t, fetchedPlan)
assert.Equal(t, tt.setupPlan.Name, fetchedPlan.Name)
assert.Equal(t, tt.setupPlan.Description, fetchedPlan.Description)
assert.Equal(t, tt.setupPlan.ExecutionType, fetchedPlan.ExecutionType)
assert.Equal(t, tt.setupPlan.ContentType, fetchedPlan.ContentType)
assert.Empty(t, fetchedPlan.SubPlans, "GetBasicPlanByID 不应加载子计划")
assert.Empty(t, fetchedPlan.Tasks, "GetBasicPlanByID 不应加载任务")
}
})
}
}
// TestGetPlanByID 测试 GetPlanByID 方法确保它能根据ID正确返回计划的完整信息包括关联数据。
func TestGetPlanByID(t *testing.T) {
type testCase struct {
name string
setupData func(db *gorm.DB) // 用于在测试前插入数据
planID uint
expectedPlan *models.Plan
expectedError string
}
testCases := []testCase{
{
name: "PlanNotFound",
setupData: func(db *gorm.DB) {
// 不插入任何数据
},
planID: 999,
expectedPlan: nil,
expectedError: "record not found",
},
{
name: "PlanWithTasks",
setupData: func(db *gorm.DB) {
// 使用硬编码的ID创建计划使测试可预测
plan := models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan A",
ContentType: models.PlanContentTypeTasks,
}
db.Create(&plan)
// 创建任务它们的ID将由数据库自动生成
db.Create(&models.Task{PlanID: 1, Name: "Task 2", ExecutionOrder: 1})
db.Create(&models.Task{PlanID: 1, Name: "Task 1", ExecutionOrder: 2})
},
planID: 1,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan A",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
// 期望按 "order" 字段升序排序
{PlanID: 1, Name: "Task 2", ExecutionOrder: 1},
{PlanID: 1, Name: "Task 1", ExecutionOrder: 2},
},
},
expectedError: "",
},
{
name: "PlanWithMultiLevelSubPlans",
setupData: func(db *gorm.DB) {
// 创建一个三层结构的计划
db.Create(&models.Plan{Model: gorm.Model{ID: 20}, Name: "Grandparent Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 21}, Name: "Parent Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 22}, Name: "Child Plan With Tasks", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Task{PlanID: 22, Name: "Grandchild's Task", ExecutionOrder: 1})
// 创建关联关系
db.Create(&models.SubPlan{ParentPlanID: 20, ChildPlanID: 21, ExecutionOrder: 1})
db.Create(&models.SubPlan{ParentPlanID: 21, ChildPlanID: 22, ExecutionOrder: 1})
},
planID: 20,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 20},
Name: "Grandparent Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{
ParentPlanID: 20,
ChildPlanID: 21,
ExecutionOrder: 1,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 21},
Name: "Parent Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{
ParentPlanID: 21,
ChildPlanID: 22,
ExecutionOrder: 1,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 22},
Name: "Child Plan With Tasks",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{PlanID: 22, Name: "Grandchild's Task", ExecutionOrder: 1},
},
},
},
},
},
},
},
},
expectedError: "",
},
{
name: "UnknownContentType",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{
Model: gorm.Model{ID: 30},
Name: "Unknown Type Plan",
ContentType: "INVALID_TYPE",
})
},
planID: 30,
expectedPlan: nil,
expectedError: fmt.Sprintf("未知的计划内容类型: INVALID_TYPE; 计划ID: 30"),
},
// 新增场景:测试空的关联数据
{
name: "PlanWithTasksType_ButNoTasks",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{
Model: gorm.Model{ID: 50},
Name: "Plan with empty tasks",
ContentType: models.PlanContentTypeTasks,
})
},
planID: 50,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 50},
Name: "Plan with empty tasks",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{}, // 期望一个空的切片
},
expectedError: "",
},
// 新增场景:测试复杂的同级排序
{
name: "PlanWithSubPlans_ComplexSorting",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 60}, Name: "Main Plan For Sorting", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 61}, Name: "SubPlan C", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Plan{Model: gorm.Model{ID: 62}, Name: "SubPlan A", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Plan{Model: gorm.Model{ID: 63}, Name: "SubPlan B", ContentType: models.PlanContentTypeTasks})
// 故意打乱顺序插入
db.Create(&models.SubPlan{ParentPlanID: 60, ChildPlanID: 61, ExecutionOrder: 3})
db.Create(&models.SubPlan{ParentPlanID: 60, ChildPlanID: 62, ExecutionOrder: 1})
db.Create(&models.SubPlan{ParentPlanID: 60, ChildPlanID: 63, ExecutionOrder: 2})
},
planID: 60,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 60},
Name: "Main Plan For Sorting",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ParentPlanID: 60, ChildPlanID: 62, ExecutionOrder: 1, ChildPlan: &models.Plan{Model: gorm.Model{ID: 62}, Name: "SubPlan A", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}}},
{ParentPlanID: 60, ChildPlanID: 63, ExecutionOrder: 2, ChildPlan: &models.Plan{Model: gorm.Model{ID: 63}, Name: "SubPlan B", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}}},
{ParentPlanID: 60, ChildPlanID: 61, ExecutionOrder: 3, ChildPlan: &models.Plan{Model: gorm.Model{ID: 61}, Name: "SubPlan C", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}}},
},
},
expectedError: "",
},
// 新增场景:测试混合内容的子计划树
{
name: "PlanWithSubPlans_MixedContentTypes",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 70}, Name: "Mixed Main Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 71}, Name: "Child with SubPlans", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 72}, Name: "Grandchild with Tasks", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Task{PlanID: 72, Name: "Grandchild's Task", ExecutionOrder: 1})
db.Create(&models.Plan{Model: gorm.Model{ID: 73}, Name: "Child with Tasks", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Task{PlanID: 73, Name: "Child's Task", ExecutionOrder: 1})
// 创建关联
db.Create(&models.SubPlan{ParentPlanID: 70, ChildPlanID: 71, ExecutionOrder: 1}) // Main -> Child with SubPlans
db.Create(&models.SubPlan{ParentPlanID: 70, ChildPlanID: 73, ExecutionOrder: 2}) // Main -> Child with Tasks
db.Create(&models.SubPlan{ParentPlanID: 71, ChildPlanID: 72, ExecutionOrder: 1}) // Child with SubPlans -> Grandchild
},
planID: 70,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 70},
Name: "Mixed Main Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{
ParentPlanID: 70, ChildPlanID: 71, ExecutionOrder: 1,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 71}, Name: "Child with SubPlans", ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ParentPlanID: 71, ChildPlanID: 72, ExecutionOrder: 1, ChildPlan: &models.Plan{
Model: gorm.Model{ID: 72}, Name: "Grandchild with Tasks", ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{{PlanID: 72, Name: "Grandchild's Task", ExecutionOrder: 1}},
}},
},
},
},
{
ParentPlanID: 70, ChildPlanID: 73, ExecutionOrder: 2,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 73}, Name: "Child with Tasks", ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{{PlanID: 73, Name: "Child's Task", ExecutionOrder: 1}},
},
},
},
},
expectedError: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := setupTestDB(t)
// 使用 defer 确保数据库连接在测试结束后关闭
sqlDB, _ := db.DB()
defer sqlDB.Close()
tc.setupData(db)
repo := repository.NewGormPlanRepository(db)
plan, err := repo.GetPlanByID(tc.planID)
if tc.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedError)
assert.Nil(t, plan)
} else {
assert.NoError(t, err)
assert.NotNil(t, plan)
// 在比较之前,清理实际结果和期望结果中所有不确定的、由数据库自动生成的字段
cleanPlanForComparison(plan)
cleanPlanForComparison(tc.expectedPlan)
assert.Equal(t, tc.expectedPlan, plan)
}
})
}
}
// cleanPlanForComparison 递归地重置 Plan 对象及其关联对象中由数据库自动生成的字段如ID和时间戳
// 以便在测试中断言它们与期望值相等。
func cleanPlanForComparison(p *models.Plan) {
if p == nil {
return
}
// 重置 Plan 自身的时间戳
p.CreatedAt = time.Time{}
p.UpdatedAt = time.Time{}
p.DeletedAt = gorm.DeletedAt{}
// 重置所有 Task 的自动生成字段
for i := range p.Tasks {
p.Tasks[i].ID = 0 // ID是自动生成的必须重置为0才能与期望值匹配
p.Tasks[i].CreatedAt = time.Time{}
p.Tasks[i].UpdatedAt = time.Time{}
p.Tasks[i].DeletedAt = gorm.DeletedAt{}
}
// 重置所有 SubPlan 的自动生成字段,并递归清理子计划
for i := range p.SubPlans {
p.SubPlans[i].ID = 0 // SubPlan 连接记录的ID也是自动生成的
p.SubPlans[i].CreatedAt = time.Time{}
p.SubPlans[i].UpdatedAt = time.Time{}
p.SubPlans[i].DeletedAt = gorm.DeletedAt{}
// 递归调用以清理嵌套的子计划
cleanPlanForComparison(p.SubPlans[i].ChildPlan)
}
}
// TestUpdatePlan_Validation 专注于测试 UpdatePlan 中前置检查逻辑的各种失败和成功场景。
func TestUpdatePlan_Validation(t *testing.T) {
// 定义Go测试中使用的计划实体
planA := &models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan A", ContentType: models.PlanContentTypeSubPlans}
planB := &models.Plan{Model: gorm.Model{ID: 2}, Name: "Plan B", ContentType: models.PlanContentTypeSubPlans}
planC := &models.Plan{Model: gorm.Model{ID: 3}, Name: "Plan C", ContentType: models.PlanContentTypeSubPlans}
planD := &models.Plan{Model: gorm.Model{ID: 4}, Name: "Plan D", ContentType: models.PlanContentTypeTasks}
planNew := &models.Plan{Model: gorm.Model{ID: 0}, Name: "New Plan"} // ID为0的新计划
type testCase struct {
name string
setupDB func(db *gorm.DB)
buildInput func() *models.Plan // 修改为构建函数
expectedError string // 保持 string 类型
}
testCases := []testCase{
{
name: "成功-合法的菱形依赖树",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}})
db.Create(&models.Plan{Model: gorm.Model{ID: 3}})
db.Create(&models.Plan{Model: gorm.Model{ID: 4}})
},
buildInput: func() *models.Plan {
planD.ContentType = models.PlanContentTypeTasks
planB.ContentType = models.PlanContentTypeSubPlans
planB.SubPlans = []models.SubPlan{{ChildPlanID: 4, ChildPlan: planD}}
planC.ContentType = models.PlanContentTypeSubPlans
planC.SubPlans = []models.SubPlan{{ChildPlanID: 4, ChildPlan: planD}}
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{
{ChildPlanID: 2, ChildPlan: planB, ExecutionOrder: 1},
{ChildPlanID: 3, ChildPlan: planC, ExecutionOrder: 2},
}
return planA
},
expectedError: "", // 期望没有错误
},
{
name: "错误-根节点ID为零",
setupDB: func(db *gorm.DB) {},
buildInput: func() *models.Plan { return planNew },
expectedError: repository.ErrUpdateWithInvalidRoot.Error(),
},
{
name: "错误-子计划ID为零",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{{ChildPlan: planNew}}
return planA
},
expectedError: repository.ErrNewSubPlanInUpdate.Error(),
},
{
name: "错误-节点在数据库中不存在",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{
{ChildPlanID: 2, ChildPlan: planB, ExecutionOrder: 1},
{ChildPlanID: 3, ChildPlan: planC, ExecutionOrder: 2}, // C 不存在
}
return planA
},
expectedError: repository.ErrNodeDoesNotExist.Error(),
},
{
name: "错误-简单循环引用(A->B->A)",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{{ChildPlanID: 2, ChildPlan: planB}}
planB.ContentType = models.PlanContentTypeSubPlans
planB.SubPlans = []models.SubPlan{{ChildPlanID: 1, ChildPlan: planA}}
return planA
},
expectedError: "检测到循环引用:计划 (ID: 1)",
},
{
name: "错误-复杂循环引用(A->B->C->A)",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}})
db.Create(&models.Plan{Model: gorm.Model{ID: 3}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{{ChildPlanID: 2, ChildPlan: planB}}
planB.ContentType = models.PlanContentTypeSubPlans
planB.SubPlans = []models.SubPlan{{ChildPlanID: 3, ChildPlan: planC}}
planC.ContentType = models.PlanContentTypeSubPlans
planC.SubPlans = []models.SubPlan{{ChildPlanID: 1, ChildPlan: planA}}
return planA
},
expectedError: "检测到循环引用:计划 (ID: 1)",
},
{
name: "错误-自引用(A->A)",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{{ChildPlanID: 1, ChildPlan: planA}}
return planA
},
expectedError: "检测到循环引用:计划 (ID: 1)",
},
{
name: "错误-根节点内容混合",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{{ChildPlanID: 2, ChildPlan: planB}}
planA.Tasks = []models.Task{{Name: "A's Task"}}
return planA
},
expectedError: "不能同时包含任务和子计划",
},
{
name: "错误-任务执行顺序重复",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeTasks
planA.Tasks = []models.Task{
{Name: "Task 1", ExecutionOrder: 1},
{Name: "Task 2", ExecutionOrder: 1}, // 重复的顺序
}
return planA
},
expectedError: fmt.Sprintf("任务执行顺序重复: %d", 1),
}, {
name: "错误-子计划执行顺序重复",
setupDB: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}})
db.Create(&models.Plan{Model: gorm.Model{ID: 10}})
db.Create(&models.Plan{Model: gorm.Model{ID: 11}})
},
buildInput: func() *models.Plan {
planA.ContentType = models.PlanContentTypeSubPlans
planA.SubPlans = []models.SubPlan{
{ChildPlanID: 10, ChildPlan: &models.Plan{Model: gorm.Model{ID: 10}}, ExecutionOrder: 1},
{ChildPlanID: 11, ChildPlan: &models.Plan{Model: gorm.Model{ID: 11}}, ExecutionOrder: 1}, // 重复的顺序
}
return planA
},
expectedError: fmt.Sprintf("子计划执行顺序重复: %d", 1),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// 1. 为每个测试用例重置基础对象的状态
*planA = models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan A"}
*planB = models.Plan{Model: gorm.Model{ID: 2}, Name: "Plan B"}
*planC = models.Plan{Model: gorm.Model{ID: 3}, Name: "Plan C"}
*planD = models.Plan{Model: gorm.Model{ID: 4}, Name: "Plan D"}
*planNew = models.Plan{Model: gorm.Model{ID: 0}, Name: "New Plan"}
// 2. 设置数据库
db := setupTestDB(t)
sqlDB, _ := db.DB()
defer sqlDB.Close()
tc.setupDB(db)
// 3. 在对象重置后,构建本次测试需要的输入结构
input := tc.buildInput()
// 4. 执行测试
repo := repository.NewGormPlanRepository(db)
err := repo.UpdatePlan(input)
// 5. 断言结果
if tc.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedError)
} else {
assert.NoError(t, err)
}
})
}
}
// TestUpdatePlan_Reconciliation 专注于测试 UpdatePlan 成功执行后,数据库状态是否与预期一致。
func TestUpdatePlan_Reconciliation(t *testing.T) {
type testCase struct {
name string
setupDB func(db *gorm.DB) (rootPlanID uint)
buildInput func(db *gorm.DB) *models.Plan
verifyDB func(t *testing.T, db *gorm.DB, rootPlanID uint)
}
testCases := []testCase{
{
name: "任务协调-新增一个任务",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan With Tasks", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
db.Create(&models.Task{PlanID: 1, Name: "Task 1", ExecutionOrder: 1})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan With Tasks",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{Model: gorm.Model{ID: 1}, PlanID: 1, Name: "Task 1", ExecutionOrder: 1},
{Name: "New Task 2", ExecutionOrder: 2},
},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var finalPlan models.Plan
db.Preload("Tasks").First(&finalPlan, rootPlanID)
assert.Len(t, finalPlan.Tasks, 2)
assert.Equal(t, "New Task 2", finalPlan.Tasks[1].Name)
},
},
{
name: "任务协调-删除一个任务",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan With Tasks", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
db.Create(&models.Task{Model: gorm.Model{ID: 10}, PlanID: 1, Name: "Task 1", ExecutionOrder: 1})
db.Create(&models.Task{Model: gorm.Model{ID: 11}, PlanID: 1, Name: "Task to Delete", ExecutionOrder: 2})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan With Tasks",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{Model: gorm.Model{ID: 10}, PlanID: 1, Name: "Task 1", ExecutionOrder: 1},
},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var tasks []models.Task
db.Where("plan_id = ?", rootPlanID).Find(&tasks)
assert.Len(t, tasks, 1)
var count int64
db.Model(&models.Task{}).Where("id = ?", 11).Count(&count)
assert.Equal(t, int64(0), count, "被删除的任务不应再存在")
},
},
{
name: "任务协调-更新并重排序任务",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
db.Create(&models.Task{Model: gorm.Model{ID: 10}, PlanID: 1, Name: "A", ExecutionOrder: 1})
db.Create(&models.Task{Model: gorm.Model{ID: 11}, PlanID: 1, Name: "B", ExecutionOrder: 2})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{Model: gorm.Model{ID: 11}, PlanID: 1, Name: "B Updated", ExecutionOrder: 1},
{Model: gorm.Model{ID: 10}, PlanID: 1, Name: "A", ExecutionOrder: 2},
},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var finalPlan models.Plan
db.Preload("Tasks", func(db *gorm.DB) *gorm.DB {
return db.Order("execution_order")
}).First(&finalPlan, rootPlanID)
assert.Len(t, finalPlan.Tasks, 2)
assert.Equal(t, "B Updated", finalPlan.Tasks[0].Name)
assert.Equal(t, uint(11), finalPlan.Tasks[0].ID)
},
},
{
name: "任务协调-混沌的同步操作(增删改重排)",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
db.Create(&models.Task{Model: gorm.Model{ID: 10}, PlanID: 1, Name: "Task 1 (Original)", ExecutionOrder: 1})
db.Create(&models.Task{Model: gorm.Model{ID: 11}, PlanID: 1, Name: "Task 2 (To Be Deleted)", ExecutionOrder: 2})
db.Create(&models.Task{Model: gorm.Model{ID: 12}, PlanID: 1, Name: "Task 3 (Original)", ExecutionOrder: 3})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
// T4 (新) -> T3 (不变) -> T1 (更新)
{Name: "Task 4 (New)", ExecutionOrder: 1},
{Model: gorm.Model{ID: 12}, PlanID: 1, Name: "Task 3 (Original)", ExecutionOrder: 2},
{Model: gorm.Model{ID: 10}, PlanID: 1, Name: "Task 1 (Updated)", ExecutionOrder: 3},
},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var finalPlan models.Plan
db.Preload("Tasks", func(db *gorm.DB) *gorm.DB {
return db.Order("execution_order")
}).First(&finalPlan, rootPlanID)
// 验证最终数量
assert.Len(t, finalPlan.Tasks, 3)
// 验证被删除的 T2 不存在
var count int64
db.Model(&models.Task{}).Where("id = ?", 11).Count(&count)
assert.Equal(t, int64(0), count)
// 验证顺序和内容
assert.Equal(t, "Task 4 (New)", finalPlan.Tasks[0].Name)
assert.Equal(t, "Task 3 (Original)", finalPlan.Tasks[1].Name)
assert.Equal(t, "Task 1 (Updated)", finalPlan.Tasks[2].Name)
},
},
{
name: "子计划协调-新增一个关联",
setupDB: func(db *gorm.DB) uint {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "Parent", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}, Name: "Existing Child"})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Parent",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 2}},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var links []models.SubPlan
db.Where("parent_plan_id = ?", rootPlanID).Find(&links)
assert.Len(t, links, 1)
assert.Equal(t, uint(2), links[0].ChildPlanID)
},
},
{
name: "子计划协调-删除一个关联",
setupDB: func(db *gorm.DB) uint {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "Parent", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}, Name: "Child To Unlink"})
db.Create(&models.SubPlan{ParentPlanID: 1, ChildPlanID: 2})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Parent",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var linkCount int64
db.Model(&models.SubPlan{}).Where("parent_plan_id = ?", rootPlanID).Count(&linkCount)
assert.Equal(t, int64(0), linkCount)
var planCount int64
db.Model(&models.Plan{}).Where("id = ?", 2).Count(&planCount)
assert.Equal(t, int64(1), planCount, "子计划本身不应被删除")
},
},
{
name: "类型转换-从任务切换到子计划",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
db.Create(&models.Task{PlanID: 1, Name: "Old Task"})
db.Create(&models.Plan{Model: gorm.Model{ID: 10}, Name: "New Child"})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 10}},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var taskCount int64
db.Model(&models.Task{}).Where("plan_id = ?", rootPlanID).Count(&taskCount)
assert.Equal(t, int64(0), taskCount, "旧任务应被清理")
var linkCount int64
db.Model(&models.SubPlan{}).Where("parent_plan_id = ?", rootPlanID).Count(&linkCount)
assert.Equal(t, int64(1), linkCount, "新关联应被创建")
},
},
{
name: "类型转换-从子计划切换到任务",
setupDB: func(db *gorm.DB) uint {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 10}, Name: "Old Child"})
db.Create(&models.SubPlan{ParentPlanID: 1, ChildPlanID: 10})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
return &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan",
ContentType: models.PlanContentTypeTasks, // 类型变更
Tasks: []models.Task{{Name: "New Task"}},
}
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var linkCount int64
db.Model(&models.SubPlan{}).Where("parent_plan_id = ?", rootPlanID).Count(&linkCount)
assert.Equal(t, int64(0), linkCount, "旧的子计划关联应被清理")
var taskCount int64
db.Model(&models.Task{}).Where("plan_id = ?", rootPlanID).Count(&taskCount)
assert.Equal(t, int64(1), taskCount, "新任务应被创建")
},
},
{
name: "递归更新-深层节点的变更",
setupDB: func(db *gorm.DB) uint {
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "A", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}, Name: "B", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 3}, Name: "C", ContentType: models.PlanContentTypeTasks})
db.Create(&models.SubPlan{Model: gorm.Model{ID: 101}, ParentPlanID: 1, ChildPlanID: 2, ExecutionOrder: 1})
db.Create(&models.SubPlan{Model: gorm.Model{ID: 102}, ParentPlanID: 2, ChildPlanID: 3, ExecutionOrder: 1})
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
planC := &models.Plan{Model: gorm.Model{ID: 3}, Name: "C Updated", ContentType: models.PlanContentTypeTasks}
planB := &models.Plan{
Model: gorm.Model{ID: 2},
Name: "B",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{Model: gorm.Model{ID: 102}, ParentPlanID: 2, ChildPlanID: 3, ChildPlan: planC, ExecutionOrder: 2}},
}
planA := &models.Plan{
Model: gorm.Model{ID: 1},
Name: "A Updated",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{Model: gorm.Model{ID: 101}, ParentPlanID: 1, ChildPlanID: 2, ChildPlan: planB, ExecutionOrder: 1}},
}
return planA
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
var finalA, finalC models.Plan
var finalLinkB models.SubPlan
db.First(&finalA, 1)
assert.Equal(t, "A Updated", finalA.Name)
db.First(&finalLinkB, 102)
assert.Equal(t, 2, finalLinkB.ExecutionOrder)
db.First(&finalC, 3)
assert.Equal(t, "C Updated", finalC.Name)
},
},
{
name: "递归更新-深层节点的类型转换(C从SubPlans变为Tasks)",
setupDB: func(db *gorm.DB) uint {
// 初始状态: A -> B -> C -> D
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "A", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}, Name: "B", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 3}, Name: "C", ContentType: models.PlanContentTypeSubPlans}) // C的初始类型
db.Create(&models.Plan{Model: gorm.Model{ID: 4}, Name: "D"})
db.Create(&models.SubPlan{ParentPlanID: 1, ChildPlanID: 2})
db.Create(&models.SubPlan{ParentPlanID: 2, ChildPlanID: 3})
db.Create(&models.SubPlan{Model: gorm.Model{ID: 99}, ParentPlanID: 3, ChildPlanID: 4}) // C->D 的关联
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
// 更新操作: C的类型变为Tasks并增加一个新Task
planC := &models.Plan{
Model: gorm.Model{ID: 3},
Name: "C",
ContentType: models.PlanContentTypeTasks, // 类型变更
Tasks: []models.Task{{Name: "C's New Task"}},
}
planB := &models.Plan{
Model: gorm.Model{ID: 2},
Name: "B",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 3, ChildPlan: planC}},
}
planA := &models.Plan{
Model: gorm.Model{ID: 1},
Name: "A",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 2, ChildPlan: planB}},
}
return planA
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
// 1. 验证 C->D 的旧关联已被删除
var linkCount int64
db.Model(&models.SubPlan{}).Where("id = ?", 99).Count(&linkCount)
assert.Equal(t, int64(0), linkCount, "C的旧子计划关联应被清理")
// 2. 验证 C 的新Task已被创建
var task models.Task
err := db.Where("plan_id = ?", 3).First(&task).Error
assert.NoError(t, err, "C的新任务应该被创建")
assert.Equal(t, "C's New Task", task.Name)
// 3. 验证 D 计划本身依然存在
var planDCount int64
db.Model(&models.Plan{}).Where("id = ?", 4).Count(&planDCount)
assert.Equal(t, int64(1), planDCount, "计划D本身不应被删除")
},
}, {
name: "递归更新-中间层分支替换(A->B变为A->D)",
setupDB: func(db *gorm.DB) uint {
// 初始状态: A -> B -> C
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "A", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}, Name: "B", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 3}, Name: "C"})
db.Create(&models.SubPlan{Model: gorm.Model{ID: 101}, ParentPlanID: 1, ChildPlanID: 2}) // A->B
db.Create(&models.SubPlan{ParentPlanID: 2, ChildPlanID: 3}) // B->C
// 准备用于替换的分支: D -> E
db.Create(&models.Plan{Model: gorm.Model{ID: 4}, Name: "D", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 5}, Name: "E"})
db.Create(&models.SubPlan{ParentPlanID: 4, ChildPlanID: 5}) // D->E
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
// 更新操作: A的子计划从 B 替换为 D
planE := &models.Plan{Model: gorm.Model{ID: 5}, Name: "E Updated"} // 同时更新深层节点
planD := &models.Plan{
Model: gorm.Model{ID: 4},
Name: "D",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 5, ChildPlan: planE}},
}
planA := &models.Plan{
Model: gorm.Model{ID: 1},
Name: "A",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 4, ChildPlan: planD}}, // 新关联 A->D
}
return planA
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
// 1. 验证 A->B 的旧关联已被删除
var linkCount int64
db.Model(&models.SubPlan{}).Where("id = ?", 101).Count(&linkCount)
assert.Equal(t, int64(0), linkCount, "A->B 的旧关联应被删除")
// 2. 验证 A->D 的新关联已创建
var newLink models.SubPlan
err := db.Where("parent_plan_id = ? AND child_plan_id = ?", 1, 4).First(&newLink).Error
assert.NoError(t, err, "A->D 的新关联应被创建")
// 3. 验证 B, C, D, E 计划本身都依然存在
var planCount int64
db.Model(&models.Plan{}).Where("id IN ?", []uint{2, 3, 4, 5}).Count(&planCount)
assert.Equal(t, int64(4), planCount, "所有被引用或解引用的计划本身都不应被删除")
// 4. 验证对新分支深层节点的递归更新已生效
var finalE models.Plan
db.First(&finalE, 5)
assert.Equal(t, "E Updated", finalE.Name)
},
},
{
name: "递归更新-菱形依赖下的冲突更新",
setupDB: func(db *gorm.DB) uint {
// 初始状态: A -> B -> D, A -> C -> D
db.Create(&models.Plan{Model: gorm.Model{ID: 1}, Name: "A", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 2}, Name: "B", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 3}, Name: "C", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 4}, Name: "D (Original)"}) // D的初始名字
// 创建关联
db.Create(&models.SubPlan{ParentPlanID: 1, ChildPlanID: 2, ExecutionOrder: 1}) // A->B
db.Create(&models.SubPlan{ParentPlanID: 1, ChildPlanID: 3, ExecutionOrder: 2}) // A->C
db.Create(&models.SubPlan{ParentPlanID: 2, ChildPlanID: 4}) // B->D
db.Create(&models.SubPlan{ParentPlanID: 3, ChildPlanID: 4}) // C->D
return 1
},
buildInput: func(db *gorm.DB) *models.Plan {
// 在一次调用中,通过不同路径对 D 进行不同的更新
planD_fromB := &models.Plan{Model: gorm.Model{ID: 4}, Name: "D (Updated from B)"}
planD_fromC := &models.Plan{Model: gorm.Model{ID: 4}, Name: "D (Updated from C)"}
planB := &models.Plan{
Model: gorm.Model{ID: 2},
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 4, ChildPlan: planD_fromB}},
}
planC := &models.Plan{
Model: gorm.Model{ID: 3},
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{{ChildPlanID: 4, ChildPlan: planD_fromC}},
}
planA := &models.Plan{
Model: gorm.Model{ID: 1},
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ChildPlanID: 2, ChildPlan: planB, ExecutionOrder: 1},
{ChildPlanID: 3, ChildPlan: planC, ExecutionOrder: 2}, // C 在 B 之后
},
}
return planA
},
verifyDB: func(t *testing.T, db *gorm.DB, rootPlanID uint) {
// 验证D 的最终名字应该符合“最后一次更新获胜”的原则
// 由于 planC 在 planB 之后被处理D 的名字应该是 "D (Updated from C)"
var finalD models.Plan
db.First(&finalD, 4)
assert.Equal(t, "D (Updated from C)", finalD.Name, "共享下游节点的更新应以后一次执行为准")
// 确保所有关联依然存在
var linkCount int64
db.Model(&models.SubPlan{}).Where("child_plan_id = ?", 4).Count(&linkCount)
assert.Equal(t, int64(2), linkCount, "D 的两个上游关联都应继续存在")
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := setupTestDB(t)
sqlDB, _ := db.DB()
defer sqlDB.Close()
rootID := tc.setupDB(db)
input := tc.buildInput(db)
repo := repository.NewGormPlanRepository(db)
err := repo.UpdatePlan(input)
assert.NoError(t, err)
tc.verifyDB(t, db, rootID)
})
}
}
// createExistingPlan 辅助函数,用于在数据库中创建已存在的计划
func createExistingPlan(db *gorm.DB, name string, contentType models.PlanContentType) *models.Plan {
plan := &models.Plan{
Name: name,
ContentType: contentType,
}
db.Create(plan)
return plan
}
func TestPlanRepository_Create(t *testing.T) {
type testCase struct {
name string
setupDB func(db *gorm.DB) // 准备数据库的初始状态
inputPlan *models.Plan // 传入 Create 方法的计划对象
expectedError error // 期望的错误类型
verifyDB func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) // 验证数据库状态
}
testCases := []testCase{
{
name: "成功创建-只包含基本信息",
setupDB: func(db *gorm.DB) {
// 无需额外设置
},
inputPlan: &models.Plan{
Name: "简单计划",
Description: "一个不包含任务或子计划的简单计划",
ContentType: models.PlanContentTypeTasks, // 修改为有效的 ContentType
Tasks: []models.Task{}, // 明确为空任务列表
},
expectedError: nil,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
assert.NotZero(t, createdPlan.ID, "创建后计划ID不应为0")
var foundPlan models.Plan
err := db.First(&foundPlan, createdPlan.ID).Error
assert.NoError(t, err)
assert.Equal(t, "简单计划", foundPlan.Name)
assert.Equal(t, models.PlanContentTypeTasks, foundPlan.ContentType)
var tasks []models.Task
db.Where("plan_id = ?", createdPlan.ID).Find(&tasks)
assert.Len(t, tasks, 0, "不应创建任何任务")
},
},
{
name: "成功创建-包含任务",
setupDB: func(db *gorm.DB) {
// 无需额外设置
},
inputPlan: &models.Plan{
Name: "任务计划",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{Name: "任务A", ExecutionOrder: 1},
{Name: "任务B", ExecutionOrder: 2},
},
},
expectedError: nil,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
assert.NotZero(t, createdPlan.ID, "计划ID不应为0")
var foundPlan models.Plan
db.Preload("Tasks").First(&foundPlan, createdPlan.ID)
assert.Len(t, foundPlan.Tasks, 2, "应创建两个任务")
assert.NotZero(t, foundPlan.Tasks[0].ID, "任务ID不应为0")
assert.Equal(t, "任务A", foundPlan.Tasks[0].Name)
},
},
{
name: "成功创建-包含子计划关联",
setupDB: func(db *gorm.DB) {
// 预先创建子计划实体,使用有效的 ContentType
createExistingPlan(db, "子计划1", models.PlanContentTypeTasks)
createExistingPlan(db, "子计划2", models.PlanContentTypeTasks)
},
inputPlan: &models.Plan{
Name: "父计划",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ChildPlanID: 1, ExecutionOrder: 1}, // 关联已存在的子计划1
{ChildPlanID: 2, ExecutionOrder: 2, ChildPlan: &models.Plan{Model: gorm.Model{ID: 2}}}, // 关联已存在的子计划2
},
},
expectedError: nil,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
assert.NotZero(t, createdPlan.ID, "创建后计划ID不应为0")
// 直接查询 SubPlan 关联记录
var foundSubPlanLinks []models.SubPlan
err := db.Where("parent_plan_id = ?", createdPlan.ID).Find(&foundSubPlanLinks).Error
assert.NoError(t, err)
assert.Len(t, foundSubPlanLinks, 2, "应创建两个子计划关联")
assert.NotZero(t, foundSubPlanLinks[0].ID, "子计划关联ID不应为0")
assert.Equal(t, createdPlan.ID, foundSubPlanLinks[0].ParentPlanID)
assert.Equal(t, uint(1), foundSubPlanLinks[0].ChildPlanID)
},
},
{
name: "失败-计划ID不为0",
setupDB: func(db *gorm.DB) {
// 无需额外设置
},
inputPlan: &models.Plan{
Model: gorm.Model{ID: 100}, // ID不为0
Name: "无效计划",
},
expectedError: repository.ErrCreateWithNonZeroID,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
// 验证数据库中没有创建该计划
var count int64
db.Model(&models.Plan{}).Where("id = ?", 100).Count(&count)
assert.Equal(t, int64(0), count, "计划不应被创建")
},
},
{
name: "失败-同时包含任务和子计划",
setupDB: func(db *gorm.DB) {
createExistingPlan(db, "子计划", models.PlanContentTypeTasks) // 使用有效的 ContentType
},
inputPlan: &models.Plan{
Name: "混合内容计划",
ContentType: models.PlanContentTypeTasks, // 声明为任务类型
Tasks: []models.Task{{Name: "任务A"}},
SubPlans: []models.SubPlan{{ChildPlanID: 1, ChildPlan: &models.Plan{Model: gorm.Model{ID: 1}}}}, // 但也包含子计划
},
expectedError: repository.ErrMixedContent,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
// 验证数据库中没有创建该计划
var count int64
db.Model(&models.Plan{}).Where("name = ?", "混合内容计划").Count(&count)
assert.Equal(t, int64(0), count, "计划不应被创建")
},
},
{
name: "失败-子计划ID为0",
setupDB: func(db *gorm.DB) {
// 无需额外设置
},
inputPlan: &models.Plan{
Name: "无效子计划关联",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ChildPlanID: 0, ChildPlan: &models.Plan{Model: gorm.Model{ID: 0}}}, // 子计划ID为0
},
},
expectedError: repository.ErrSubPlanIDIsZeroOnCreate,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
var count int64
db.Model(&models.Plan{}).Where("name = ?", "无效子计划关联").Count(&count)
assert.Equal(t, int64(0), count, "计划不应被创建")
},
},
{
name: "失败-子计划在数据库中不存在",
setupDB: func(db *gorm.DB) {
// 不创建ID为999的计划
},
inputPlan: &models.Plan{
Name: "不存在的子计划",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ChildPlanID: 999, ChildPlan: &models.Plan{Model: gorm.Model{ID: 999}}}, // 关联一个不存在的ID
},
},
expectedError: repository.ErrNodeDoesNotExist,
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
var count int64
db.Model(&models.Plan{}).Where("name = ?", "不存在的子计划").Count(&count)
assert.Equal(t, int64(0), count, "计划不应被创建")
},
},
{
name: "失败-任务执行顺序重复",
setupDB: func(db *gorm.DB) {
// 无需额外设置
},
inputPlan: &models.Plan{
Name: "重复任务顺序计划",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{Name: "Task 1", ExecutionOrder: 1},
{Name: "Task 2", ExecutionOrder: 1}, // 重复的顺序
},
},
expectedError: fmt.Errorf("任务执行顺序重复: %d", 1), // 假设 Create 方法会返回此错误
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
var count int64
db.Model(&models.Plan{}).Where("name = ?", "重复任务顺序计划").Count(&count)
assert.Equal(t, int64(0), count, "重复任务顺序的计划不应被创建")
},
},
{
name: "失败-子计划执行顺序重复",
setupDB: func(db *gorm.DB) {
createExistingPlan(db, "子计划A", models.PlanContentTypeTasks)
createExistingPlan(db, "子计划B", models.PlanContentTypeTasks)
},
inputPlan: &models.Plan{
Name: "重复子计划顺序计划",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ChildPlanID: 1, ExecutionOrder: 1},
{ChildPlanID: 2, ExecutionOrder: 1}, // 重复的顺序
},
},
expectedError: fmt.Errorf("子计划执行顺序重复: %d", 1), // 假设 Create 方法会返回此错误
verifyDB: func(t *testing.T, db *gorm.DB, createdPlan *models.Plan) {
var count int64
db.Model(&models.Plan{}).Where("name = ?", "重复子计划顺序计划").Count(&count)
assert.Equal(t, int64(0), count, "重复子计划顺序的计划不应被创建")
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := setupTestDB(t)
repo := repository.NewGormPlanRepository(db)
// 准备数据库状态
tc.setupDB(db)
// 执行 Create 操作
err := repo.CreatePlan(tc.inputPlan)
// 断言错误
if tc.expectedError != nil {
assert.Error(t, err)
// 使用 Contains 检查错误信息,因为 fmt.Errorf 会创建新的错误实例
assert.Contains(t, err.Error(), tc.expectedError.Error())
} else {
assert.NoError(t, err)
}
// 验证数据库状态
tc.verifyDB(t, db, tc.inputPlan)
})
}
}
func TestPlanRepository_DeletePlan(t *testing.T) {
type testCase struct {
name string
setupDB func(db *gorm.DB) (planToDeleteID uint)
expectedError string
verifyDB func(t *testing.T, db *gorm.DB, planToDeleteID uint)
}
testCases := []testCase{
{
name: "成功删除-包含任务的计划",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Name: "Plan with Tasks", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
db.Create(&models.Task{PlanID: plan.ID, Name: "Task 1"})
db.Create(&models.Task{PlanID: plan.ID, Name: "Task 2"})
return plan.ID
},
expectedError: "",
verifyDB: func(t *testing.T, db *gorm.DB, planToDeleteID uint) {
var plan models.Plan
err := db.First(&plan, planToDeleteID).Error
assert.Error(t, err)
assert.True(t, errors.Is(err, gorm.ErrRecordNotFound), "计划应被删除")
var taskCount int64
db.Model(&models.Task{}).Where("plan_id = ?", planToDeleteID).Count(&taskCount)
assert.Equal(t, int64(0), taskCount, "关联任务应被删除")
},
},
{
name: "成功删除-包含子计划链接的计划",
setupDB: func(db *gorm.DB) uint {
parentPlan := models.Plan{Name: "Parent Plan", ContentType: models.PlanContentTypeSubPlans}
childPlan := models.Plan{Name: "Child Plan", ContentType: models.PlanContentTypeTasks}
db.Create(&parentPlan)
db.Create(&childPlan)
db.Create(&models.SubPlan{ParentPlanID: parentPlan.ID, ChildPlanID: childPlan.ID})
return parentPlan.ID
},
expectedError: "",
verifyDB: func(t *testing.T, db *gorm.DB, planToDeleteID uint) {
var parentPlan models.Plan
err := db.First(&parentPlan, planToDeleteID).Error
assert.Error(t, err)
assert.True(t, errors.Is(err, gorm.ErrRecordNotFound), "父计划应被删除")
var subPlanLinkCount int64
db.Model(&models.SubPlan{}).Where("parent_plan_id = ?", planToDeleteID).Count(&subPlanLinkCount)
assert.Equal(t, int64(0), subPlanLinkCount, "子计划链接应被删除")
// 验证子计划本身未被删除
var childPlan models.Plan
err = db.First(&childPlan, 2).Error // Assuming childPlan.ID is 2 from setup
assert.NoError(t, err, "子计划本身不应被删除")
},
},
{
name: "失败删除-作为子计划的计划",
setupDB: func(db *gorm.DB) uint {
parentPlan := models.Plan{Name: "Parent Plan", ContentType: models.PlanContentTypeSubPlans}
childPlan := models.Plan{Name: "Child Plan", ContentType: models.PlanContentTypeTasks}
db.Create(&parentPlan)
db.Create(&childPlan)
db.Create(&models.SubPlan{ParentPlanID: parentPlan.ID, ChildPlanID: childPlan.ID})
return childPlan.ID // 尝试删除子计划
},
expectedError: repository.ErrDeleteWithReferencedPlan.Error(),
verifyDB: func(t *testing.T, db *gorm.DB, planToDeleteID uint) {
var childPlan models.Plan
err := db.First(&childPlan, planToDeleteID).Error
assert.NoError(t, err, "子计划不应被删除")
var subPlanLinkCount int64
db.Model(&models.SubPlan{}).Where("child_plan_id = ?", planToDeleteID).Count(&subPlanLinkCount)
assert.Equal(t, int64(1), subPlanLinkCount, "子计划链接不应被删除")
},
},
{
name: "失败删除-不存在的计划",
setupDB: func(db *gorm.DB) uint {
return 999 // 不存在的ID
},
expectedError: "record not found",
verifyDB: func(t *testing.T, db *gorm.DB, planToDeleteID uint) {
// 数据库状态应保持不变
},
},
{
name: "成功删除-不含任何关联的计划",
setupDB: func(db *gorm.DB) uint {
plan := models.Plan{Name: "Simple Plan", ContentType: models.PlanContentTypeTasks}
db.Create(&plan)
return plan.ID
},
expectedError: "",
verifyDB: func(t *testing.T, db *gorm.DB, planToDeleteID uint) {
var plan models.Plan
err := db.First(&plan, planToDeleteID).Error
assert.Error(t, err)
assert.True(t, errors.Is(err, gorm.ErrRecordNotFound), "计划应被删除")
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := setupTestDB(t)
sqlDB, _ := db.DB()
defer sqlDB.Close()
planToDeleteID := tc.setupDB(db)
repo := repository.NewGormPlanRepository(db)
err := repo.DeletePlan(planToDeleteID)
if tc.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedError)
} else {
assert.NoError(t, err)
}
tc.verifyDB(t, db, planToDeleteID)
})
}
}