diff --git a/internal/infra/repository/main_test.go b/internal/infra/repository/main_test.go index 1ec9752..700e438 100644 --- a/internal/infra/repository/main_test.go +++ b/internal/infra/repository/main_test.go @@ -14,11 +14,11 @@ import ( func setupTestDB(t *testing.T) *gorm.DB { // "file::memory:?cache=shared" 是 GORM 连接内存 SQLite 的标准方式,确保在同一测试中的不同连接可以访问相同的数据。 db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{}) - assert.NoError(t, err, "连接内存数据库不应出错") + assert.NoError(t, err, "连接内存数据库时发生错误") // 自动迁移所有需要的表结构 - err = db.AutoMigrate(&models.User{}, &models.Device{}) - assert.NoError(t, err, "数据库迁移不应出错") + err = db.AutoMigrate(&models.User{}, &models.Device{}, &models.SubPlan{}, &models.Task{}, &models.Plan{}) + assert.NoError(t, err, "数据库迁移时发生错误") return db } diff --git a/internal/infra/repository/plan_repository.go b/internal/infra/repository/plan_repository.go new file mode 100644 index 0000000..ed8cf07 --- /dev/null +++ b/internal/infra/repository/plan_repository.go @@ -0,0 +1,49 @@ +package repository + +import ( + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" +) + +// PlanRepository 定义了与计划模型相关的数据库操作接口 +// 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现 +type PlanRepository interface { + // ListBasicPlans 获取所有计划的基本信息,不包含子计划和任务详情 + ListBasicPlans() ([]models.Plan, error) + // GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情 + GetBasicPlanByID(id uint) (*models.Plan, error) +} + +// gormPlanRepository 是 PlanRepository 的 GORM 实现 +type gormPlanRepository struct { + db *gorm.DB +} + +// NewGormPlanRepository 创建一个新的 PlanRepository GORM 实现实例 +func NewGormPlanRepository(db *gorm.DB) PlanRepository { + return &gormPlanRepository{ + db: db, + } +} + +// ListBasicPlans 获取所有计划的基本信息,不包含子计划和任务详情 +func (r *gormPlanRepository) ListBasicPlans() ([]models.Plan, error) { + var plans []models.Plan + // GORM 默认不会加载关联,除非使用 Preload,所以直接 Find 即可满足要求 + result := r.db.Find(&plans) + if result.Error != nil { + return nil, result.Error + } + return plans, nil +} + +// GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情 +func (r *gormPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) { + var plan models.Plan + // GORM 默认不会加载关联,除非使用 Preload,所以直接 First 即可满足要求 + result := r.db.First(&plan, id) + if result.Error != nil { + return nil, result.Error + } + return &plan, nil +} diff --git a/internal/infra/repository/plan_repository_test.go b/internal/infra/repository/plan_repository_test.go new file mode 100644 index 0000000..7752ff1 --- /dev/null +++ b/internal/infra/repository/plan_repository_test.go @@ -0,0 +1,136 @@ +package repository_test + +import ( + "errors" + "testing" + + "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 不应加载任务") + } + }) + } +}