重构 #4
| @@ -1,12 +1,20 @@ | |||||||
| package repository | package repository | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // 定义仓库层可导出的公共错误 | ||||||
|  | var ( | ||||||
|  | 	ErrUpdateWithInvalidRoot = errors.New("更新操作的目标根计划无效或ID为0") | ||||||
|  | 	ErrNewSubPlanInUpdate    = errors.New("计划树中包含一个ID为0的新子计划,更新操作只允许关联已存在的计划") | ||||||
|  | 	ErrNodeDoesNotExist      = errors.New("计划树中包含一个或多个在数据库中不存在的计划") | ||||||
|  | ) | ||||||
|  |  | ||||||
| // PlanRepository 定义了与计划模型相关的数据库操作接口 | // PlanRepository 定义了与计划模型相关的数据库操作接口 | ||||||
| // 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现 | // 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现 | ||||||
| type PlanRepository interface { | type PlanRepository interface { | ||||||
| @@ -118,7 +126,7 @@ func (r *gormPlanRepository) updatePlanTx(tx *gorm.DB, plan *models.Plan) error | |||||||
| func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) error { | func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) error { | ||||||
| 	// 1. 检查根节点 | 	// 1. 检查根节点 | ||||||
| 	if plan == nil || plan.ID == 0 { | 	if plan == nil || plan.ID == 0 { | ||||||
| 		return fmt.Errorf("更新操作的目标计划无效或ID为0") | 		return ErrUpdateWithInvalidRoot | ||||||
| 	} | 	} | ||||||
| 	if len(plan.Tasks) > 0 && len(plan.SubPlans) > 0 { | 	if len(plan.Tasks) > 0 && len(plan.SubPlans) > 0 { | ||||||
| 		return fmt.Errorf("计划 (ID: %d) 不能同时包含任务和子计划", plan.ID) | 		return fmt.Errorf("计划 (ID: %d) 不能同时包含任务和子计划", plan.ID) | ||||||
| @@ -143,7 +151,7 @@ func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) er | |||||||
| 			return fmt.Errorf("检查计划存在性时出错: %w", err) | 			return fmt.Errorf("检查计划存在性时出错: %w", err) | ||||||
| 		} | 		} | ||||||
| 		if int(count) != len(idsToCheck) { | 		if int(count) != len(idsToCheck) { | ||||||
| 			return fmt.Errorf("计划树中包含一个或多个在数据库中不存在的计划") | 			return ErrNodeDoesNotExist | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -156,7 +164,7 @@ func validateNodeAndDetectCycles(plan *models.Plan, allIDs, recursionStack map[u | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	if plan.ID == 0 { | 	if plan.ID == 0 { | ||||||
| 		return fmt.Errorf("错误:计划树中包含一个ID为0的新计划,更新操作只允许操作已存在的计划") | 		return ErrNewSubPlanInUpdate | ||||||
| 	} | 	} | ||||||
| 	if recursionStack[plan.ID] { | 	if recursionStack[plan.ID] { | ||||||
| 		return fmt.Errorf("检测到循环引用:计划 (ID: %d) 是其自身的祖先", plan.ID) | 		return fmt.Errorf("检测到循环引用:计划 (ID: %d) 是其自身的祖先", plan.ID) | ||||||
|   | |||||||
| @@ -398,3 +398,400 @@ func cleanPlanForComparison(p *models.Plan) { | |||||||
| 		cleanPlanForComparison(p.SubPlans[i].ChildPlan) | 		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}, | ||||||
|  | 					{ChildPlanID: 3, ChildPlan: planC}, | ||||||
|  | 				} | ||||||
|  | 				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}, | ||||||
|  | 					{ChildPlanID: 3, ChildPlan: planC}, // 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: "不能同时包含任务和子计划", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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 { | ||||||
|  | 				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: "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) | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user