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. func newTestRouter(t *testing.T) http.Handler { 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{} router := r.(chi.Router) _ = chi.Walk(router, 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) } }