Files
pig-farm-controller/internal/app/controller/plan/plan_controller_test.go

362 lines
9.9 KiB
Go
Raw Normal View History

2025-09-14 16:12:56 +08:00
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"
2025-09-14 16:40:28 +08:00
"gorm.io/gorm"
2025-09-14 16:12:56 +08:00
)
// MockPlanRepository 是 repository.PlanRepository 的一个模拟实现,用于测试
type MockPlanRepository struct {
2025-09-14 16:52:59 +08:00
CreatePlanFunc func(plan *models.Plan) error
GetPlanByIDFunc func(id uint) (*models.Plan, error)
ListBasicPlansFunc func() ([]models.Plan, error)
2025-09-14 16:12:56 +08:00
}
func (m *MockPlanRepository) ListBasicPlans() ([]models.Plan, error) {
2025-09-14 16:52:59 +08:00
return m.ListBasicPlansFunc()
2025-09-14 16:12:56 +08:00
}
func (m *MockPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) {
panic("implement me")
}
func (m *MockPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
2025-09-14 16:40:28 +08:00
return m.GetPlanByIDFunc(id)
2025-09-14 16:12:56 +08:00
}
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 引擎和控制器实例
2025-09-14 16:40:28 +08:00
func setupTestRouter(repo repository.PlanRepository) *gin.Engine {
2025-09-14 16:12:56 +08:00
gin.SetMode(gin.TestMode)
router := gin.Default()
2025-09-14 16:40:28 +08:00
logger := logs.NewSilentLogger()
planController := NewController(logger, repo)
2025-09-14 16:12:56 +08:00
router.POST("/plans", planController.CreatePlan)
2025-09-14 16:40:28 +08:00
router.GET("/plans/:id", planController.GetPlan)
2025-09-14 16:52:59 +08:00
router.GET("/plans", planController.ListPlans)
2025-09-14 16:40:28 +08:00
return router
2025-09-14 16:12:56 +08:00
}
2025-09-14 16:40:28 +08:00
// TestController_CreatePlan [保持原样,不做任何修改]
2025-09-14 16:12:56 +08:00
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
},
}
2025-09-14 16:40:28 +08:00
router := setupTestRouter(mockRepo)
2025-09-14 16:12:56 +08:00
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"])
})
2025-09-14 16:40:28 +08:00
}
2025-09-14 16:12:56 +08:00
2025-09-14 16:40:28 +08:00
// TestController_GetPlan 是为 GetPlan 方法新增的单元测试函数
func TestController_GetPlan(t *testing.T) {
t.Run("成功-获取计划详情", func(t *testing.T) {
2025-09-14 16:12:56 +08:00
// Arrange
2025-09-14 16:40:28 +08:00
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)
2025-09-14 16:12:56 +08:00
w := httptest.NewRecorder()
2025-09-14 16:40:28 +08:00
req, _ := http.NewRequest(http.MethodGet, "/plans/1", nil)
2025-09-14 16:12:56 +08:00
// 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)
2025-09-14 16:40:28 +08:00
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"])
2025-09-14 16:12:56 +08:00
})
2025-09-14 16:40:28 +08:00
t.Run("成功-获取内容为空的计划详情", func(t *testing.T) {
2025-09-14 16:12:56 +08:00
// Arrange
2025-09-14 16:40:28 +08:00
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
2025-09-14 16:12:56 +08:00
},
}
2025-09-14 16:40:28 +08:00
router := setupTestRouter(mockRepo)
2025-09-14 16:12:56 +08:00
w := httptest.NewRecorder()
2025-09-14 16:40:28 +08:00
req, _ := http.NewRequest(http.MethodGet, "/plans/3", nil)
2025-09-14 16:12:56 +08:00
// 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)
2025-09-14 16:40:28 +08:00
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响应中")
2025-09-14 16:12:56 +08:00
})
2025-09-14 16:40:28 +08:00
t.Run("失败-计划不存在", func(t *testing.T) {
2025-09-14 16:12:56 +08:00
// Arrange
mockRepo := &MockPlanRepository{
2025-09-14 16:40:28 +08:00
GetPlanByIDFunc: func(id uint) (*models.Plan, error) {
return nil, gorm.ErrRecordNotFound
2025-09-14 16:12:56 +08:00
},
}
2025-09-14 16:40:28 +08:00
router := setupTestRouter(mockRepo)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/plans/999", nil)
2025-09-14 16:12:56 +08:00
2025-09-14 16:40:28 +08:00
// Act
router.ServeHTTP(w, req)
2025-09-14 16:12:56 +08:00
2025-09-14 16:40:28 +08:00
// 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)
2025-09-14 16:12:56 +08:00
w := httptest.NewRecorder()
2025-09-14 16:40:28 +08:00
req, _ := http.NewRequest(http.MethodGet, "/plans/abc", nil)
2025-09-14 16:12:56 +08:00
// 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)
2025-09-14 16:40:28 +08:00
assert.Equal(t, "无效的计划ID格式", resp.Message)
2025-09-14 16:12:56 +08:00
})
t.Run("失败-仓库层内部错误", func(t *testing.T) {
// Arrange
internalErr := errors.New("database connection lost")
mockRepo := &MockPlanRepository{
2025-09-14 16:40:28 +08:00
GetPlanByIDFunc: func(id uint) (*models.Plan, error) {
return nil, internalErr
2025-09-14 16:12:56 +08:00
},
}
2025-09-14 16:40:28 +08:00
router := setupTestRouter(mockRepo)
2025-09-14 16:12:56 +08:00
w := httptest.NewRecorder()
2025-09-14 16:40:28 +08:00
req, _ := http.NewRequest(http.MethodGet, "/plans/1", nil)
2025-09-14 16:12:56 +08:00
// 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)
2025-09-14 16:40:28 +08:00
assert.Equal(t, controller.CodeInternalError, resp.Code)
assert.Equal(t, "获取计划详情时发生内部错误", resp.Message)
2025-09-14 16:12:56 +08:00
})
}
2025-09-14 16:52:59 +08:00
// TestController_ListPlans tests the ListPlans method
func TestController_ListPlans(t *testing.T) {
t.Run("成功-获取计划列表", func(t *testing.T) {
// Arrange
mockPlans := []models.Plan{
{Model: gorm.Model{ID: 1}, Name: "Plan 1", ContentType: models.PlanContentTypeTasks},
{Model: gorm.Model{ID: 2}, Name: "Plan 2", ContentType: models.PlanContentTypeTasks},
}
mockRepo := &MockPlanRepository{
ListBasicPlansFunc: func() ([]models.Plan, error) {
return mockPlans, nil
},
}
router := setupTestRouter(mockRepo)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/plans", 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)
assert.Equal(t, "获取计划列表成功", resp.Message)
dataBytes, err := json.Marshal(resp.Data)
assert.NoError(t, err)
var listResp ListPlansResponse
err = json.Unmarshal(dataBytes, &listResp)
assert.NoError(t, err)
assert.Equal(t, 2, listResp.Total)
assert.Len(t, listResp.Plans, 2)
assert.Equal(t, uint(1), listResp.Plans[0].ID)
assert.Equal(t, "Plan 1", listResp.Plans[0].Name)
})
t.Run("成功-返回空列表", func(t *testing.T) {
// Arrange
mockRepo := &MockPlanRepository{
ListBasicPlansFunc: func() ([]models.Plan, error) {
return []models.Plan{}, nil
},
}
router := setupTestRouter(mockRepo)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/plans", 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)
dataBytes, err := json.Marshal(resp.Data)
assert.NoError(t, err)
var listResp ListPlansResponse
err = json.Unmarshal(dataBytes, &listResp)
assert.NoError(t, err)
assert.Equal(t, 0, listResp.Total)
assert.Len(t, listResp.Plans, 0)
})
t.Run("失败-仓库层返回错误", func(t *testing.T) {
// Arrange
dbErr := errors.New("db error")
mockRepo := &MockPlanRepository{
ListBasicPlansFunc: func() ([]models.Plan, error) {
return nil, dbErr
},
}
router := setupTestRouter(mockRepo)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/plans", 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)
})
}