增加类型转换时顺序检测
This commit is contained in:
		| @@ -40,10 +40,10 @@ func PlanToResponse(plan *models.Plan) *PlanResponse { | |||||||
| 	return response | 	return response | ||||||
| } | } | ||||||
|  |  | ||||||
| // PlanFromCreateRequest 将CreatePlanRequest转换为Plan模型 | // PlanFromCreateRequest 将CreatePlanRequest转换为Plan模型,并进行业务规则验证 | ||||||
| func PlanFromCreateRequest(req *CreatePlanRequest) *models.Plan { | func PlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { | ||||||
| 	if req == nil { | 	if req == nil { | ||||||
| 		return nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	plan := &models.Plan{ | 	plan := &models.Plan{ | ||||||
| @@ -60,7 +60,7 @@ func PlanFromCreateRequest(req *CreatePlanRequest) *models.Plan { | |||||||
| 		for i, childPlanID := range req.SubPlanIDs { | 		for i, childPlanID := range req.SubPlanIDs { | ||||||
| 			plan.SubPlans[i] = models.SubPlan{ | 			plan.SubPlans[i] = models.SubPlan{ | ||||||
| 				ChildPlanID:    childPlanID, | 				ChildPlanID:    childPlanID, | ||||||
| 				ExecutionOrder: i + 1, // 默认执行顺序 | 				ExecutionOrder: i, // 默认执行顺序, ReorderSteps会再次确认 | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -69,19 +69,27 @@ func PlanFromCreateRequest(req *CreatePlanRequest) *models.Plan { | |||||||
| 	if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil { | 	if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil { | ||||||
| 		plan.Tasks = make([]models.Task, len(req.Tasks)) | 		plan.Tasks = make([]models.Task, len(req.Tasks)) | ||||||
| 		for i, taskReq := range req.Tasks { | 		for i, taskReq := range req.Tasks { | ||||||
|  | 			// 使用来自请求的ExecutionOrder | ||||||
| 			plan.Tasks[i] = TaskFromRequest(&taskReq) | 			plan.Tasks[i] = TaskFromRequest(&taskReq) | ||||||
| 			// 设置执行顺序 |  | ||||||
| 			plan.Tasks[i].ExecutionOrder = i + 1 |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return plan | 	// 1. 首先,执行重复性验证 | ||||||
|  | 	if err := plan.ValidateExecutionOrder(); err != nil { | ||||||
|  | 		// 如果检测到重复,立即返回错误 | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 2. 然后,调用方法来修复顺序断层 | ||||||
|  | 	plan.ReorderSteps() | ||||||
|  |  | ||||||
|  | 	return plan, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // PlanFromUpdateRequest 将UpdatePlanRequest转换为Plan模型 | // PlanFromUpdateRequest 将UpdatePlanRequest转换为Plan模型,并进行业务规则验证 | ||||||
| func PlanFromUpdateRequest(req *UpdatePlanRequest) *models.Plan { | func PlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { | ||||||
| 	if req == nil { | 	if req == nil { | ||||||
| 		return nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	plan := &models.Plan{ | 	plan := &models.Plan{ | ||||||
| @@ -98,7 +106,7 @@ func PlanFromUpdateRequest(req *UpdatePlanRequest) *models.Plan { | |||||||
| 		for i, childPlanID := range req.SubPlanIDs { | 		for i, childPlanID := range req.SubPlanIDs { | ||||||
| 			plan.SubPlans[i] = models.SubPlan{ | 			plan.SubPlans[i] = models.SubPlan{ | ||||||
| 				ChildPlanID:    childPlanID, | 				ChildPlanID:    childPlanID, | ||||||
| 				ExecutionOrder: i + 1, // 默认执行顺序 | 				ExecutionOrder: i, // 默认执行顺序, ReorderSteps会再次确认 | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -107,13 +115,21 @@ func PlanFromUpdateRequest(req *UpdatePlanRequest) *models.Plan { | |||||||
| 	if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil { | 	if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil { | ||||||
| 		plan.Tasks = make([]models.Task, len(req.Tasks)) | 		plan.Tasks = make([]models.Task, len(req.Tasks)) | ||||||
| 		for i, taskReq := range req.Tasks { | 		for i, taskReq := range req.Tasks { | ||||||
|  | 			// 使用来自请求的ExecutionOrder | ||||||
| 			plan.Tasks[i] = TaskFromRequest(&taskReq) | 			plan.Tasks[i] = TaskFromRequest(&taskReq) | ||||||
| 			// 设置执行顺序 |  | ||||||
| 			plan.Tasks[i].ExecutionOrder = i + 1 |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return plan | 	// 1. 首先,执行重复性验证 | ||||||
|  | 	if err := plan.ValidateExecutionOrder(); err != nil { | ||||||
|  | 		// 如果检测到重复,立即返回错误 | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 2. 然后,调用方法来修复顺序断层 | ||||||
|  | 	plan.ReorderSteps() | ||||||
|  |  | ||||||
|  | 	return plan, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // SubPlanToResponse 将SubPlan模型转换为SubPlanResponse | // SubPlanToResponse 将SubPlan模型转换为SubPlanResponse | ||||||
|   | |||||||
							
								
								
									
										459
									
								
								internal/app/controller/plan/converter_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										459
									
								
								internal/app/controller/plan/converter_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,459 @@ | |||||||
|  | package plan_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller" | ||||||
|  | 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan" | ||||||
|  | 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"gorm.io/datatypes" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestPlanToResponse(t *testing.T) { | ||||||
|  | 	t.Run("nil plan", func(t *testing.T) { | ||||||
|  | 		response := plan.PlanToResponse(nil) | ||||||
|  | 		assert.Nil(t, response) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("basic plan without associations", func(t *testing.T) { | ||||||
|  | 		planModel := &models.Plan{ | ||||||
|  | 			Model:          gorm.Model{ID: 1}, | ||||||
|  | 			Name:           "Test Plan", | ||||||
|  | 			Description:    "A test plan", | ||||||
|  | 			ExecutionType:  models.PlanExecutionTypeAutomatic, | ||||||
|  | 			CronExpression: "0 0 * * *", | ||||||
|  | 			ContentType:    models.PlanContentTypeTasks, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		response := plan.PlanToResponse(planModel) | ||||||
|  | 		assert.NotNil(t, response) | ||||||
|  | 		assert.Equal(t, uint(1), response.ID) | ||||||
|  | 		assert.Equal(t, "Test Plan", response.Name) | ||||||
|  | 		assert.Equal(t, "A test plan", response.Description) | ||||||
|  | 		assert.Equal(t, models.PlanExecutionTypeAutomatic, response.ExecutionType) | ||||||
|  | 		assert.Equal(t, "0 0 * * *", response.CronExpression) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeTasks, response.ContentType) | ||||||
|  | 		assert.Empty(t, response.SubPlans) | ||||||
|  | 		assert.Empty(t, response.Tasks) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with sub plans", func(t *testing.T) { | ||||||
|  | 		childPlan := &models.Plan{ | ||||||
|  | 			Model:       gorm.Model{ID: 2}, | ||||||
|  | 			Name:        "Child Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel := &models.Plan{ | ||||||
|  | 			Model:       gorm.Model{ID: 1}, | ||||||
|  | 			Name:        "Parent Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeSubPlans, | ||||||
|  | 			SubPlans: []models.SubPlan{ | ||||||
|  | 				{ | ||||||
|  | 					Model:          gorm.Model{ID: 10}, | ||||||
|  | 					ParentPlanID:   1, | ||||||
|  | 					ChildPlanID:    2, | ||||||
|  | 					ExecutionOrder: 1, | ||||||
|  | 					ChildPlan:      childPlan, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		response := plan.PlanToResponse(planModel) | ||||||
|  | 		assert.NotNil(t, response) | ||||||
|  | 		assert.Equal(t, uint(1), response.ID) | ||||||
|  | 		assert.Equal(t, "Parent Plan", response.Name) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeSubPlans, response.ContentType) | ||||||
|  | 		assert.Len(t, response.SubPlans, 1) | ||||||
|  | 		assert.Empty(t, response.Tasks) | ||||||
|  |  | ||||||
|  | 		subPlanResp := response.SubPlans[0] | ||||||
|  | 		assert.Equal(t, uint(10), subPlanResp.ID) | ||||||
|  | 		assert.Equal(t, uint(1), subPlanResp.ParentPlanID) | ||||||
|  | 		assert.Equal(t, uint(2), subPlanResp.ChildPlanID) | ||||||
|  | 		assert.Equal(t, 1, subPlanResp.ExecutionOrder) | ||||||
|  | 		assert.NotNil(t, subPlanResp.ChildPlan) | ||||||
|  | 		assert.Equal(t, "Child Plan", subPlanResp.ChildPlan.Name) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with tasks", func(t *testing.T) { | ||||||
|  | 		params := datatypes.JSON([]byte(`{"device_id": 1, "value": 25}`)) | ||||||
|  |  | ||||||
|  | 		planModel := &models.Plan{ | ||||||
|  | 			Model:       gorm.Model{ID: 1}, | ||||||
|  | 			Name:        "Task Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []models.Task{ | ||||||
|  | 				{ | ||||||
|  | 					Model:          gorm.Model{ID: 10}, | ||||||
|  | 					PlanID:         1, | ||||||
|  | 					Name:           "Task 1", | ||||||
|  | 					Description:    "First task", | ||||||
|  | 					ExecutionOrder: 1, | ||||||
|  | 					Type:           models.TaskTypeWaiting, | ||||||
|  | 					Parameters:     params, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		response := plan.PlanToResponse(planModel) | ||||||
|  | 		assert.NotNil(t, response) | ||||||
|  | 		assert.Equal(t, uint(1), response.ID) | ||||||
|  | 		assert.Equal(t, "Task Plan", response.Name) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeTasks, response.ContentType) | ||||||
|  | 		assert.Len(t, response.Tasks, 1) | ||||||
|  | 		assert.Empty(t, response.SubPlans) | ||||||
|  |  | ||||||
|  | 		taskResp := response.Tasks[0] | ||||||
|  | 		assert.Equal(t, uint(10), taskResp.ID) | ||||||
|  | 		assert.Equal(t, uint(1), taskResp.PlanID) | ||||||
|  | 		assert.Equal(t, "Task 1", taskResp.Name) | ||||||
|  | 		assert.Equal(t, "First task", taskResp.Description) | ||||||
|  | 		assert.Equal(t, 1, taskResp.ExecutionOrder) | ||||||
|  | 		assert.Equal(t, models.TaskTypeWaiting, taskResp.Type) | ||||||
|  | 		assert.Equal(t, controller.Properties(params), taskResp.Parameters) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPlanFromCreateRequest(t *testing.T) { | ||||||
|  | 	t.Run("nil request", func(t *testing.T) { | ||||||
|  | 		planModel, err := plan.PlanFromCreateRequest(nil) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Nil(t, planModel) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("basic plan without associations", func(t *testing.T) { | ||||||
|  | 		req := &plan.CreatePlanRequest{ | ||||||
|  | 			Name:           "Test Plan", | ||||||
|  | 			Description:    "A test plan", | ||||||
|  | 			ExecutionType:  models.PlanExecutionTypeAutomatic, | ||||||
|  | 			CronExpression: "0 0 * * *", | ||||||
|  | 			ContentType:    models.PlanContentTypeTasks, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromCreateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Equal(t, "Test Plan", planModel.Name) | ||||||
|  | 		assert.Equal(t, "A test plan", planModel.Description) | ||||||
|  | 		assert.Equal(t, models.PlanExecutionTypeAutomatic, planModel.ExecutionType) | ||||||
|  | 		assert.Equal(t, "0 0 * * *", planModel.CronExpression) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) | ||||||
|  | 		assert.Empty(t, planModel.SubPlans) | ||||||
|  | 		assert.Empty(t, planModel.Tasks) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with sub plan IDs", func(t *testing.T) { | ||||||
|  | 		req := &plan.CreatePlanRequest{ | ||||||
|  | 			Name:        "Parent Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeSubPlans, | ||||||
|  | 			SubPlanIDs:  []uint{2, 3}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromCreateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Equal(t, "Parent Plan", planModel.Name) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeSubPlans, planModel.ContentType) | ||||||
|  | 		assert.Len(t, planModel.SubPlans, 2) | ||||||
|  | 		assert.Empty(t, planModel.Tasks) | ||||||
|  |  | ||||||
|  | 		assert.Equal(t, uint(2), planModel.SubPlans[0].ChildPlanID) | ||||||
|  | 		assert.Equal(t, 1, planModel.SubPlans[0].ExecutionOrder) | ||||||
|  | 		assert.Equal(t, uint(3), planModel.SubPlans[1].ChildPlanID) | ||||||
|  | 		assert.Equal(t, 2, planModel.SubPlans[1].ExecutionOrder) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with tasks", func(t *testing.T) { | ||||||
|  | 		params := controller.Properties([]byte(`{"device_id": 1, "value": 25}`)) | ||||||
|  |  | ||||||
|  | 		req := &plan.CreatePlanRequest{ | ||||||
|  | 			Name:        "Task Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []plan.TaskRequest{ | ||||||
|  | 				{ | ||||||
|  | 					Name:           "Task 1", | ||||||
|  | 					Description:    "First task", | ||||||
|  | 					ExecutionOrder: 1, | ||||||
|  | 					Type:           models.TaskTypeWaiting, | ||||||
|  | 					Parameters:     params, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromCreateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Equal(t, "Task Plan", planModel.Name) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) | ||||||
|  | 		assert.Len(t, planModel.Tasks, 1) | ||||||
|  | 		assert.Empty(t, planModel.SubPlans) | ||||||
|  |  | ||||||
|  | 		task := planModel.Tasks[0] | ||||||
|  | 		assert.Equal(t, "Task 1", task.Name) | ||||||
|  | 		assert.Equal(t, "First task", task.Description) | ||||||
|  | 		assert.Equal(t, 1, task.ExecutionOrder) | ||||||
|  | 		assert.Equal(t, models.TaskTypeWaiting, task.Type) | ||||||
|  | 		assert.Equal(t, datatypes.JSON(params), task.Parameters) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with tasks with gapped execution order", func(t *testing.T) { | ||||||
|  | 		req := &plan.CreatePlanRequest{ | ||||||
|  | 			Name:        "Task Plan with Gaps", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []plan.TaskRequest{ | ||||||
|  | 				{Name: "Task 3", ExecutionOrder: 5}, | ||||||
|  | 				{Name: "Task 1", ExecutionOrder: 2}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromCreateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Len(t, planModel.Tasks, 2) | ||||||
|  |  | ||||||
|  | 		// After ReorderSteps, tasks are sorted by their original ExecutionOrder and then re-numbered. | ||||||
|  | 		assert.Equal(t, "Task 1", planModel.Tasks[0].Name) | ||||||
|  | 		assert.Equal(t, 1, planModel.Tasks[0].ExecutionOrder) | ||||||
|  | 		assert.Equal(t, "Task 3", planModel.Tasks[1].Name) | ||||||
|  | 		assert.Equal(t, 2, planModel.Tasks[1].ExecutionOrder) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with duplicate task execution order", func(t *testing.T) { | ||||||
|  | 		req := &plan.CreatePlanRequest{ | ||||||
|  | 			Name:        "Invalid Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []plan.TaskRequest{ | ||||||
|  | 				{Name: "Task 1", ExecutionOrder: 1}, | ||||||
|  | 				{Name: "Task 2", ExecutionOrder: 1}, // Duplicate order | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromCreateRequest(req) | ||||||
|  | 		assert.Error(t, err) | ||||||
|  | 		assert.Contains(t, err.Error(), "任务执行顺序重复") | ||||||
|  | 		assert.Nil(t, planModel) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPlanFromUpdateRequest(t *testing.T) { | ||||||
|  | 	t.Run("nil request", func(t *testing.T) { | ||||||
|  | 		planModel, err := plan.PlanFromUpdateRequest(nil) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Nil(t, planModel) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("basic plan without associations", func(t *testing.T) { | ||||||
|  | 		req := &plan.UpdatePlanRequest{ | ||||||
|  | 			Name:           "Updated Plan", | ||||||
|  | 			Description:    "An updated plan", | ||||||
|  | 			ExecutionType:  models.PlanExecutionTypeManual, | ||||||
|  | 			CronExpression: "0 30 * * *", | ||||||
|  | 			ContentType:    models.PlanContentTypeTasks, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromUpdateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Equal(t, "Updated Plan", planModel.Name) | ||||||
|  | 		assert.Equal(t, "An updated plan", planModel.Description) | ||||||
|  | 		assert.Equal(t, models.PlanExecutionTypeManual, planModel.ExecutionType) | ||||||
|  | 		assert.Equal(t, "0 30 * * *", planModel.CronExpression) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) | ||||||
|  | 		assert.Empty(t, planModel.SubPlans) | ||||||
|  | 		assert.Empty(t, planModel.Tasks) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with sub plan IDs", func(t *testing.T) { | ||||||
|  | 		req := &plan.UpdatePlanRequest{ | ||||||
|  | 			Name:        "Updated Parent Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeSubPlans, | ||||||
|  | 			SubPlanIDs:  []uint{2, 3}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromUpdateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  |  | ||||||
|  | 		assert.Equal(t, "Updated Parent Plan", planModel.Name) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeSubPlans, planModel.ContentType) | ||||||
|  | 		assert.Len(t, planModel.SubPlans, 2) | ||||||
|  | 		assert.Empty(t, planModel.Tasks) | ||||||
|  |  | ||||||
|  | 		assert.Equal(t, uint(2), planModel.SubPlans[0].ChildPlanID) | ||||||
|  | 		assert.Equal(t, 1, planModel.SubPlans[0].ExecutionOrder) | ||||||
|  | 		assert.Equal(t, uint(3), planModel.SubPlans[1].ChildPlanID) | ||||||
|  | 		assert.Equal(t, 2, planModel.SubPlans[1].ExecutionOrder) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with tasks", func(t *testing.T) { | ||||||
|  | 		params := controller.Properties([]byte(`{"device_id": 1, "value": 25}`)) | ||||||
|  |  | ||||||
|  | 		req := &plan.UpdatePlanRequest{ | ||||||
|  | 			Name:        "Updated Task Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []plan.TaskRequest{ | ||||||
|  | 				{ | ||||||
|  | 					Name:           "Task 1", | ||||||
|  | 					Description:    "First task", | ||||||
|  | 					ExecutionOrder: 1, | ||||||
|  | 					Type:           models.TaskTypeWaiting, | ||||||
|  | 					Parameters:     params, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromUpdateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Equal(t, "Updated Task Plan", planModel.Name) | ||||||
|  | 		assert.Equal(t, models.PlanContentTypeTasks, planModel.ContentType) | ||||||
|  | 		assert.Len(t, planModel.Tasks, 1) | ||||||
|  | 		assert.Empty(t, planModel.SubPlans) | ||||||
|  |  | ||||||
|  | 		task := planModel.Tasks[0] | ||||||
|  | 		assert.Equal(t, "Task 1", task.Name) | ||||||
|  | 		assert.Equal(t, 1, task.ExecutionOrder) | ||||||
|  | 		assert.Equal(t, datatypes.JSON(params), task.Parameters) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with duplicate task execution order", func(t *testing.T) { | ||||||
|  | 		req := &plan.UpdatePlanRequest{ | ||||||
|  | 			Name:        "Invalid Updated Plan", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []plan.TaskRequest{ | ||||||
|  | 				{Name: "Task 1", ExecutionOrder: 1}, | ||||||
|  | 				{Name: "Task 2", ExecutionOrder: 1}, // Duplicate order | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromUpdateRequest(req) | ||||||
|  | 		assert.Error(t, err) | ||||||
|  | 		assert.Contains(t, err.Error(), "任务执行顺序重复") | ||||||
|  | 		assert.Nil(t, planModel) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("plan with tasks with gapped execution order", func(t *testing.T) { | ||||||
|  | 		req := &plan.UpdatePlanRequest{ | ||||||
|  | 			Name:        "Updated Task Plan with Gaps", | ||||||
|  | 			ContentType: models.PlanContentTypeTasks, | ||||||
|  | 			Tasks: []plan.TaskRequest{ | ||||||
|  | 				{Name: "Task 3", ExecutionOrder: 5}, | ||||||
|  | 				{Name: "Task 1", ExecutionOrder: 2}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		planModel, err := plan.PlanFromUpdateRequest(req) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotNil(t, planModel) | ||||||
|  | 		assert.Len(t, planModel.Tasks, 2) | ||||||
|  |  | ||||||
|  | 		// After ReorderSteps, tasks are sorted by their original ExecutionOrder and then re-numbered. | ||||||
|  | 		assert.Equal(t, "Task 1", planModel.Tasks[0].Name) | ||||||
|  | 		assert.Equal(t, 1, planModel.Tasks[0].ExecutionOrder) | ||||||
|  | 		assert.Equal(t, "Task 3", planModel.Tasks[1].Name) | ||||||
|  | 		assert.Equal(t, 2, planModel.Tasks[1].ExecutionOrder) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSubPlanToResponse(t *testing.T) { | ||||||
|  | 	t.Run("nil sub plan", func(t *testing.T) { | ||||||
|  | 		response := plan.SubPlanToResponse(nil) | ||||||
|  | 		assert.Equal(t, plan.SubPlanResponse{}, response) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("sub plan without child plan", func(t *testing.T) { | ||||||
|  | 		subPlan := &models.SubPlan{ | ||||||
|  | 			Model:          gorm.Model{ID: 10}, | ||||||
|  | 			ParentPlanID:   1, | ||||||
|  | 			ChildPlanID:    2, | ||||||
|  | 			ExecutionOrder: 1, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		response := plan.SubPlanToResponse(subPlan) | ||||||
|  | 		assert.Equal(t, uint(10), response.ID) | ||||||
|  | 		assert.Equal(t, uint(1), response.ParentPlanID) | ||||||
|  | 		assert.Equal(t, uint(2), response.ChildPlanID) | ||||||
|  | 		assert.Equal(t, 1, response.ExecutionOrder) | ||||||
|  | 		assert.Nil(t, response.ChildPlan) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("sub plan with child plan", func(t *testing.T) { | ||||||
|  | 		childPlan := &models.Plan{ | ||||||
|  | 			Model: gorm.Model{ID: 2}, | ||||||
|  | 			Name:  "Child Plan", | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		subPlan := &models.SubPlan{ | ||||||
|  | 			Model:          gorm.Model{ID: 10}, | ||||||
|  | 			ParentPlanID:   1, | ||||||
|  | 			ChildPlanID:    2, | ||||||
|  | 			ExecutionOrder: 1, | ||||||
|  | 			ChildPlan:      childPlan, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		response := plan.SubPlanToResponse(subPlan) | ||||||
|  | 		assert.Equal(t, uint(10), response.ID) | ||||||
|  | 		assert.Equal(t, uint(1), response.ParentPlanID) | ||||||
|  | 		assert.Equal(t, uint(2), response.ChildPlanID) | ||||||
|  | 		assert.Equal(t, 1, response.ExecutionOrder) | ||||||
|  | 		assert.NotNil(t, response.ChildPlan) | ||||||
|  | 		assert.Equal(t, "Child Plan", response.ChildPlan.Name) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTaskToResponse(t *testing.T) { | ||||||
|  | 	t.Run("nil task", func(t *testing.T) { | ||||||
|  | 		response := plan.TaskToResponse(nil) | ||||||
|  | 		assert.Equal(t, plan.TaskResponse{}, response) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("task with parameters", func(t *testing.T) { | ||||||
|  | 		params := datatypes.JSON([]byte(`{"device_id": 1, "value": 25}`)) | ||||||
|  | 		task := &models.Task{ | ||||||
|  | 			Model:          gorm.Model{ID: 10}, | ||||||
|  | 			PlanID:         1, | ||||||
|  | 			Name:           "Test Task", | ||||||
|  | 			Description:    "A test task", | ||||||
|  | 			ExecutionOrder: 1, | ||||||
|  | 			Type:           models.TaskTypeWaiting, | ||||||
|  | 			Parameters:     params, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		response := plan.TaskToResponse(task) | ||||||
|  | 		assert.Equal(t, uint(10), response.ID) | ||||||
|  | 		assert.Equal(t, uint(1), response.PlanID) | ||||||
|  | 		assert.Equal(t, "Test Task", response.Name) | ||||||
|  | 		assert.Equal(t, "A test task", response.Description) | ||||||
|  | 		assert.Equal(t, 1, response.ExecutionOrder) | ||||||
|  | 		assert.Equal(t, models.TaskTypeWaiting, response.Type) | ||||||
|  | 		assert.Equal(t, controller.Properties(params), response.Parameters) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTaskFromRequest(t *testing.T) { | ||||||
|  | 	t.Run("nil request", func(t *testing.T) { | ||||||
|  | 		task := plan.TaskFromRequest(nil) | ||||||
|  | 		assert.Equal(t, models.Task{}, task) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("task with parameters", func(t *testing.T) { | ||||||
|  | 		params := controller.Properties([]byte(`{"device_id": 1, "value": 25}`)) | ||||||
|  | 		req := &plan.TaskRequest{ | ||||||
|  | 			Name:           "Test Task", | ||||||
|  | 			Description:    "A test task", | ||||||
|  | 			ExecutionOrder: 1, | ||||||
|  | 			Type:           models.TaskTypeWaiting, | ||||||
|  | 			Parameters:     params, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		task := plan.TaskFromRequest(req) | ||||||
|  | 		assert.Equal(t, "Test Task", task.Name) | ||||||
|  | 		assert.Equal(t, "A test task", task.Description) | ||||||
|  | 		assert.Equal(t, 1, task.ExecutionOrder) | ||||||
|  | 		assert.Equal(t, models.TaskTypeWaiting, task.Type) | ||||||
|  | 		assert.Equal(t, datatypes.JSON(params), task.Parameters) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user