package main import ( "log/slog" "net/http" "net/http/httptest" "os" "strings" "testing" "github.com/stretchr/testify/assert" chassisauth "git.d-ma.be/mathias/mcp-chassis/auth" "github.com/mathiasbq/hyperguild/ingestion/internal/api" "github.com/mathiasbq/hyperguild/ingestion/internal/pipeline" ) const routesTok = "routes-test-token" // hyperguild#149: only /mcp was authenticated; every REST route on the same // mux was open, and infra#517 found them on the internet. The REST API is now // mounted at "/" behind the bearer middleware, so any route not registered // as explicitly public is authenticated, including routes added later. func testRouter(t *testing.T) http.Handler { t.Helper() h := api.NewHandler(t.TempDir(), slog.New(slog.NewTextHandler(os.Stderr, nil)), pipeline.Config{}) mux := http.NewServeMux() mux.HandleFunc("GET /metrics", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) }) mountREST(mux, h, func(next http.Handler) http.Handler { return chassisauth.BearerMiddleware(routesTok, nil, "brain", "", next) }) return mux } func do(t *testing.T, h http.Handler, method, path, authz string) int { t.Helper() req := httptest.NewRequest(method, path, strings.NewReader(`{"query":"x"}`)) if authz != "" { req.Header.Set("Authorization", authz) } rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec.Code } func TestRESTRoutesRefuseRequestsWithoutAToken(t *testing.T) { r := testRouter(t) for _, rt := range []struct{ method, path string }{ {"POST", "/query"}, {"POST", "/write"}, {"POST", "/index"}, {"POST", "/ingest"}, {"POST", "/ingest-path"}, {"POST", "/ingest-raw"}, {"POST", "/backfill-refs"}, {"GET", "/pending"}, {"POST", "/promote"}, {"POST", "/backfill-embeddings"}, {"GET", "/pass-rate"}, } { assert.Equal(t, http.StatusUnauthorized, do(t, r, rt.method, rt.path, ""), "%s %s without a token", rt.method, rt.path) assert.Equal(t, http.StatusUnauthorized, do(t, r, rt.method, rt.path, "Bearer wrong"), "%s %s with a wrong token", rt.method, rt.path) } } func TestRESTRoutesServeAValidToken(t *testing.T) { r := testRouter(t) assert.NotEqual(t, http.StatusUnauthorized, do(t, r, "POST", "/query", "Bearer "+routesTok)) assert.Equal(t, http.StatusOK, do(t, r, "GET", "/pending", "Bearer "+routesTok)) } // Default-deny: a path nobody registered is still behind auth, so a route // added to the REST mux later cannot ship open by omission. func TestUnregisteredPathIsAuthenticatedByDefault(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, do(t, testRouter(t), "POST", "/some-future-route", "")) } // Explicitly public routes registered on the outer mux keep working. func TestMetricsStaysOpenForPrometheus(t *testing.T) { assert.Equal(t, http.StatusOK, do(t, testRouter(t), "GET", "/metrics", "")) }