scheduled_test_service.go 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package service
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/robfig/cron/v3"
  7. )
  8. var scheduledTestCronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
  9. // ScheduledTestService provides CRUD operations for scheduled test plans and results.
  10. type ScheduledTestService struct {
  11. planRepo ScheduledTestPlanRepository
  12. resultRepo ScheduledTestResultRepository
  13. }
  14. // NewScheduledTestService creates a new ScheduledTestService.
  15. func NewScheduledTestService(
  16. planRepo ScheduledTestPlanRepository,
  17. resultRepo ScheduledTestResultRepository,
  18. ) *ScheduledTestService {
  19. return &ScheduledTestService{
  20. planRepo: planRepo,
  21. resultRepo: resultRepo,
  22. }
  23. }
  24. // CreatePlan validates the cron expression, computes next_run_at, and persists the plan.
  25. func (s *ScheduledTestService) CreatePlan(ctx context.Context, plan *ScheduledTestPlan) (*ScheduledTestPlan, error) {
  26. nextRun, err := computeNextRun(plan.CronExpression, time.Now())
  27. if err != nil {
  28. return nil, fmt.Errorf("invalid cron expression: %w", err)
  29. }
  30. plan.NextRunAt = &nextRun
  31. if plan.MaxResults <= 0 {
  32. plan.MaxResults = 50
  33. }
  34. return s.planRepo.Create(ctx, plan)
  35. }
  36. // GetPlan retrieves a plan by ID.
  37. func (s *ScheduledTestService) GetPlan(ctx context.Context, id int64) (*ScheduledTestPlan, error) {
  38. return s.planRepo.GetByID(ctx, id)
  39. }
  40. // ListPlansByAccount returns all plans for a given account.
  41. func (s *ScheduledTestService) ListPlansByAccount(ctx context.Context, accountID int64) ([]*ScheduledTestPlan, error) {
  42. return s.planRepo.ListByAccountID(ctx, accountID)
  43. }
  44. // UpdatePlan validates cron and updates the plan.
  45. func (s *ScheduledTestService) UpdatePlan(ctx context.Context, plan *ScheduledTestPlan) (*ScheduledTestPlan, error) {
  46. nextRun, err := computeNextRun(plan.CronExpression, time.Now())
  47. if err != nil {
  48. return nil, fmt.Errorf("invalid cron expression: %w", err)
  49. }
  50. plan.NextRunAt = &nextRun
  51. return s.planRepo.Update(ctx, plan)
  52. }
  53. // DeletePlan removes a plan and its results (via CASCADE).
  54. func (s *ScheduledTestService) DeletePlan(ctx context.Context, id int64) error {
  55. return s.planRepo.Delete(ctx, id)
  56. }
  57. // ListResults returns the most recent results for a plan.
  58. func (s *ScheduledTestService) ListResults(ctx context.Context, planID int64, limit int) ([]*ScheduledTestResult, error) {
  59. if limit <= 0 {
  60. limit = 50
  61. }
  62. return s.resultRepo.ListByPlanID(ctx, planID, limit)
  63. }
  64. // SaveResult inserts a result and prunes old entries beyond maxResults.
  65. func (s *ScheduledTestService) SaveResult(ctx context.Context, planID int64, maxResults int, result *ScheduledTestResult) error {
  66. result.PlanID = planID
  67. if _, err := s.resultRepo.Create(ctx, result); err != nil {
  68. return err
  69. }
  70. return s.resultRepo.PruneOldResults(ctx, planID, maxResults)
  71. }
  72. func computeNextRun(cronExpr string, from time.Time) (time.Time, error) {
  73. sched, err := scheduledTestCronParser.Parse(cronExpr)
  74. if err != nil {
  75. return time.Time{}, err
  76. }
  77. return sched.Next(from), nil
  78. }