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) }) }