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:
+13
-37
@@ -68,16 +68,8 @@ func TestJobsRoundTrip(t *testing.T) {
|
||||
t.Errorf("Enabled: got %v, want %v", g.Enabled, w.Enabled)
|
||||
}
|
||||
|
||||
// Runtime fields must not survive the save→load round-trip.
|
||||
if g.LastRun != "" {
|
||||
t.Errorf("LastRun should be empty after load, got %q", g.LastRun)
|
||||
}
|
||||
if g.LastState != "" {
|
||||
t.Errorf("LastState should be empty after load, got %q", g.LastState)
|
||||
}
|
||||
if g.Logs != nil {
|
||||
t.Errorf("Logs should be nil after load, got %v", g.Logs)
|
||||
}
|
||||
// Runtime state no longer lives on Job at all (it moved to domain.JobRuntime),
|
||||
// so there is nothing transient that could survive the save→load round-trip.
|
||||
}
|
||||
|
||||
func TestConfigRoundTrip(t *testing.T) {
|
||||
@@ -137,7 +129,9 @@ func TestNormalizeJobsFillsDefaults(t *testing.T) {
|
||||
|
||||
normalizeJobs(jobs)
|
||||
|
||||
// Blank enabled job gets default name, schedule, command, exit codes, and runtime state.
|
||||
// Blank enabled job gets default name, schedule, command, and exit codes.
|
||||
// normalizeJobs only fills durable configuration now; runtime status is built
|
||||
// separately by domain.NewRuntime.
|
||||
if jobs[0].ID != 1 {
|
||||
t.Errorf("first auto ID: got %d, want 1", jobs[0].ID)
|
||||
}
|
||||
@@ -150,20 +144,6 @@ func TestNormalizeJobsFillsDefaults(t *testing.T) {
|
||||
if jobs[0].SuccessExitCodes != "0" {
|
||||
t.Errorf("default exit codes: got %q, want '0'", jobs[0].SuccessExitCodes)
|
||||
}
|
||||
if jobs[0].LastState != "Ready" {
|
||||
t.Errorf("enabled job state: got %q, want 'Ready'", jobs[0].LastState)
|
||||
}
|
||||
if jobs[0].NextRun != "After start" {
|
||||
t.Errorf("enabled job next run: got %q, want 'After start'", jobs[0].NextRun)
|
||||
}
|
||||
|
||||
// Disabled job is marked Paused.
|
||||
if jobs[1].LastState != "Paused" {
|
||||
t.Errorf("disabled job state: got %q, want 'Paused'", jobs[1].LastState)
|
||||
}
|
||||
if jobs[1].NextRun != "Paused" {
|
||||
t.Errorf("disabled job next run: got %q, want 'Paused'", jobs[1].NextRun)
|
||||
}
|
||||
|
||||
// Pre-set fields survive normalization unchanged.
|
||||
if jobs[2].ID != 5 {
|
||||
@@ -175,20 +155,16 @@ func TestNormalizeJobsFillsDefaults(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJobsYAMLDoesNotPersistRuntimeNoise(t *testing.T) {
|
||||
// Job carries only durable configuration; runtime state lives in
|
||||
// domain.JobRuntime and is never marshalled. This guards against a future
|
||||
// runtime field accidentally being added back onto Job with a yaml tag.
|
||||
jobs := []domain.Job{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "Clean job",
|
||||
Schedule: "@every 10s",
|
||||
Command: echoCommand("ok"),
|
||||
Enabled: true,
|
||||
LastRun: "2026-06-14 12:00:00",
|
||||
NextRun: "2026-06-14 12:00:10",
|
||||
LastState: "OK",
|
||||
Output: "stdout: ok",
|
||||
Logs: []domain.RunRecord{
|
||||
{Time: "2026-06-14 12:00:00", JobName: "Clean job", Output: "stdout: ok"},
|
||||
},
|
||||
ID: 1,
|
||||
Name: "Clean job",
|
||||
Schedule: "@every 10s",
|
||||
Command: echoCommand("ok"),
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user