Files
pig-farm-controller/internal/app/controller/device/device_controller_test.go
2025-10-06 15:08:32 +08:00

742 lines
24 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package device_test
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// MockDeviceRepository 是 DeviceRepository 接口的模拟实现
type MockDeviceRepository struct {
mock.Mock
}
// CreateTx 模拟 DeviceRepository 的 CreateTx 方法
func (m *MockDeviceRepository) Create(device *models.Device) error {
args := m.Called(device)
return args.Error(0)
}
// FindByID 模拟 DeviceRepository 的 FindByID 方法
func (m *MockDeviceRepository) FindByID(id uint) (*models.Device, error) {
args := m.Called(id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*models.Device), args.Error(1)
}
// FindByIDString 模拟 DeviceRepository 的 FindByIDString 方法
func (m *MockDeviceRepository) FindByIDString(id string) (*models.Device, error) {
args := m.Called(id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*models.Device), args.Error(1)
}
// ListAll 模拟 DeviceRepository 的 ListAll 方法
func (m *MockDeviceRepository) ListAll() ([]*models.Device, error) {
args := m.Called()
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*models.Device), args.Error(1)
}
// ListByParentID 模拟 DeviceRepository 的 ListByParentID 方法
func (m *MockDeviceRepository) ListByParentID(parentID *uint) ([]*models.Device, error) {
args := m.Called(parentID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*models.Device), args.Error(1)
}
// Update 模拟 DeviceRepository 的 Update 方法
func (m *MockDeviceRepository) Update(device *models.Device) error {
args := m.Called(device)
return args.Error(0)
}
// Delete 模拟 DeviceRepository 的 Delete 方法
func (m *MockDeviceRepository) Delete(id uint) error {
args := m.Called(id)
return args.Error(0)
}
// testCase 结构体定义了所有测试用例的通用参数
type testCase struct {
name string
httpMethod string // 新增字段HTTP 方法
requestBody interface{}
paramID string // URL 中的 ID 参数
mockRepoSetup func(*MockDeviceRepository)
expectedStatus int // HTTP 状态码
expectedCode int // 业务状态码
expectedMessage string
expectedDataFunc func(interface{}) bool // 用于验证 data 字段的函数
}
// runTest 是一个辅助函数,用于执行单个测试用例
func runTest(t *testing.T, tc testCase, controllerMethod func(*gin.Context, *MockDeviceRepository)) {
// 初始化 Gin 上下文
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
// 设置请求体和 HTTP 方法
if tc.requestBody != nil {
jsonBody, _ := json.Marshal(tc.requestBody)
ctx.Request = httptest.NewRequest(tc.httpMethod, "/", io.NopCloser(bytes.NewBuffer(jsonBody)))
ctx.Request.Header.Set("Content-Type", "application/json")
} else {
// 对于没有请求体的请求 (GET, DELETE, 或没有 body 的 POST/PUT)
ctx.Request = httptest.NewRequest(tc.httpMethod, "/", nil)
}
// 设置 URL 参数
if tc.paramID != "" {
ctx.Params = append(ctx.Params, gin.Param{Key: "id", Value: tc.paramID})
}
// 创建 Mock Repository
mockRepo := new(MockDeviceRepository)
// 设置 Mock 行为
tc.mockRepoSetup(mockRepo)
// 调用被测试的方法,并传入 mockRepo
controllerMethod(ctx, mockRepo)
// 解析响应体
var responseBody controller.Response
err := json.Unmarshal(w.Body.Bytes(), &responseBody)
assert.NoError(t, err)
// 断言 HTTP 状态码始终为 200 OK
assert.Equal(t, tc.expectedStatus, w.Code)
// 断言业务状态码和消息
assert.Equal(t, tc.expectedCode, responseBody.Code)
assert.Equal(t, tc.expectedMessage, responseBody.Message)
// 断言数据字段
if tc.expectedDataFunc != nil {
var data interface{}
// 只有当 responseBody.Data 不为 nil 且其底层类型为 []byte 时才尝试 Unmarshal
if responseBody.Data != nil {
if byteData, ok := responseBody.Data.([]byte); ok {
err = json.Unmarshal(byteData, &data)
assert.NoError(t, err, "无法解析响应数据") // 增加对 Unmarshal 错误的断言
} else {
// 如果 Data 不为 nil 但也不是 []byte这通常不应该发生
// 但为了健壮性,直接将原始 interface{} 赋值给 data
data = responseBody.Data
}
}
assert.True(t, tc.expectedDataFunc(data), "数据字段验证失败")
}
// 验证 Mock 期望是否都已满足
mockRepo.AssertExpectations(t)
}
func TestCreateDevice(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []testCase{
{
name: "成功创建区域主控",
httpMethod: http.MethodPost,
requestBody: device.CreateDeviceRequest{
Name: "主控A",
Type: models.DeviceTypeAreaController,
Location: "猪舍1",
Properties: controller.Properties(`{"lora_address":"0x1234"}`),
},
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("CreateTx", mock.MatchedBy(func(dev *models.Device) bool {
// 检查 Name 字段
nameMatch := dev.Name == "主控A"
// 检查 Type 字段
typeMatch := dev.Type == models.DeviceTypeAreaController
// 检查 Location 字段
locationMatch := dev.Location == "猪舍1"
// 检查 Properties 字段的字节内容
expectedProperties := controller.Properties(`{"lora_address":"0x1234"}`)
propertiesMatch := bytes.Equal(dev.Properties, expectedProperties)
return nameMatch && typeMatch && locationMatch && propertiesMatch
})).Return(nil).Run(func(args mock.Arguments) {
// 模拟 GORM 自动填充 ID
arg := args.Get(0).(*models.Device)
arg.ID = 1
arg.CreatedAt = time.Now()
arg.UpdatedAt = time.Now()
}).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeCreated,
expectedMessage: "设备创建成功",
expectedDataFunc: func(data interface{}) bool {
dataMap, ok := data.(map[string]interface{})
if !ok {
return false
}
return dataMap["id"] != nil &&
dataMap["name"] == "主控A" &&
dataMap["type"] == string(models.DeviceTypeAreaController) &&
dataMap["properties"] != nil
},
},
{
name: "成功创建普通设备",
httpMethod: http.MethodPost,
requestBody: device.CreateDeviceRequest{
Name: "温度传感器",
Type: models.DeviceTypeDevice,
SubType: models.SubTypeSensorTemp,
ParentID: func() *uint { id := uint(1); return &id }(),
Location: "猪舍1-A区",
Properties: controller.Properties(`{"bus_id":1,"bus_address":10}`),
},
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("CreateTx", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
arg := args.Get(0).(*models.Device)
arg.ID = 2
arg.CreatedAt = time.Now()
arg.UpdatedAt = time.Now()
}).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeCreated,
expectedMessage: "设备创建成功",
expectedDataFunc: func(data interface{}) bool {
dataMap, ok := data.(map[string]interface{})
if !ok {
return false
}
return dataMap["id"] != nil &&
dataMap["name"] == "温度传感器" &&
dataMap["type"] == string(models.DeviceTypeDevice) &&
dataMap["sub_type"] == string(models.SubTypeSensorTemp) &&
dataMap["parent_id"] != nil &&
dataMap["properties"] != nil
},
},
{
name: "请求参数绑定失败",
httpMethod: http.MethodPost,
requestBody: device.CreateDeviceRequest{
Name: "", // 缺少必填字段 Name
Type: models.DeviceTypeAreaController,
},
mockRepoSetup: func(m *MockDeviceRepository) {},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeBadRequest,
expectedMessage: "Key: 'CreateDeviceRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "数据库创建失败",
httpMethod: http.MethodPost,
requestBody: device.CreateDeviceRequest{
Name: "失败设备",
Type: models.DeviceTypeDevice,
},
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("CreateTx", mock.Anything).Return(errors.New("db error")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError,
expectedMessage: "创建设备失败",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
// 新增Properties字段JSON格式无效
{
name: "Properties字段JSON格式无效",
httpMethod: http.MethodPost,
requestBody: device.CreateDeviceRequest{
Name: "无效JSON设备",
Type: models.DeviceTypeDevice,
Properties: controller.Properties(`{invalid json}`),
},
mockRepoSetup: func(m *MockDeviceRepository) {
// 期望 CreateTx 方法被调用,并返回一个模拟的数据库错误
// 这个错误模拟的是数据库层因为 Properties 字段的 JSON 格式无效而拒绝保存
m.On("CreateTx", mock.Anything).Return(errors.New("database error: invalid json format")).Run(func(args mock.Arguments) {
dev := args.Get(0).(*models.Device)
assert.Equal(t, "无效JSON设备", dev.Name)
assert.Equal(t, models.DeviceTypeDevice, dev.Type)
expectedProperties := controller.Properties(`{invalid json}`)
assert.True(t, bytes.Equal(dev.Properties, expectedProperties), "Properties should match")
}).Once()
},
expectedStatus: http.StatusOK, // HTTP status is 200 OK for business errors
expectedCode: controller.CodeInternalError, // Business code for internal server error
expectedMessage: "创建设备失败", // The message returned by the controller
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runTest(t, tc, func(ctx *gin.Context, repo *MockDeviceRepository) {
device.NewController(repo, logs.NewSilentLogger()).CreateDevice(ctx)
})
})
}
}
func TestGetDevice(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []testCase{
{
name: "成功获取设备",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "1").Return(&models.Device{
Model: gorm.Model{
ID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Name: "测试设备",
Type: models.DeviceTypeAreaController,
Location: "测试地点",
Properties: datatypes.JSON(`{"key":"value"}`),
}, nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeSuccess,
expectedMessage: "获取设备信息成功",
expectedDataFunc: func(data interface{}) bool {
dataMap, ok := data.(map[string]interface{})
if !ok {
return false
}
return dataMap["id"] == float64(1) &&
dataMap["name"] == "测试设备" &&
dataMap["properties"] != nil
},
},
{
name: "设备未找到",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "999",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "999").Return(nil, gorm.ErrRecordNotFound).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeNotFound,
expectedMessage: "设备未找到",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "ID格式无效",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "abc",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "abc").Return(nil, errors.New("无效的设备ID格式")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeBadRequest,
expectedMessage: "无效的设备ID格式",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "数据库查询失败",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "1").Return(nil, errors.New("db error")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError,
expectedMessage: "获取设备信息失败",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runTest(t, tc, func(ctx *gin.Context, repo *MockDeviceRepository) {
device.NewController(repo, logs.NewSilentLogger()).GetDevice(ctx)
})
})
}
}
func TestListDevices(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []testCase{
{
name: "成功获取空列表",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("ListAll").Return([]*models.Device{}, nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeSuccess,
expectedMessage: "获取设备列表成功",
expectedDataFunc: func(data interface{}) bool {
s, ok := data.([]interface{})
return ok && len(s) == 0
},
},
{
name: "成功获取包含设备的列表",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("ListAll").Return([]*models.Device{
{
Model: gorm.Model{
ID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Name: "设备1",
Type: models.DeviceTypeAreaController,
},
{
Model: gorm.Model{
ID: 2,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Name: "设备2",
Type: models.DeviceTypeDevice,
SubType: models.SubTypeFan,
ParentID: func() *uint { id := uint(1); return &id }(),
},
}, nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeSuccess,
expectedMessage: "获取设备列表成功",
expectedDataFunc: func(data interface{}) bool {
dataList, ok := data.([]interface{})
if !ok {
return false
}
// 检查长度
if len(dataList) != 2 {
return false
}
// 检查第一个设备
item1, ok1 := dataList[0].(map[string]interface{})
if !ok1 || item1["id"] != float64(1) || item1["name"] != "设备1" {
return false
}
// 检查第二个设备
item2, ok2 := dataList[1].(map[string]interface{})
if !ok2 || item2["id"] != float64(2) || item2["name"] != "设备2" {
return false
}
return true
},
},
{
name: "数据库查询失败",
httpMethod: http.MethodGet,
requestBody: nil,
paramID: "",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("ListAll").Return(nil, errors.New("db error")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError,
expectedMessage: "获取设备列表失败",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runTest(t, tc, func(ctx *gin.Context, repo *MockDeviceRepository) {
device.NewController(repo, logs.NewSilentLogger()).ListDevices(ctx)
})
})
}
}
func TestUpdateDevice(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []testCase{
{
name: "成功更新设备",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "更新后的主控",
Type: models.DeviceTypeAreaController,
Location: "新地点",
Properties: controller.Properties(`{"lora_address":"0x5678"}`),
},
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
// 模拟 FindByIDString 找到设备
m.On("FindByIDString", "1").Return(&models.Device{
Model: gorm.Model{
ID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Name: "旧主控",
Type: models.DeviceTypeAreaController,
Location: "旧地点",
Properties: datatypes.JSON(`{"lora_address":"0x1234"}`),
}, nil).Once()
// 模拟 Update 成功
m.On("Update", mock.AnythingOfType("*models.Device")).Return(nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeSuccess,
expectedMessage: "设备更新成功",
expectedDataFunc: func(data interface{}) bool {
dataMap, ok := data.(map[string]interface{})
if !ok {
return false
}
return dataMap["id"] == float64(1) &&
dataMap["name"] == "更新后的主控" &&
dataMap["location"] == "新地点" &&
dataMap["properties"] != nil
},
},
{
name: "请求参数绑定失败",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "", // 缺少必填字段 Name
Type: models.DeviceTypeAreaController,
},
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
// 模拟 FindByIDString 找到设备,以便进入参数绑定阶段
m.On("FindByIDString", "1").Return(&models.Device{Model: gorm.Model{ID: 1}}, nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeBadRequest,
expectedMessage: "Key: 'UpdateDeviceRequest.Name' Error:Field validation for 'Name' failed on the 'required' tag",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "设备未找到",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "任意名称", Type: models.DeviceTypeAreaController,
},
paramID: "999",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "999").Return(nil, gorm.ErrRecordNotFound).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeNotFound,
expectedMessage: "设备未找到",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "ID格式无效",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "任意名称", Type: models.DeviceTypeAreaController,
},
paramID: "abc",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "abc").Return(nil, errors.New("无效的设备ID格式")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeBadRequest,
expectedMessage: "无效的设备ID格式",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "数据库更新失败",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "更新失败设备", Type: models.DeviceTypeAreaController,
},
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("FindByIDString", "1").Return(&models.Device{Model: gorm.Model{ID: 1}}, nil).Once()
m.On("Update", mock.AnythingOfType("*models.Device")).Return(errors.New("db error")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError,
expectedMessage: "更新设备失败",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
// 新增Properties字段JSON格式无效
{
name: "Properties字段JSON格式无效",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "无效JSON设备",
Type: models.DeviceTypeDevice,
Properties: controller.Properties(`{invalid json}`),
},
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
// 模拟 FindByIDString 找到设备,以便进入参数绑定阶段
m.On("FindByIDString", "1").Return(&models.Device{Model: gorm.Model{ID: 1}}, nil).Once()
// 期望 Update 方法被调用,并返回一个模拟的数据库错误
m.On("Update", mock.Anything).Return(errors.New("database error: invalid json format")).Run(func(args mock.Arguments) {
dev := args.Get(0).(*models.Device)
assert.Equal(t, "无效JSON设备", dev.Name)
assert.Equal(t, models.DeviceTypeDevice, dev.Type)
expectedProperties := controller.Properties(`{invalid json}`)
assert.True(t, bytes.Equal(dev.Properties, expectedProperties), "Properties should match")
}).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError, // Expected to be internal server error due to DB error
expectedMessage: "更新设备失败", // The message returned by the controller
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
// 新增成功更新设备的ParentID
{
name: "成功更新设备的ParentID",
httpMethod: http.MethodPut,
requestBody: device.UpdateDeviceRequest{
Name: "更新ParentID设备",
Type: models.DeviceTypeDevice,
ParentID: func() *uint { id := uint(10); return &id }(),
Location: "新地点",
Properties: controller.Properties(`{"key":"value"}`),
},
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
// 模拟 FindByIDString 找到设备
m.On("FindByIDString", "1").Return(&models.Device{
Model: gorm.Model{
ID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Name: "旧设备",
Type: models.DeviceTypeDevice,
ParentID: func() *uint { id := uint(1); return &id }(),
Location: "旧地点",
Properties: datatypes.JSON(`{"old_key":"old_value"}`),
}, nil).Once()
// 模拟 Update 成功,并验证 ParentID 被更新
m.On("Update", mock.MatchedBy(func(dev *models.Device) bool {
return dev.ID == 1 && *dev.ParentID == 10
})).Return(nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeSuccess,
expectedMessage: "设备更新成功",
expectedDataFunc: func(data interface{}) bool {
dataMap, ok := data.(map[string]interface{})
if !ok {
return false
}
return dataMap["id"] == float64(1) &&
dataMap["parent_id"] == float64(10) &&
dataMap["properties"] != nil
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runTest(t, tc, func(ctx *gin.Context, repo *MockDeviceRepository) {
device.NewController(repo, logs.NewSilentLogger()).UpdateDevice(ctx)
})
})
}
}
func TestDeleteDevice(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []testCase{
{
name: "成功删除设备",
httpMethod: http.MethodDelete,
requestBody: nil,
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("Delete", uint(1)).Return(nil).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeSuccess,
expectedMessage: "设备删除成功",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "ID格式无效",
httpMethod: http.MethodDelete,
requestBody: nil,
paramID: "abc",
mockRepoSetup: func(m *MockDeviceRepository) {},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeBadRequest,
expectedMessage: "无效的设备ID格式",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
{
name: "数据库删除失败",
httpMethod: http.MethodDelete,
requestBody: nil,
paramID: "1",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("Delete", uint(1)).Return(errors.New("db error")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError,
expectedMessage: "删除设备失败",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
// 新增:删除设备未找到
{
name: "删除设备未找到",
httpMethod: http.MethodDelete,
requestBody: nil,
paramID: "999",
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("Delete", uint(999)).Return(gorm.ErrRecordNotFound).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError, // 当前控制器逻辑会将 ErrRecordNotFound 视为内部错误
expectedMessage: "删除设备失败",
expectedDataFunc: func(data interface{}) bool { return data == nil },
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runTest(t, tc, func(ctx *gin.Context, repo *MockDeviceRepository) {
device.NewController(repo, logs.NewSilentLogger()).DeleteDevice(ctx)
})
})
}
}