466 lines
13 KiB
Go
466 lines
13 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"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// MockPlanRepository 是 repository.PlanRepository 的一个模拟实现,用于测试
|
||
type MockPlanRepository struct {
|
||
CreatePlanFunc func(plan *models.Plan) error
|
||
GetPlanByIDFunc func(id uint) (*models.Plan, error)
|
||
ListBasicPlansFunc func() ([]models.Plan, error)
|
||
DeletePlanFunc func(id uint) error
|
||
}
|
||
|
||
func (m *MockPlanRepository) ListBasicPlans() ([]models.Plan, error) {
|
||
return m.ListBasicPlansFunc()
|
||
}
|
||
|
||
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 {
|
||
return m.DeletePlanFunc(id)
|
||
}
|
||
|
||
// 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)
|
||
router.GET("/plans", planController.ListPlans)
|
||
router.DELETE("/plans/:id", planController.DeletePlan)
|
||
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)
|
||
})
|
||
}
|
||
|
||
// 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)
|
||
})
|
||
}
|
||
|
||
// TestController_DeletePlan 是 DeletePlan 的单元测试
|
||
func TestController_DeletePlan(t *testing.T) {
|
||
t.Run("成功-删除计划", func(t *testing.T) {
|
||
// Arrange
|
||
mockRepo := &MockPlanRepository{
|
||
DeletePlanFunc: func(id uint) error {
|
||
assert.Equal(t, uint(1), id)
|
||
return nil // Simulate successful deletion
|
||
},
|
||
}
|
||
router := setupTestRouter(mockRepo)
|
||
w := httptest.NewRecorder()
|
||
req, _ := http.NewRequest(http.MethodDelete, "/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)
|
||
assert.Equal(t, "计划删除成功", resp.Message)
|
||
assert.Nil(t, resp.Data)
|
||
})
|
||
|
||
t.Run("失败-计划不存在", func(t *testing.T) {
|
||
// Arrange
|
||
mockRepo := &MockPlanRepository{
|
||
DeletePlanFunc: func(id uint) error {
|
||
return gorm.ErrRecordNotFound // Simulate not found
|
||
},
|
||
}
|
||
router := setupTestRouter(mockRepo)
|
||
w := httptest.NewRecorder()
|
||
req, _ := http.NewRequest(http.MethodDelete, "/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.CodeInternalError, resp.Code)
|
||
assert.Equal(t, "删除计划时发生内部错误", resp.Message)
|
||
})
|
||
|
||
t.Run("失败-无效的ID格式", func(t *testing.T) {
|
||
// Arrange
|
||
mockRepo := &MockPlanRepository{} // No repo call expected
|
||
router := setupTestRouter(mockRepo)
|
||
w := httptest.NewRecorder()
|
||
req, _ := http.NewRequest(http.MethodDelete, "/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("something went wrong")
|
||
mockRepo := &MockPlanRepository{
|
||
DeletePlanFunc: func(id uint) error {
|
||
return internalErr // Simulate internal error
|
||
},
|
||
}
|
||
router := setupTestRouter(mockRepo)
|
||
w := httptest.NewRecorder()
|
||
req, _ := http.NewRequest(http.MethodDelete, "/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)
|
||
})
|
||
}
|