- 移除 ScheduledTestOutcome 中间类型,RunTestBackground 直接返回 *ScheduledTestResult - 简化 SaveResult 直接接受 *ScheduledTestResult - 移除 handler 中不必要的 nil 检查 - 移除前端 ScheduledTestsPanel 中多余的 String() 转换 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
2.9 KiB
Go
95 lines
2.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
)
|
|
|
|
var scheduledTestCronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
|
|
|
// ScheduledTestService provides CRUD operations for scheduled test plans and results.
|
|
type ScheduledTestService struct {
|
|
planRepo ScheduledTestPlanRepository
|
|
resultRepo ScheduledTestResultRepository
|
|
}
|
|
|
|
// NewScheduledTestService creates a new ScheduledTestService.
|
|
func NewScheduledTestService(
|
|
planRepo ScheduledTestPlanRepository,
|
|
resultRepo ScheduledTestResultRepository,
|
|
) *ScheduledTestService {
|
|
return &ScheduledTestService{
|
|
planRepo: planRepo,
|
|
resultRepo: resultRepo,
|
|
}
|
|
}
|
|
|
|
// CreatePlan validates the cron expression, computes next_run_at, and persists the plan.
|
|
func (s *ScheduledTestService) CreatePlan(ctx context.Context, plan *ScheduledTestPlan) (*ScheduledTestPlan, error) {
|
|
nextRun, err := computeNextRun(plan.CronExpression, time.Now())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid cron expression: %w", err)
|
|
}
|
|
plan.NextRunAt = &nextRun
|
|
|
|
if plan.MaxResults <= 0 {
|
|
plan.MaxResults = 50
|
|
}
|
|
|
|
return s.planRepo.Create(ctx, plan)
|
|
}
|
|
|
|
// GetPlan retrieves a plan by ID.
|
|
func (s *ScheduledTestService) GetPlan(ctx context.Context, id int64) (*ScheduledTestPlan, error) {
|
|
return s.planRepo.GetByID(ctx, id)
|
|
}
|
|
|
|
// ListPlansByAccount returns all plans for a given account.
|
|
func (s *ScheduledTestService) ListPlansByAccount(ctx context.Context, accountID int64) ([]*ScheduledTestPlan, error) {
|
|
return s.planRepo.ListByAccountID(ctx, accountID)
|
|
}
|
|
|
|
// UpdatePlan validates cron and updates the plan.
|
|
func (s *ScheduledTestService) UpdatePlan(ctx context.Context, plan *ScheduledTestPlan) (*ScheduledTestPlan, error) {
|
|
nextRun, err := computeNextRun(plan.CronExpression, time.Now())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid cron expression: %w", err)
|
|
}
|
|
plan.NextRunAt = &nextRun
|
|
|
|
return s.planRepo.Update(ctx, plan)
|
|
}
|
|
|
|
// DeletePlan removes a plan and its results (via CASCADE).
|
|
func (s *ScheduledTestService) DeletePlan(ctx context.Context, id int64) error {
|
|
return s.planRepo.Delete(ctx, id)
|
|
}
|
|
|
|
// ListResults returns the most recent results for a plan.
|
|
func (s *ScheduledTestService) ListResults(ctx context.Context, planID int64, limit int) ([]*ScheduledTestResult, error) {
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
return s.resultRepo.ListByPlanID(ctx, planID, limit)
|
|
}
|
|
|
|
// SaveResult inserts a result and prunes old entries beyond maxResults.
|
|
func (s *ScheduledTestService) SaveResult(ctx context.Context, planID int64, maxResults int, result *ScheduledTestResult) error {
|
|
result.PlanID = planID
|
|
if _, err := s.resultRepo.Create(ctx, result); err != nil {
|
|
return err
|
|
}
|
|
return s.resultRepo.PruneOldResults(ctx, planID, maxResults)
|
|
}
|
|
|
|
func computeNextRun(cronExpr string, from time.Time) (time.Time, error) {
|
|
sched, err := scheduledTestCronParser.Parse(cronExpr)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
return sched.Next(from), nil
|
|
}
|