Files
pig-farm-controller/internal/infra/repository/plan_repository_test.go

401 lines
14 KiB
Go
Raw Normal View History

2025-09-13 13:04:19 +08:00
package repository_test
import (
"errors"
2025-09-13 15:42:03 +08:00
"fmt"
2025-09-13 13:04:19 +08:00
"testing"
2025-09-13 15:42:03 +08:00
"time"
2025-09-13 13:04:19 +08:00
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)
// createTestPlan 是一个辅助函数,用于创建测试计划。
func createTestPlan(name, description string, execType models.PlanExecutionType, contentType models.PlanContentType) models.Plan {
return models.Plan{
Name: name,
Description: description,
ExecutionType: execType,
ContentType: contentType,
}
}
// TestListBasicPlans 测试 ListBasicPlans 方法,确保它能正确返回所有计划的基本信息。
func TestListBasicPlans(t *testing.T) {
tests := []struct {
name string
setupPlans []models.Plan
expectedCount int
expectedError error
}{
{
name: "数据库中没有计划",
setupPlans: []models.Plan{},
expectedCount: 0,
expectedError: nil,
},
{
name: "数据库中有多个计划",
setupPlans: []models.Plan{
createTestPlan("计划 A", "描述 A", models.PlanExecutionTypeAutomatic, models.PlanContentTypeTasks),
createTestPlan("计划 B", "描述 B", models.PlanExecutionTypeManual, models.PlanContentTypeSubPlans),
},
expectedCount: 2,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := setupTestDB(t)
repo := repository.NewGormPlanRepository(db)
for i := range tt.setupPlans {
err := db.Create(&tt.setupPlans[i]).Error
assert.NoError(t, err, "插入设置计划失败")
}
plans, err := repo.ListBasicPlans()
if tt.expectedError != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.expectedError))
} else {
assert.NoError(t, err)
assert.Len(t, plans, tt.expectedCount)
if tt.expectedCount > 0 {
// 验证返回的计划是否包含预期的ID
var actualIDs []uint
for _, p := range plans {
actualIDs = append(actualIDs, p.ID)
assert.Empty(t, p.SubPlans, "ListBasicPlans 不应加载子计划")
assert.Empty(t, p.Tasks, "ListBasicPlans 不应加载任务")
}
for _, setupPlan := range tt.setupPlans {
assert.Contains(t, actualIDs, setupPlan.ID, "返回的计划应包含设置计划的ID")
}
}
}
})
}
}
// TestGetBasicPlanByID 测试 GetBasicPlanByID 方法确保它能根据ID正确返回计划的基本信息。
func TestGetBasicPlanByID(t *testing.T) {
tests := []struct {
name string
setupPlan models.Plan
idToFetch uint
expectFound bool
expectedError error
}{
{
name: "通过ID找到计划",
setupPlan: createTestPlan("计划 C", "描述 C", models.PlanExecutionTypeAutomatic, models.PlanContentTypeTasks),
idToFetch: 0, // 创建后设置
expectFound: true,
},
{
name: "通过ID未找到计划",
setupPlan: models.Plan{}, // 此情况下无需设置计划
idToFetch: 999,
expectFound: false,
expectedError: gorm.ErrRecordNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := setupTestDB(t)
repo := repository.NewGormPlanRepository(db)
if tt.setupPlan.Name != "" { // 仅在有效设置时创建计划
err := db.Create(&tt.setupPlan).Error
assert.NoError(t, err, "插入设置计划失败")
tt.idToFetch = tt.setupPlan.ID // 使用数据库生成的ID
}
fetchedPlan, err := repo.GetBasicPlanByID(tt.idToFetch)
if tt.expectedError != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.expectedError), "预期错误类型不匹配")
assert.Nil(t, fetchedPlan)
} else {
assert.NoError(t, err)
assert.NotNil(t, fetchedPlan)
assert.Equal(t, tt.setupPlan.Name, fetchedPlan.Name)
assert.Equal(t, tt.setupPlan.Description, fetchedPlan.Description)
assert.Equal(t, tt.setupPlan.ExecutionType, fetchedPlan.ExecutionType)
assert.Equal(t, tt.setupPlan.ContentType, fetchedPlan.ContentType)
assert.Empty(t, fetchedPlan.SubPlans, "GetBasicPlanByID 不应加载子计划")
assert.Empty(t, fetchedPlan.Tasks, "GetBasicPlanByID 不应加载任务")
}
})
}
}
2025-09-13 15:42:03 +08:00
// TestGetPlanByID 测试 GetPlanByID 方法确保它能根据ID正确返回计划的完整信息包括关联数据。
func TestGetPlanByID(t *testing.T) {
type testCase struct {
name string
setupData func(db *gorm.DB) // 用于在测试前插入数据
planID uint
expectedPlan *models.Plan
expectedError string
}
testCases := []testCase{
{
name: "PlanNotFound",
setupData: func(db *gorm.DB) {
// 不插入任何数据
},
planID: 999,
expectedPlan: nil,
expectedError: "record not found",
},
{
name: "PlanWithTasks",
setupData: func(db *gorm.DB) {
// 使用硬编码的ID创建计划使测试可预测
plan := models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan A",
ContentType: models.PlanContentTypeTasks,
}
db.Create(&plan)
// 创建任务它们的ID将由数据库自动生成
db.Create(&models.Task{PlanID: 1, Name: "Task 2", ExecutionOrder: 1})
db.Create(&models.Task{PlanID: 1, Name: "Task 1", ExecutionOrder: 2})
},
planID: 1,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 1},
Name: "Plan A",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
// 期望按 "order" 字段升序排序
{PlanID: 1, Name: "Task 2", ExecutionOrder: 1},
{PlanID: 1, Name: "Task 1", ExecutionOrder: 2},
},
},
expectedError: "",
},
{
name: "PlanWithMultiLevelSubPlans",
setupData: func(db *gorm.DB) {
// 创建一个三层结构的计划
db.Create(&models.Plan{Model: gorm.Model{ID: 20}, Name: "Grandparent Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 21}, Name: "Parent Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 22}, Name: "Child Plan With Tasks", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Task{PlanID: 22, Name: "Grandchild's Task", ExecutionOrder: 1})
// 创建关联关系
db.Create(&models.SubPlan{ParentPlanID: 20, ChildPlanID: 21, ExecutionOrder: 1})
db.Create(&models.SubPlan{ParentPlanID: 21, ChildPlanID: 22, ExecutionOrder: 1})
},
planID: 20,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 20},
Name: "Grandparent Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{
ParentPlanID: 20,
ChildPlanID: 21,
ExecutionOrder: 1,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 21},
Name: "Parent Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{
ParentPlanID: 21,
ChildPlanID: 22,
ExecutionOrder: 1,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 22},
Name: "Child Plan With Tasks",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{
{PlanID: 22, Name: "Grandchild's Task", ExecutionOrder: 1},
},
},
},
},
},
},
},
},
expectedError: "",
},
{
name: "UnknownContentType",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{
Model: gorm.Model{ID: 30},
Name: "Unknown Type Plan",
ContentType: "INVALID_TYPE",
})
},
planID: 30,
expectedPlan: nil,
expectedError: fmt.Sprintf("未知的计划内容类型: INVALID_TYPE; 计划ID: 30"),
},
// 新增场景:测试空的关联数据
{
name: "PlanWithTasksType_ButNoTasks",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{
Model: gorm.Model{ID: 50},
Name: "Plan with empty tasks",
ContentType: models.PlanContentTypeTasks,
})
},
planID: 50,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 50},
Name: "Plan with empty tasks",
ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{}, // 期望一个空的切片
},
expectedError: "",
},
// 新增场景:测试复杂的同级排序
{
name: "PlanWithSubPlans_ComplexSorting",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 60}, Name: "Main Plan For Sorting", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 61}, Name: "SubPlan C", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Plan{Model: gorm.Model{ID: 62}, Name: "SubPlan A", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Plan{Model: gorm.Model{ID: 63}, Name: "SubPlan B", ContentType: models.PlanContentTypeTasks})
// 故意打乱顺序插入
db.Create(&models.SubPlan{ParentPlanID: 60, ChildPlanID: 61, ExecutionOrder: 3})
db.Create(&models.SubPlan{ParentPlanID: 60, ChildPlanID: 62, ExecutionOrder: 1})
db.Create(&models.SubPlan{ParentPlanID: 60, ChildPlanID: 63, ExecutionOrder: 2})
},
planID: 60,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 60},
Name: "Main Plan For Sorting",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ParentPlanID: 60, ChildPlanID: 62, ExecutionOrder: 1, ChildPlan: &models.Plan{Model: gorm.Model{ID: 62}, Name: "SubPlan A", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}}},
{ParentPlanID: 60, ChildPlanID: 63, ExecutionOrder: 2, ChildPlan: &models.Plan{Model: gorm.Model{ID: 63}, Name: "SubPlan B", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}}},
{ParentPlanID: 60, ChildPlanID: 61, ExecutionOrder: 3, ChildPlan: &models.Plan{Model: gorm.Model{ID: 61}, Name: "SubPlan C", ContentType: models.PlanContentTypeTasks, Tasks: []models.Task{}}},
},
},
expectedError: "",
},
// 新增场景:测试混合内容的子计划树
{
name: "PlanWithSubPlans_MixedContentTypes",
setupData: func(db *gorm.DB) {
db.Create(&models.Plan{Model: gorm.Model{ID: 70}, Name: "Mixed Main Plan", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 71}, Name: "Child with SubPlans", ContentType: models.PlanContentTypeSubPlans})
db.Create(&models.Plan{Model: gorm.Model{ID: 72}, Name: "Grandchild with Tasks", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Task{PlanID: 72, Name: "Grandchild's Task", ExecutionOrder: 1})
db.Create(&models.Plan{Model: gorm.Model{ID: 73}, Name: "Child with Tasks", ContentType: models.PlanContentTypeTasks})
db.Create(&models.Task{PlanID: 73, Name: "Child's Task", ExecutionOrder: 1})
// 创建关联
db.Create(&models.SubPlan{ParentPlanID: 70, ChildPlanID: 71, ExecutionOrder: 1}) // Main -> Child with SubPlans
db.Create(&models.SubPlan{ParentPlanID: 70, ChildPlanID: 73, ExecutionOrder: 2}) // Main -> Child with Tasks
db.Create(&models.SubPlan{ParentPlanID: 71, ChildPlanID: 72, ExecutionOrder: 1}) // Child with SubPlans -> Grandchild
},
planID: 70,
expectedPlan: &models.Plan{
Model: gorm.Model{ID: 70},
Name: "Mixed Main Plan",
ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{
ParentPlanID: 70, ChildPlanID: 71, ExecutionOrder: 1,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 71}, Name: "Child with SubPlans", ContentType: models.PlanContentTypeSubPlans,
SubPlans: []models.SubPlan{
{ParentPlanID: 71, ChildPlanID: 72, ExecutionOrder: 1, ChildPlan: &models.Plan{
Model: gorm.Model{ID: 72}, Name: "Grandchild with Tasks", ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{{PlanID: 72, Name: "Grandchild's Task", ExecutionOrder: 1}},
}},
},
},
},
{
ParentPlanID: 70, ChildPlanID: 73, ExecutionOrder: 2,
ChildPlan: &models.Plan{
Model: gorm.Model{ID: 73}, Name: "Child with Tasks", ContentType: models.PlanContentTypeTasks,
Tasks: []models.Task{{PlanID: 73, Name: "Child's Task", ExecutionOrder: 1}},
},
},
},
},
expectedError: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := setupTestDB(t)
// 使用 defer 确保数据库连接在测试结束后关闭
sqlDB, _ := db.DB()
defer sqlDB.Close()
tc.setupData(db)
repo := repository.NewGormPlanRepository(db)
plan, err := repo.GetPlanByID(tc.planID)
if tc.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedError)
assert.Nil(t, plan)
} else {
assert.NoError(t, err)
assert.NotNil(t, plan)
// 在比较之前,清理实际结果和期望结果中所有不确定的、由数据库自动生成的字段
cleanPlanForComparison(plan)
cleanPlanForComparison(tc.expectedPlan)
assert.Equal(t, tc.expectedPlan, plan)
}
})
}
}
// cleanPlanForComparison 递归地重置 Plan 对象及其关联对象中由数据库自动生成的字段如ID和时间戳
// 以便在测试中断言它们与期望值相等。
func cleanPlanForComparison(p *models.Plan) {
if p == nil {
return
}
// 重置 Plan 自身的时间戳
p.CreatedAt = time.Time{}
p.UpdatedAt = time.Time{}
p.DeletedAt = gorm.DeletedAt{}
// 重置所有 Task 的自动生成字段
for i := range p.Tasks {
p.Tasks[i].ID = 0 // ID是自动生成的必须重置为0才能与期望值匹配
p.Tasks[i].CreatedAt = time.Time{}
p.Tasks[i].UpdatedAt = time.Time{}
p.Tasks[i].DeletedAt = gorm.DeletedAt{}
}
// 重置所有 SubPlan 的自动生成字段,并递归清理子计划
for i := range p.SubPlans {
p.SubPlans[i].ID = 0 // SubPlan 连接记录的ID也是自动生成的
p.SubPlans[i].CreatedAt = time.Time{}
p.SubPlans[i].UpdatedAt = time.Time{}
p.SubPlans[i].DeletedAt = gorm.DeletedAt{}
// 递归调用以清理嵌套的子计划
cleanPlanForComparison(p.SubPlans[i].ChildPlan)
}
}