1226 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1226 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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, "新任务应被创建")
 | ||
| 			},
 | ||
| 		},
 | ||
| 	}
 | ||
| 
 | ||
| 	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)
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 |