package plan import ( "bytes" "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "gorm.io/gorm" ) // MockPlanRepository 是 repository.PlanRepository 的一个模拟实现,用于测试 // [保持原样,不做任何修改] type MockPlanRepository struct { CreatePlanFunc func(plan *models.Plan) error GetPlanByIDFunc func(id uint) (*models.Plan, error) // ... 可以根据需要模拟其他接口方法 } func (m *MockPlanRepository) ListBasicPlans() ([]models.Plan, error) { panic("implement me") } func (m *MockPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) { panic("implement me") } func (m *MockPlanRepository) GetPlanByID(id uint) (*models.Plan, error) { return m.GetPlanByIDFunc(id) } func (m *MockPlanRepository) CreatePlan(plan *models.Plan) error { if m.CreatePlanFunc != nil { return m.CreatePlanFunc(plan) } return nil } func (m *MockPlanRepository) UpdatePlan(plan *models.Plan) error { panic("implement me") } func (m *MockPlanRepository) DeletePlan(id uint) error { panic("implement me") } // setupTestRouter 创建一个用于测试的 gin 引擎和控制器实例 func setupTestRouter(repo repository.PlanRepository) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.Default() logger := logs.NewSilentLogger() planController := NewController(logger, repo) router.POST("/plans", planController.CreatePlan) router.GET("/plans/:id", planController.GetPlan) return router } // TestController_CreatePlan [保持原样,不做任何修改] func TestController_CreatePlan(t *testing.T) { t.Run("成功-创建包含任务的计划", func(t *testing.T) { // Arrange mockRepo := &MockPlanRepository{ CreatePlanFunc: func(plan *models.Plan) error { plan.ID = 1 for i := range plan.Tasks { plan.Tasks[i].ID = uint(i + 1) plan.Tasks[i].PlanID = plan.ID } return nil }, } router := setupTestRouter(mockRepo) reqBody := CreatePlanRequest{ Name: "Test Plan with Tasks", ExecutionType: models.PlanExecutionTypeManual, ContentType: models.PlanContentTypeTasks, Tasks: []TaskRequest{ {Name: "Task 1", ExecutionOrder: 1, Type: models.TaskTypeWaiting}, }, } bodyBytes, _ := json.Marshal(reqBody) req, _ := http.NewRequest(http.MethodPost, "/plans", bytes.NewBuffer(bodyBytes)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() // Act router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp controller.Response err := json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, controller.CodeCreated, resp.Code) assert.Equal(t, "计划创建成功", resp.Message) dataMap, ok := resp.Data.(map[string]interface{}) assert.True(t, ok) assert.Equal(t, float64(1), dataMap["id"]) }) } // TestController_GetPlan 是为 GetPlan 方法新增的单元测试函数 func TestController_GetPlan(t *testing.T) { t.Run("成功-获取计划详情", func(t *testing.T) { // Arrange mockRepo := &MockPlanRepository{ GetPlanByIDFunc: func(id uint) (*models.Plan, error) { assert.Equal(t, uint(1), id) return &models.Plan{ Model: gorm.Model{ID: 1}, Name: "Test Plan", ContentType: models.PlanContentTypeTasks, }, nil }, } router := setupTestRouter(mockRepo) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/plans/1", nil) // Act router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp controller.Response err := json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, controller.CodeSuccess, resp.Code) dataMap, ok := resp.Data.(map[string]interface{}) assert.True(t, ok) assert.Equal(t, float64(1), dataMap["id"]) }) t.Run("成功-获取内容为空的计划详情", func(t *testing.T) { // Arrange mockRepo := &MockPlanRepository{ GetPlanByIDFunc: func(id uint) (*models.Plan, error) { assert.Equal(t, uint(3), id) return &models.Plan{ Model: gorm.Model{ID: 3}, Name: "Empty Plan", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}, // 任务列表为空 }, nil }, } router := setupTestRouter(mockRepo) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/plans/3", nil) // Act router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp controller.Response err := json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, controller.CodeSuccess, resp.Code) dataMap, ok := resp.Data.(map[string]interface{}) assert.True(t, ok) assert.Equal(t, float64(3), dataMap["id"]) assert.Equal(t, "Empty Plan", dataMap["name"]) // 关键断言:因为 omitempty 标签,当 tasks 列表为空时,该字段不应该出现在JSON中 _, ok = dataMap["tasks"] assert.False(t, ok, "当任务列表为空时,'tasks' 字段因为 omitempty 标签,不应该出现在JSON响应中") }) t.Run("失败-计划不存在", func(t *testing.T) { // Arrange mockRepo := &MockPlanRepository{ GetPlanByIDFunc: func(id uint) (*models.Plan, error) { return nil, gorm.ErrRecordNotFound }, } router := setupTestRouter(mockRepo) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/plans/999", nil) // Act router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp controller.Response err := json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, controller.CodeNotFound, resp.Code) assert.Equal(t, "计划不存在", resp.Message) }) t.Run("失败-无效的ID格式", func(t *testing.T) { // Arrange mockRepo := &MockPlanRepository{} router := setupTestRouter(mockRepo) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/plans/abc", nil) // Act router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp controller.Response err := json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, controller.CodeBadRequest, resp.Code) assert.Equal(t, "无效的计划ID格式", resp.Message) }) t.Run("失败-仓库层内部错误", func(t *testing.T) { // Arrange internalErr := errors.New("database connection lost") mockRepo := &MockPlanRepository{ GetPlanByIDFunc: func(id uint) (*models.Plan, error) { return nil, internalErr }, } router := setupTestRouter(mockRepo) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/plans/1", nil) // Act router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) var resp controller.Response err := json.Unmarshal(w.Body.Bytes(), &resp) assert.NoError(t, err) assert.Equal(t, controller.CodeInternalError, resp.Code) assert.Equal(t, "获取计划详情时发生内部错误", resp.Message) }) }