package gitea_test import ( "context" "net/http" "net/http/httptest" "sync/atomic" "testing" "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // #36 second line of defence: when owner or repo is empty the path contains an // empty segment ("//"). Rather than forward it upstream — where gitea answers // with its opaque /api/swagger 404 — the client must reject it locally with a // validation error and never touch the network. func TestEmptyPathSegmentRejectedBeforeNetwork(t *testing.T) { var hits int32 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt32(&hits, 1) w.WriteHeader(http.StatusOK) })) defer srv.Close() c := gitea.NewClient(srv.URL, "tok") paths := []string{ "/api/v1/repos/mathias//issues", // empty repo "/api/v1/repos//gitea-mcp/contents/", // empty owner "/api/v1/repos/mathias//issues?state=open", // empty repo before query } for _, p := range paths { _, _, err := c.GetJSON(context.Background(), p) require.Error(t, err, "path %q should be rejected", p) assert.ErrorIs(t, err, gitea.ErrValidation) } assert.Equal(t, int32(0), atomic.LoadInt32(&hits), "guard must short-circuit before any HTTP call") } // A well-formed path with a query string must still pass the guard. func TestWellFormedPathPasses(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`[]`)) })) defer srv.Close() c := gitea.NewClient(srv.URL, "tok") _, status, err := c.GetJSON(context.Background(), "/api/v1/repos/mathias/gitea-mcp/issues?state=open") require.NoError(t, err) assert.Equal(t, 200, status) }