Files
pig-farm-controller/internal/infra/repository/plan_repository_test.go
2025-09-13 15:42:03 +08:00

401 lines
14 KiB
Go
Raw 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 repository_test
import (
"errors"
"fmt"
"testing"
"time"
"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 不应加载任务")
}
})
}
}
// 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)
}
}