T2.3: Split domain.Job (durable) from domain.JobRuntime (transient)
Move all transient execution state off domain.Job into a new domain.JobRuntime, keyed by job ID: - domain: Job now holds only durable YAML fields; remove the yaml:"-" fields (LastRun/NextRun/LastState/Logs/Output) and NextDue. Add runtime.go with JobRuntime plus NewRuntime/NewRuntimes constructors, which now own the runtime-init logic moved out of normalizeJobs. - runner: RunJob no longer mutates the job; it is pure and returns the RunRecord for the caller to fold into the runtime. - scheduler: take a shared map[int]*JobRuntime and route status/next-run bookkeeping through runtimeFor(job); prepareNextRun writes a *JobRuntime. - storage: normalizeJobs touches only durable config. - gui: own the runtime map (NewRuntimes), share it with the scheduler, and read/write runtime state via runtimeFor; maintain the map by ID on add/edit/delete. - tests: update scheduler/storage tests to the split; tidy a pre-existing import-order nit in scheduler.go. This also satisfies T2.4 (storage load/save only Job, runtime init in domain.NewRuntime, round-trip tests), since removing the fields forced it. Runtime-map ownership remains GUI-side glue until T3.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,35 +10,37 @@ import (
|
||||
|
||||
func TestPrepareNextRunSetsDisplayString(t *testing.T) {
|
||||
jobs := []domain.Job{{Schedule: "*/5 * * * *", Enabled: true}}
|
||||
s := &Scheduler{jobs: &jobs, schedules: make(map[int]domain.Schedule)}
|
||||
s := &Scheduler{jobs: &jobs, runtimes: domain.NewRuntimes(jobs), schedules: make(map[int]domain.Schedule)}
|
||||
s.parseJobSchedule(&jobs[0])
|
||||
runtime := s.runtimeFor(&jobs[0])
|
||||
from := time.Date(2026, 6, 14, 12, 3, 0, 0, time.UTC)
|
||||
|
||||
s.prepareNextRun(&jobs[0], from)
|
||||
s.prepareNextRun(&jobs[0], runtime, from)
|
||||
|
||||
want := "2026-06-14 12:05:00"
|
||||
if jobs[0].NextRun != want {
|
||||
t.Errorf("NextRun: got %q, want %q", jobs[0].NextRun, want)
|
||||
if runtime.NextRun != want {
|
||||
t.Errorf("NextRun: got %q, want %q", runtime.NextRun, want)
|
||||
}
|
||||
wantDue := time.Date(2026, 6, 14, 12, 5, 0, 0, time.UTC)
|
||||
if !jobs[0].NextDue.Equal(wantDue) {
|
||||
t.Errorf("NextDue: got %v, want %v", jobs[0].NextDue, wantDue)
|
||||
if !runtime.NextDue.Equal(wantDue) {
|
||||
t.Errorf("NextDue: got %v, want %v", runtime.NextDue, wantDue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareNextRunSetsInvalidScheduleLabel(t *testing.T) {
|
||||
jobs := []domain.Job{{Schedule: "not-a-cron", Enabled: true}}
|
||||
s := &Scheduler{jobs: &jobs, schedules: make(map[int]domain.Schedule)}
|
||||
s := &Scheduler{jobs: &jobs, runtimes: domain.NewRuntimes(jobs), schedules: make(map[int]domain.Schedule)}
|
||||
// parseJobSchedule will drop the invalid spec, so schedules map stays empty.
|
||||
s.parseJobSchedule(&jobs[0])
|
||||
runtime := s.runtimeFor(&jobs[0])
|
||||
|
||||
s.prepareNextRun(&jobs[0], time.Now())
|
||||
s.prepareNextRun(&jobs[0], runtime, time.Now())
|
||||
|
||||
if jobs[0].NextRun != "Invalid schedule" {
|
||||
t.Errorf("NextRun: got %q, want 'Invalid schedule'", jobs[0].NextRun)
|
||||
if runtime.NextRun != "Invalid schedule" {
|
||||
t.Errorf("NextRun: got %q, want 'Invalid schedule'", runtime.NextRun)
|
||||
}
|
||||
if !jobs[0].NextDue.IsZero() {
|
||||
t.Errorf("NextDue should be zero for invalid schedule, got %v", jobs[0].NextDue)
|
||||
if !runtime.NextDue.IsZero() {
|
||||
t.Errorf("NextDue should be zero for invalid schedule, got %v", runtime.NextDue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user