实现GetPlan单测
This commit is contained in:
@@ -14,11 +14,14 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockPlanRepository 是 repository.PlanRepository 的一个模拟实现,用于测试
|
// MockPlanRepository 是 repository.PlanRepository 的一个模拟实现,用于测试
|
||||||
|
// [保持原样,不做任何修改]
|
||||||
type MockPlanRepository struct {
|
type MockPlanRepository struct {
|
||||||
CreatePlanFunc func(plan *models.Plan) error
|
CreatePlanFunc func(plan *models.Plan) error
|
||||||
|
GetPlanByIDFunc func(id uint) (*models.Plan, error)
|
||||||
// ... 可以根据需要模拟其他接口方法
|
// ... 可以根据需要模拟其他接口方法
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ func (m *MockPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
|
func (m *MockPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
|
||||||
panic("implement me")
|
return m.GetPlanByIDFunc(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockPlanRepository) CreatePlan(plan *models.Plan) error {
|
func (m *MockPlanRepository) CreatePlan(plan *models.Plan) error {
|
||||||
@@ -50,20 +53,22 @@ func (m *MockPlanRepository) DeletePlan(id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupTestRouter 创建一个用于测试的 gin 引擎和控制器实例
|
// setupTestRouter 创建一个用于测试的 gin 引擎和控制器实例
|
||||||
func setupTestRouter(repo repository.PlanRepository) (*gin.Engine, *Controller) {
|
func setupTestRouter(repo repository.PlanRepository) *gin.Engine {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
planController := NewController(logs.NewSilentLogger(), repo)
|
logger := logs.NewSilentLogger()
|
||||||
|
planController := NewController(logger, repo)
|
||||||
router.POST("/plans", planController.CreatePlan)
|
router.POST("/plans", planController.CreatePlan)
|
||||||
return router, planController
|
router.GET("/plans/:id", planController.GetPlan)
|
||||||
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestController_CreatePlan [保持原样,不做任何修改]
|
||||||
func TestController_CreatePlan(t *testing.T) {
|
func TestController_CreatePlan(t *testing.T) {
|
||||||
t.Run("成功-创建包含任务的计划", func(t *testing.T) {
|
t.Run("成功-创建包含任务的计划", func(t *testing.T) {
|
||||||
// Arrange
|
// Arrange
|
||||||
mockRepo := &MockPlanRepository{
|
mockRepo := &MockPlanRepository{
|
||||||
CreatePlanFunc: func(plan *models.Plan) error {
|
CreatePlanFunc: func(plan *models.Plan) error {
|
||||||
// 模拟 GORM 回填 ID
|
|
||||||
plan.ID = 1
|
plan.ID = 1
|
||||||
for i := range plan.Tasks {
|
for i := range plan.Tasks {
|
||||||
plan.Tasks[i].ID = uint(i + 1)
|
plan.Tasks[i].ID = uint(i + 1)
|
||||||
@@ -72,7 +77,7 @@ func TestController_CreatePlan(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
router, _ := setupTestRouter(mockRepo)
|
router := setupTestRouter(mockRepo)
|
||||||
|
|
||||||
reqBody := CreatePlanRequest{
|
reqBody := CreatePlanRequest{
|
||||||
Name: "Test Plan with Tasks",
|
Name: "Test Plan with Tasks",
|
||||||
@@ -101,95 +106,116 @@ func TestController_CreatePlan(t *testing.T) {
|
|||||||
assert.Equal(t, controller.CodeCreated, resp.Code)
|
assert.Equal(t, controller.CodeCreated, resp.Code)
|
||||||
assert.Equal(t, "计划创建成功", resp.Message)
|
assert.Equal(t, "计划创建成功", resp.Message)
|
||||||
|
|
||||||
// 验证 Data 部分
|
|
||||||
dataMap, ok := resp.Data.(map[string]interface{})
|
dataMap, ok := resp.Data.(map[string]interface{})
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, float64(1), dataMap["id"])
|
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))
|
// TestController_GetPlan 是为 GetPlan 方法新增的单元测试函数
|
||||||
req.Header.Set("Content-Type", "application/json")
|
func TestController_GetPlan(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
t.Run("成功-获取计划详情", func(t *testing.T) {
|
||||||
|
|
||||||
// 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
|
// Arrange
|
||||||
mockRepo := &MockPlanRepository{
|
mockRepo := &MockPlanRepository{
|
||||||
CreatePlanFunc: func(plan *models.Plan) error {
|
GetPlanByIDFunc: func(id uint) (*models.Plan, error) {
|
||||||
return repository.ErrNodeDoesNotExist // 模拟仓库层返回的业务错误
|
assert.Equal(t, uint(1), id)
|
||||||
|
return &models.Plan{
|
||||||
|
Model: gorm.Model{ID: 1},
|
||||||
|
Name: "Test Plan",
|
||||||
|
ContentType: models.PlanContentTypeTasks,
|
||||||
|
}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
router, _ := setupTestRouter(mockRepo)
|
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()
|
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
|
// Act
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
@@ -202,30 +228,20 @@ func TestController_CreatePlan(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, controller.CodeBadRequest, resp.Code)
|
assert.Equal(t, controller.CodeBadRequest, resp.Code)
|
||||||
assert.Equal(t, "创建计划失败: "+repository.ErrNodeDoesNotExist.Error(), resp.Message)
|
assert.Equal(t, "无效的计划ID格式", resp.Message)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("失败-仓库层内部错误", func(t *testing.T) {
|
t.Run("失败-仓库层内部错误", func(t *testing.T) {
|
||||||
// Arrange
|
// Arrange
|
||||||
internalErr := errors.New("database connection lost")
|
internalErr := errors.New("database connection lost")
|
||||||
mockRepo := &MockPlanRepository{
|
mockRepo := &MockPlanRepository{
|
||||||
CreatePlanFunc: func(plan *models.Plan) error {
|
GetPlanByIDFunc: func(id uint) (*models.Plan, error) {
|
||||||
return internalErr // 模拟一个未知的内部错误
|
return nil, internalErr
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
router, _ := setupTestRouter(mockRepo)
|
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()
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest(http.MethodGet, "/plans/1", nil)
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
@@ -237,7 +253,7 @@ func TestController_CreatePlan(t *testing.T) {
|
|||||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, controller.CodeBadRequest, resp.Code)
|
assert.Equal(t, controller.CodeInternalError, resp.Code)
|
||||||
assert.Equal(t, "创建计划失败: "+internalErr.Error(), resp.Message)
|
assert.Equal(t, "获取计划详情时发生内部错误", resp.Message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user