From ec2595a751521bf08068f99ac6256473a053ca44 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 13 Sep 2025 17:49:43 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/repository/plan_repository_test.go | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/internal/infra/repository/plan_repository_test.go b/internal/infra/repository/plan_repository_test.go index c846462..b436ff4 100644 --- a/internal/infra/repository/plan_repository_test.go +++ b/internal/infra/repository/plan_repository_test.go @@ -661,6 +661,49 @@ func TestUpdatePlan_Reconciliation(t *testing.T) { 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 { @@ -736,6 +779,32 @@ func TestUpdatePlan_Reconciliation(t *testing.T) { 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 { @@ -776,6 +845,166 @@ func TestUpdatePlan_Reconciliation(t *testing.T) { 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 {