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, ChildPlan: &models.Plan{Model: gorm.Model{ID: 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.Create(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) }) } }