package session import ( "path/filepath" "testing" "time" "antidrift/internal/domain" ) func newTestController(t *testing.T) (*Controller, string) { t.Helper() path := filepath.Join(t.TempDir(), "state.json") c, err := New(path) if err != nil { t.Fatalf("new controller: %v", err) } return c, path } func TestHappyPathDrivesStates(t *testing.T) { c, _ := newTestController(t) if c.State().RuntimeState != domain.RuntimeLocked { t.Fatalf("should start Locked") } if err := c.EnterPlanning(); err != nil { t.Fatalf("enter planning: %v", err) } if err := c.StartManualCommitment("Port session", "session tests pass", 25*time.Minute); err != nil { t.Fatalf("start commitment: %v", err) } st := c.State() if st.RuntimeState != domain.RuntimeActive { t.Fatalf("should be Active, got %s", st.RuntimeState) } if st.Commitment == nil || st.Commitment.NextAction != "Port session" { t.Fatalf("active commitment missing: %+v", st.Commitment) } if st.Commitment.DeadlineUnixSecs <= time.Now().Unix() { t.Fatalf("deadline should be in the future") } if err := c.Complete(); err != nil { t.Fatalf("complete: %v", err) } if c.State().RuntimeState != domain.RuntimeReview { t.Fatalf("should be Review after complete") } if err := c.End(); err != nil { t.Fatalf("end: %v", err) } if c.State().RuntimeState != domain.RuntimeLocked { t.Fatalf("should be Locked after end") } } func TestStartCommitmentRejectsInvalidInput(t *testing.T) { c, _ := newTestController(t) _ = c.EnterPlanning() if err := c.StartManualCommitment("", "x", 25*time.Minute); err == nil { t.Fatalf("empty next action should error") } if c.State().RuntimeState != domain.RuntimePlanning { t.Fatalf("failed start must not change state, got %s", c.State().RuntimeState) } } func TestStateRestoresFromSnapshot(t *testing.T) { path := filepath.Join(t.TempDir(), "state.json") first, err := New(path) if err != nil { t.Fatalf("new: %v", err) } _ = first.EnterPlanning() _ = first.StartManualCommitment("Persisted action", "done", 25*time.Minute) second, err := New(path) if err != nil { t.Fatalf("reopen: %v", err) } st := second.State() if st.RuntimeState != domain.RuntimeActive { t.Fatalf("restored state should be Active, got %s", st.RuntimeState) } if st.Commitment == nil || st.Commitment.NextAction != "Persisted action" { t.Fatalf("restored commitment missing: %+v", st.Commitment) } } func TestRestoredCommitmentKeepsDeadline(t *testing.T) { path := filepath.Join(t.TempDir(), "state.json") first, err := New(path) if err != nil { t.Fatalf("new: %v", err) } _ = first.EnterPlanning() _ = first.StartManualCommitment("Keep deadline", "done", 25*time.Minute) want := first.State().Commitment.DeadlineUnixSecs second, err := New(path) if err != nil { t.Fatalf("reopen: %v", err) } got := second.State().Commitment if got == nil || got.DeadlineUnixSecs != want { t.Fatalf("restored deadline: got %+v want %d", got, want) } }