root 6c02c905c8 scrum lift_001: 4 fixes from cross-lineage review
Cross-lineage scrum on b2e45f7 produced 1 convergent + 3 single-reviewer
findings worth fixing. All apply.

1. (Opus WARN + Qwen INFO convergent) scripts/playbook_lift.sh: replace
   sleep 2.5 in SQL probe with active polling up to 5s. refresh_every=1s
   is a lower bound; under load the manifest may not be visible in a
   fixed sleep, which would 4xx the probe and abort the reality run.

2. (Opus INFO) scripts/playbook_lift.sh: report template glued
   "env JUDGE_MODEL" + value as "env JUDGE_MODELqwen2.5:latest" with no
   separator. Replaced two :+/:- substitution chains with a single
   JUDGE_SOURCE variable computed once at the top of the harness.

3. (Opus INFO) scripts/staffing_workers/main.go: -id-prefix "" silently
   allowed, defeating the flag's purpose (cross-corpus collision prevent).
   Now log.Fatal at startup with explicit hint.

4. (Opus WARN) cmd/{pathwayd,observerd}/main_test.go: newTestRouter
   returned http.Handler then re-cast to chi.Router for chi.Walk.
   Returning chi.Router directly satisfies http.Handler AND avoids an
   assertion that would panic if future middleware wraps the router.

Dismissed (with rationale):
- Kimi INFO hardcoded MinIO endpoint: harness is local-by-design.
- Kimi WARN matrixd accepts 502/500: documented; real retriever needs
  real upstreams the test doesn't spin up.
- Qwen INFO queryd string.Contains: brittle but very low risk; restating
  through typed-error path would couple without adding signal.

go test ./cmd/{matrixd,queryd,pathwayd,observerd} all green.

Verdicts at reports/scrum/_evidence/2026-04-30/verdicts/lift_001_*.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 06:27:24 -05:00

108 lines
3.3 KiB
Go

package main
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"git.agentview.dev/profit/golangLAKEHOUSE/internal/pathway"
)
// newTestRouter builds the pathwayd router with an in-memory store
// (nil persistor). Closes R-005 for pathwayd: 9 routes mounted with
// no router-level test prior to this file.
//
// Returns chi.Router (not http.Handler) so chi.Walk works without a
// type assertion that would panic if a future refactor wraps the
// router in plain net/http middleware.
func newTestRouter(t *testing.T) chi.Router {
t.Helper()
h := &handlers{store: pathway.NewStore(nil)}
r := chi.NewRouter()
h.register(r)
return r
}
func TestRoutesMounted(t *testing.T) {
r := newTestRouter(t)
want := map[string]string{
"POST /pathway/add": "",
"POST /pathway/add_idempotent": "",
"POST /pathway/update": "",
"POST /pathway/revise": "",
"POST /pathway/retire": "",
"GET /pathway/get/{uid}": "",
"GET /pathway/history/{uid}": "",
"POST /pathway/search": "",
"GET /pathway/stats": "",
}
got := map[string]bool{}
_ = chi.Walk(r, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
got[method+" "+route] = true
return nil
})
for k := range want {
if !got[k] {
t.Errorf("route not mounted: %s", k)
}
}
}
// TestAdd_RoundTrip locks the happy-path contract: POST a content blob,
// receive a 201 with a trace, GET it back at /pathway/get/{uid}.
// Catches drift in either the add response shape or the get path.
func TestAdd_RoundTrip(t *testing.T) {
r := newTestRouter(t)
body := []byte(`{"content":{"hello":"world"},"tags":["test"]}`)
req := httptest.NewRequest("POST", "/pathway/add", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("expected 201 on add, got %d (body=%s)", w.Code, w.Body.String())
}
}
func TestStats_GET(t *testing.T) {
r := newTestRouter(t)
req := httptest.NewRequest("GET", "/pathway/stats", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200 on stats, got %d", w.Code)
}
}
// TestAddIdempotent_MissingUID locks the validation: empty UID must
// 4xx rather than silently accepting (which would defeat the
// idempotency contract).
func TestAddIdempotent_MissingUID(t *testing.T) {
r := newTestRouter(t)
body := []byte(`{"content":{"x":1}}`)
req := httptest.NewRequest("POST", "/pathway/add_idempotent", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code/100 != 4 {
t.Errorf("missing uid should 4xx, got %d (body=%s)", w.Code, w.Body.String())
}
}
// TestRetire_NonexistentUID locks the not-found path. The store rejects
// retiring traces that don't exist; the handler must surface that as a
// 4xx, not a 5xx.
func TestRetire_NonexistentUID(t *testing.T) {
r := newTestRouter(t)
body := []byte(`{"uid":"does-not-exist"}`)
req := httptest.NewRequest("POST", "/pathway/retire", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code/100 != 4 {
t.Errorf("retire of nonexistent uid should 4xx, got %d", w.Code)
}
}