diff --git a/internal/web/web_test.go b/internal/web/web_test.go index af31c5c..a39add3 100644 --- a/internal/web/web_test.go +++ b/internal/web/web_test.go @@ -170,6 +170,39 @@ func TestRefocusAndOnTaskOutsideActiveIs400(t *testing.T) { } } +type stubNudger struct{ msg string } + +func (s stubNudger) Nudge(ctx context.Context, commitment string, recentTitles []string) (string, error) { + return s.msg, nil +} + +func TestActiveStatePayloadCarriesNudge(t *testing.T) { + s := newTestServer(t) + s.ctrl.SetNudge(stubNudger{msg: "drifted to unrelated work"}) + r := s.Router() + _ = post(t, r, "/planning", "") + body := `{"next_action":"Build web","success_condition":"web tests pass","timebox_secs":1500,"allowed_window_classes":["code"]}` + if w := post(t, r, "/commitment", body); w.Code != http.StatusOK { + t.Fatalf("/commitment code %d body %s", w.Code, w.Body.String()) + } + // Two distinct on-task titles trip the nudge (history >= 2; first call has no + // debounce to wait on). + s.ctrl.RecordWindow(evidence.WindowSnapshot{Class: "code", Title: "a.go", Health: evidence.EvidenceHealth{Available: true}}) + s.ctrl.RecordWindow(evidence.WindowSnapshot{Class: "code", Title: "b.go", Health: evidence.EvidenceHealth{Available: true}}) + + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if d := s.ctrl.State().Drift; d != nil && d.Nudge != "" { + break + } + time.Sleep(5 * time.Millisecond) + } + js := s.stateJSON() + if !strings.Contains(js, `"nudge":"drifted to unrelated work"`) { + t.Fatalf("payload missing nudge: %s", js) + } +} + func TestCommitmentCarriesAllowedClassesAndRoutesWork(t *testing.T) { s := newTestServer(t) r := s.Router()