package main import ( "bytes" "net/http" "net/http/httptest" "strings" "testing" "github.com/go-chi/chi/v5" "git.agentview.dev/profit/golangLAKEHOUSE/internal/catalogd" "git.agentview.dev/profit/golangLAKEHOUSE/internal/storeclient" ) // Closes R-005 for catalogd: cmd-level tests for route mounting, // body-cap rejection, malformed JSON handling, and the decode-error // paths in handleRegister. Deeper Registry semantics live in // internal/catalogd/registry_test.go. func newTestHandlers(t *testing.T) (*handlers, *httptest.Server) { t.Helper() // Stub storaged so the registry can hydrate (it needs nothing // initially). Empty server = 404 on any GET; that's fine for // these tests because we don't exercise storaged paths here. stub := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) })) t.Cleanup(stub.Close) store := storeclient.New(stub.URL) reg := catalogd.NewRegistry(store) return newHandlers(reg), stub } func mountedRouter(h *handlers) chi.Router { r := chi.NewRouter() h.register(r) return r } func TestRoutesMounted(t *testing.T) { h, _ := newTestHandlers(t) r := mountedRouter(h) want := map[string]string{ "POST /catalog/register": "register endpoint", "GET /catalog/manifest/*": "manifest endpoint", "GET /catalog/list": "list endpoint", } 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 sig := range want { if !got[sig] { t.Errorf("expected route %q mounted; got %v", sig, got) } } } func TestHandleRegister_BodyTooLarge(t *testing.T) { h, _ := newTestHandlers(t) r := mountedRouter(h) srv := httptest.NewServer(r) defer srv.Close() // 5 MiB body — over the 4 MiB cap. big := bytes.Repeat([]byte("x"), 5<<20) resp, err := http.Post(srv.URL+"/catalog/register", "application/json", bytes.NewReader(big)) if err != nil { t.Fatalf("POST: %v", err) } defer resp.Body.Close() // MaxBytesReader trips during JSON decode → 400 with "body too large" // in the message, OR 413 if Content-Length up-front cap is added. // Today the path returns 400 via decode error; lock that contract. if resp.StatusCode < 400 || resp.StatusCode >= 500 { t.Errorf("expected 4xx on oversize body, got %d", resp.StatusCode) } } func TestHandleRegister_MalformedJSON(t *testing.T) { h, _ := newTestHandlers(t) r := mountedRouter(h) srv := httptest.NewServer(r) defer srv.Close() resp, err := http.Post(srv.URL+"/catalog/register", "application/json", strings.NewReader("not json")) if err != nil { t.Fatalf("POST: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("expected 400 on malformed JSON, got %d", resp.StatusCode) } } func TestHandleRegister_EmptyName_400(t *testing.T) { h, _ := newTestHandlers(t) r := mountedRouter(h) srv := httptest.NewServer(r) defer srv.Close() body := `{"name":"","schema_fingerprint":"sha256:x","objects":[{"key":"k","size":1}]}` resp, err := http.Post(srv.URL+"/catalog/register", "application/json", strings.NewReader(body)) if err != nil { t.Fatalf("POST: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { t.Errorf("expected 400 on empty name, got %d", resp.StatusCode) } } func TestHandleGetManifest_404(t *testing.T) { h, _ := newTestHandlers(t) r := mountedRouter(h) srv := httptest.NewServer(r) defer srv.Close() resp, err := http.Get(srv.URL + "/catalog/manifest/nonexistent") if err != nil { t.Fatalf("GET: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("expected 404 for missing manifest, got %d", resp.StatusCode) } } func TestHandleList_EmptyShape(t *testing.T) { h, _ := newTestHandlers(t) r := mountedRouter(h) srv := httptest.NewServer(r) defer srv.Close() resp, err := http.Get(srv.URL + "/catalog/list") if err != nil { t.Fatalf("GET: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", resp.StatusCode) } if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { t.Errorf("Content-Type = %q, want application/json", ct) } }