M2: POST /coach route wiring the planning coach
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"antidrift/internal/ai"
|
||||
"antidrift/internal/domain"
|
||||
"antidrift/internal/evidence"
|
||||
"antidrift/internal/session"
|
||||
@@ -98,3 +101,60 @@ func TestLockedStateHasNullEvidence(t *testing.T) {
|
||||
t.Fatalf("locked payload should have null evidence: %s", js)
|
||||
}
|
||||
}
|
||||
|
||||
type stubCoach struct {
|
||||
prop ai.Proposal
|
||||
err error
|
||||
}
|
||||
|
||||
func (s stubCoach) Coach(ctx context.Context, intent string) (ai.Proposal, error) {
|
||||
return s.prop, s.err
|
||||
}
|
||||
|
||||
func TestCoachRouteHappyPath(t *testing.T) {
|
||||
s := newTestServer(t)
|
||||
s.ctrl.SetCoach(stubCoach{prop: ai.Proposal{NextAction: "Do X", SuccessCondition: "done", TimeboxSecs: 1500}})
|
||||
r := s.Router()
|
||||
_ = post(t, r, "/planning", "")
|
||||
if w := post(t, r, "/coach", `{"intent":"do something"}`); w.Code != http.StatusOK {
|
||||
t.Fatalf("/coach code %d body %s", w.Code, w.Body.String())
|
||||
}
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if cv := s.ctrl.State().Coach; cv != nil && cv.Status == "ready" {
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("coach never reached ready: %+v", s.ctrl.State().Coach)
|
||||
}
|
||||
|
||||
func TestCoachRouteOutsidePlanning(t *testing.T) {
|
||||
s := newTestServer(t)
|
||||
s.ctrl.SetCoach(stubCoach{})
|
||||
r := s.Router()
|
||||
if w := post(t, r, "/coach", `{"intent":"x"}`); w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoachRouteInvalidJSON(t *testing.T) {
|
||||
s := newTestServer(t)
|
||||
r := s.Router()
|
||||
_ = post(t, r, "/planning", "")
|
||||
if w := post(t, r, "/coach", `not json`); w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoachRouteUnavailableDegrades(t *testing.T) {
|
||||
s := newTestServer(t) // no SetCoach
|
||||
r := s.Router()
|
||||
_ = post(t, r, "/planning", "")
|
||||
if w := post(t, r, "/coach", `{"intent":"x"}`); w.Code != http.StatusOK {
|
||||
t.Fatalf("unavailable coach should still 200, got %d", w.Code)
|
||||
}
|
||||
if cv := s.ctrl.State().Coach; cv == nil || cv.Status != "error" {
|
||||
t.Fatalf("want error status, got %+v", cv)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user