Files
pig-farm-controller/internal/app/controller/plan/plan_controller_test.go
2025-09-14 16:12:56 +08:00

244 lines
6.9 KiB
Go

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"
)
// MockPlanRepository 是 repository.PlanRepository 的一个模拟实现,用于测试
type MockPlanRepository struct {
CreatePlanFunc func(plan *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) {
panic("implement me")
}
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, *Controller) {
gin.SetMode(gin.TestMode)
router := gin.Default()
planController := NewController(logs.NewSilentLogger(), repo)
router.POST("/plans", planController.CreatePlan)
return router, planController
}
func TestController_CreatePlan(t *testing.T) {
t.Run("成功-创建包含任务的计划", func(t *testing.T) {
// Arrange
mockRepo := &MockPlanRepository{
CreatePlanFunc: func(plan *models.Plan) error {
// 模拟 GORM 回填 ID
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)
// 验证 Data 部分
dataMap, ok := resp.Data.(map[string]interface{})
assert.True(t, ok)
assert.Equal(t, float64(1), dataMap["id"])
assert.Equal(t, "Test Plan with Tasks", dataMap["name"])
tasks, ok := dataMap["tasks"].([]interface{})
assert.True(t, ok)
assert.Len(t, tasks, 1)
})
t.Run("失败-无效的请求体", func(t *testing.T) {
// Arrange
mockRepo := &MockPlanRepository{}
router, _ := setupTestRouter(mockRepo)
req, _ := http.NewRequest(http.MethodPost, "/plans", bytes.NewBufferString("{invalid json"))
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.CodeBadRequest, resp.Code)
assert.Contains(t, resp.Message, "无效的请求体")
})
t.Run("失败-转换器业务校验失败", func(t *testing.T) {
// Arrange
mockRepo := &MockPlanRepository{}
router, _ := setupTestRouter(mockRepo)
// 创建一个带有重复执行顺序的请求,这将导致 PlanFromCreateRequest 返回错误
reqBody := CreatePlanRequest{
Name: "Duplicate Order Plan",
ExecutionType: models.PlanExecutionTypeManual,
ContentType: models.PlanContentTypeTasks,
Tasks: []TaskRequest{
{Name: "Task 1", ExecutionOrder: 1},
{Name: "Task 2", ExecutionOrder: 1}, // 重复
},
}
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.CodeBadRequest, resp.Code)
assert.Contains(t, resp.Message, "计划数据校验失败")
assert.Contains(t, resp.Message, "任务执行顺序重复")
})
t.Run("失败-仓库层业务错误", func(t *testing.T) {
// Arrange
mockRepo := &MockPlanRepository{
CreatePlanFunc: func(plan *models.Plan) error {
return repository.ErrNodeDoesNotExist // 模拟仓库层返回的业务错误
},
}
router, _ := setupTestRouter(mockRepo)
reqBody := CreatePlanRequest{
Name: "Plan with non-existent sub-plan",
ExecutionType: models.PlanExecutionTypeManual,
ContentType: models.PlanContentTypeSubPlans,
SubPlanIDs: []uint{999}, // 假设这个ID不存在
}
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.CodeBadRequest, resp.Code)
assert.Equal(t, "创建计划失败: "+repository.ErrNodeDoesNotExist.Error(), resp.Message)
})
t.Run("失败-仓库层内部错误", func(t *testing.T) {
// Arrange
internalErr := errors.New("database connection lost")
mockRepo := &MockPlanRepository{
CreatePlanFunc: func(plan *models.Plan) error {
return internalErr // 模拟一个未知的内部错误
},
}
router, _ := setupTestRouter(mockRepo)
reqBody := CreatePlanRequest{
Name: "Test Plan",
ExecutionType: models.PlanExecutionTypeManual,
ContentType: models.PlanContentTypeTasks,
Tasks: []TaskRequest{},
}
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.CodeBadRequest, resp.Code)
assert.Equal(t, "创建计划失败: "+internalErr.Error(), resp.Message)
})
}