Compare commits

...

1 Commits

Author SHA1 Message Date
J
47562a7092 refactor(autopilot): centralize timezone default and cover invalid-timezone fallback (MUL-2742)
Follow-up nits from PR #3324 review:

- Export DefaultAutopilotTriggerTimezone so the autopilot scheduler reuses
  the same source-of-truth as the service layer instead of hardcoding "UTC"
  in two places.
- Add tests that lock down the invalid-timezone fallback (e.g. "Foo/Bar")
  for both buildIssueDescription and interpolateTemplate, so a future change
  to the resolve/format helpers can't silently emit a half-formatted
  timestamp or date.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-27 15:34:19 +08:00
3 changed files with 45 additions and 9 deletions

View File

@@ -48,7 +48,7 @@ func recoverLostTriggers(ctx context.Context, queries *db.Queries) {
if !t.CronExpression.Valid || t.CronExpression.String == "" {
continue
}
tz := "UTC"
tz := service.DefaultAutopilotTriggerTimezone
if t.Timezone.Valid && t.Timezone.String != "" {
tz = t.Timezone.String
}
@@ -112,7 +112,7 @@ func advanceNextRun(ctx context.Context, queries *db.Queries, t db.ClaimDueSched
return
}
tz := "UTC"
tz := service.DefaultAutopilotTriggerTimezone
if t.Timezone.Valid && t.Timezone.String != "" {
tz = t.Timezone.String
}

View File

@@ -31,7 +31,11 @@ type AutopilotService struct {
TaskSvc *TaskService
}
const defaultAutopilotTriggerTimezone = "UTC"
// DefaultAutopilotTriggerTimezone is the timezone used to render Autopilot
// trigger output when a trigger has no configured timezone or the configured
// timezone fails to load. Exported so the scheduler can use the same default
// when computing next run times.
const DefaultAutopilotTriggerTimezone = "UTC"
func NewAutopilotService(q *db.Queries, tx TxStarter, bus *events.Bus, taskSvc *TaskService) *AutopilotService {
return &AutopilotService{Queries: q, TxStarter: tx, Bus: bus, TaskSvc: taskSvc}
@@ -843,7 +847,7 @@ func autopilotRunDurationMS(run db.AutopilotRun) int64 {
func (s *AutopilotService) resolveAutopilotTriggerTimezone(ctx context.Context, triggerID pgtype.UUID) string {
if !triggerID.Valid || s == nil || s.Queries == nil {
return defaultAutopilotTriggerTimezone
return DefaultAutopilotTriggerTimezone
}
trigger, err := s.Queries.GetAutopilotTrigger(ctx, triggerID)
@@ -852,12 +856,12 @@ func (s *AutopilotService) resolveAutopilotTriggerTimezone(ctx context.Context,
"trigger_id", util.UUIDToString(triggerID),
"error", err,
)
return defaultAutopilotTriggerTimezone
return DefaultAutopilotTriggerTimezone
}
timezone := strings.TrimSpace(trigger.Timezone.String)
if !trigger.Timezone.Valid || timezone == "" {
return defaultAutopilotTriggerTimezone
return DefaultAutopilotTriggerTimezone
}
if _, err := time.LoadLocation(timezone); err != nil {
slog.Warn("invalid autopilot trigger timezone; falling back to UTC",
@@ -865,7 +869,7 @@ func (s *AutopilotService) resolveAutopilotTriggerTimezone(ctx context.Context,
"timezone", timezone,
"error", err,
)
return defaultAutopilotTriggerTimezone
return DefaultAutopilotTriggerTimezone
}
return timezone
}
@@ -895,11 +899,11 @@ func autopilotRunTriggeredAt(run db.AutopilotRun) time.Time {
func autopilotTriggerLocation(timezone string) (*time.Location, string) {
label := strings.TrimSpace(timezone)
if label == "" {
label = defaultAutopilotTriggerTimezone
label = DefaultAutopilotTriggerTimezone
}
loc, err := time.LoadLocation(label)
if err != nil {
return time.UTC, defaultAutopilotTriggerTimezone
return time.UTC, DefaultAutopilotTriggerTimezone
}
return loc, label
}

View File

@@ -60,6 +60,38 @@ func TestBuildIssueDescription_UsesTriggerTimezone(t *testing.T) {
}
}
// An invalid IANA timezone string must fall back to UTC instead of leaving the
// timestamp half-formatted in the issue body.
func TestBuildIssueDescription_InvalidTriggerTimezoneFallsBackToUTC(t *testing.T) {
s := &AutopilotService{}
ap := db.Autopilot{Description: pgtype.Text{String: "do the thing", Valid: true}}
run := db.AutopilotRun{
Source: "schedule",
TriggeredAt: pgtype.Timestamptz{Time: time.Date(2026, 5, 26, 0, 0, 0, 0, time.UTC), Valid: true},
}
got := s.buildIssueDescription(ap, run, "Foo/Bar")
if !strings.Contains(got.String, "Autopilot run triggered at 2026-05-26 00:00 UTC") {
t.Fatalf("invalid trigger timezone should fall back to UTC: %q", got.String)
}
}
func TestInterpolateTemplate_InvalidTriggerTimezoneFallsBackToUTC(t *testing.T) {
s := &AutopilotService{}
ap := db.Autopilot{
Title: "fallback",
IssueTitleTemplate: pgtype.Text{String: "report {{date}}", Valid: true},
}
run := db.AutopilotRun{
TriggeredAt: pgtype.Timestamptz{Time: time.Date(2026, 5, 26, 23, 30, 0, 0, time.UTC), Valid: true},
}
got := s.interpolateTemplate(ap, run, "Foo/Bar")
if want := "report 2026-05-26"; got != want {
t.Fatalf("interpolateTemplate = %q, want %q", got, want)
}
}
func TestBuildIssueDescription_WithWebhookPayload(t *testing.T) {
s := &AutopilotService{}
ap := db.Autopilot{Description: pgtype.Text{String: "watch PRs", Valid: true}}