e2e

package
v0.41.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 43 Imported by: 0

README

End-to-End Tests

This directory contains end-to-end tests for ToolHive, including both CLI and HTTP API tests.

Overview

These tests validate ToolHive functionality by exercising the full application stack:

  • CLI Tests: Test command-line interface operations (run, list, stop, restart, etc.)
  • API Tests: Test HTTP API endpoints with a real API server instance
  • Integration Tests: Test interactions between different components

Structure

Test Files
  • *_test.go - Individual test files organized by feature
  • e2e_suite_test.go - Ginkgo test suite setup
  • api_helpers.go - Helper functions for starting API server and making HTTP requests
  • helpers.go - General helper functions for e2e tests
  • mcp_client_helpers.go - MCP client helper utilities
  • oidc_mock.go - Mock OIDC server for authentication tests
  • run_tests.sh - Test runner script

Operator-specific end-to-end tests live in subdirectories, such as the VirtualMCPServer E2E tests that run against a real Kubernetes cluster.

Test Categories

Tests are organized using Ginkgo labels for parallelization and filtering:

Core CLI Tests (Label: core)
  • Client management (client_test.go)
  • Group operations (group_*.go)
  • Server restart (restart_test.go)
  • Export functionality (export_test.go)
  • THVIgnore support (thvignore_test.go)
MCP Run Tests (Label: mcp-run)
  • MCP server operations (fetch_mcp_server_test.go, osv_mcp_server_test.go)
MCP Protocol Tests (Label: mcp-protocol)
  • Streamable HTTP (osv_streamable_http_mcp_server_test.go)
  • Remote MCP servers (remote_mcp_server_test.go)
  • Protocol builds (protocol_builds_e2e_test.go)
  • Inspector functionality (inspector_test.go, inspector_autocleanup_test.go)

Note: Both mcp-run and mcp-protocol tests also carry the parent mcp label, so LABEL_FILTER=mcp still runs all MCP tests locally.

Proxy Tests (Label: proxy)
  • Stdio proxy (proxy_stdio_test.go)
  • OAuth authentication (proxy_oauth_test.go)
  • Tunnel functionality (proxy_tunnel_e2e_test.go)
  • Streamable HTTP proxy (stdio_proxy_over_streamable_http_mcp_server_test.go)
  • SSE endpoint rewriting (sse_endpoint_rewrite_test.go)
  • Network isolation (network_isolation_test.go)
Middleware & Stability Tests (Label: middleware || stability)
  • Audit middleware (audit_middleware_e2e_test.go)
  • Authorization (osv_authz_test.go, http_pdp_authz_test.go)
  • Telemetry middleware (telemetry_middleware_e2e_test.go, telemetry_metrics_validation_e2e_test.go)
  • Stability tests (unhealthy_workload_test.go, health_check_zombie_test.go)
API Registry Tests (Label: api-registry)
  • Registry CRUD operations (api_registry_test.go)
API Workloads Tests (Label: api-workloads)
  • Workload endpoints (api_workloads_test.go)
  • Workload lifecycle (api_workload_lifecycle_test.go)
API Clients Tests (Label: api-clients)
  • Client management (api_clients_test.go, api_clients_validation_test.go)
  • Skills API (api_skills_test.go)
API Misc Tests (Label: api-misc)
  • Discovery API (api_discovery_test.go)
  • Groups API (api_groups_test.go)
  • Health check API (api_healthcheck_test.go)
  • Version API (api_version_test.go)
  • Secrets API (api_secrets_test.go)

Note: All api-* tests also carry the parent api label, so LABEL_FILTER=api still runs all API tests locally.

Running Tests

Prerequisites
  • Go installed
  • Ginkgo CLI installed: go install github.com/onsi/ginkgo/v2/ginkgo@latest
  • Docker, Podman, or Colima container runtime
  • ToolHive binary built (for CLI tests): task build
Run All Tests
cd test/e2e
./run_tests.sh
Run Tests by Label
cd test/e2e

# Run only core CLI tests
E2E_LABEL_FILTER=core ./run_tests.sh

# Run only API tests
E2E_LABEL_FILTER=api ./run_tests.sh

# Run only MCP protocol tests
E2E_LABEL_FILTER=mcp ./run_tests.sh

# Run proxy tests
E2E_LABEL_FILTER=proxy ./run_tests.sh

# Run middleware and stability tests
E2E_LABEL_FILTER='middleware || stability' ./run_tests.sh
Run with Ginkgo Directly
cd test/e2e

# Run all tests
ginkgo run --vv .

# Run specific label
ginkgo run --label-filter="api" .

# Run specific test file
ginkgo run --focus-file="api_healthcheck_test.go" .
Run from Project Root
# Run all e2e tests
task test-e2e

# Run with custom label filter
E2E_LABEL_FILTER=api task test-e2e

GitHub Actions Integration

The e2e tests run in parallel in GitHub Actions using label filters. The workflow:

  1. Builds the ToolHive binary once and shares it across jobs
  2. Runs tests in parallel using matrix strategy with 9 label-based buckets:
    • core: Core CLI functionality (~57 specs)
    • mcp-run: MCP server run tests (~33 specs)
    • mcp-protocol: MCP protocol & inspector tests (~37 specs)
    • proxy: Proxy tests (~25 specs)
    • middleware: Middleware & stability tests (~28 specs)
    • api-registry: Registry API tests (~41 specs)
    • api-workloads: Workloads API tests (~56 specs)
    • api-clients: Clients & skills API tests (~44 specs)
    • api-misc: Discovery, groups, health, version, secrets API tests (~50 specs)
  3. Uploads test results as artifacts

See .github/workflows/e2e-tests.yml for the full configuration.

Writing Tests

Adding New CLI Tests
  1. Create a new test file (e.g., feature_test.go)
  2. Add appropriate labels for categorization
  3. Use existing helper functions from helpers.go
  4. Follow the pattern of existing tests

Example:

var _ = Describe("Feature Name", Label("core", "e2e"), func() {
    It("should do something", func() {
        // Test implementation
    })
})
Adding New API Tests
  1. Create a new test file (e.g., api_workloads_test.go)
  2. Use the api label along with specific labels
  3. Use e2e.StartServer() helper to start the API server
  4. Make HTTP requests using the server's methods

Example:

var _ = Describe("Workloads API", Label("api", "workloads"), func() {
    var apiServer *e2e.Server

    BeforeEach(func() {
        config := e2e.NewServerConfig()
        apiServer = e2e.StartServer(config)
    })

    It("should list workloads", func() {
        resp, err := apiServer.Get("/api/v1beta/workloads")
        Expect(err).ToNot(HaveOccurred())
        defer resp.Body.Close()
        Expect(resp.StatusCode).To(Equal(http.StatusOK))
    })
})

Troubleshooting

Container Runtime Not Available

Ensure Docker, Podman, or Colima is running:

docker ps
# or
podman ps
# or
colima status
Binary Not Found (CLI Tests)

Build the ToolHive binary:

task build
# Binary will be at ./bin/thv

Set the binary path if needed:

export THV_BINARY=/path/to/thv
Test Timeouts

Increase the timeout:

TEST_TIMEOUT=20m ./run_tests.sh
Port Conflicts (API Tests)

API tests use random available ports by default. If you encounter port binding issues, the system will automatically find an available port.

Test Best Practices

  1. Use descriptive labels - Make it easy to filter and run related tests
  2. Clean up resources - Use DeferCleanup or AfterEach to clean up
  3. Use unique names - Use GenerateUniqueServerName() for server names
  4. Avoid hardcoded ports - Use random ports for API tests
  5. Test isolation - Ensure tests can run independently
  6. Meaningful assertions - Add context messages to assertions
  7. Use Serial when needed - Mark tests as Serial if they can't run in parallel

Documentation

Overview

Package e2e provides end-to-end testing utilities for ToolHive HTTP API.

Package e2e provides end-to-end testing utilities for ToolHive.

Index

Constants

View Source
const (
	// MCPVersionModern is the Modern (stateless) MCP protocol version.
	MCPVersionModern = "2026-07-28"

	// MetaKeyProtocolVersion is the reserved _meta key carrying the per-request
	// protocol version on Modern requests.
	MetaKeyProtocolVersion = "io.modelcontextprotocol/protocolVersion"
	// MetaKeyClientInfo is the reserved _meta key carrying client implementation
	// info on Modern requests. Optional: a Modern request may omit it.
	MetaKeyClientInfo = "io.modelcontextprotocol/clientInfo"
	// MetaKeyClientCapabilities is the reserved _meta key carrying client
	// capabilities on Modern requests.
	MetaKeyClientCapabilities = "io.modelcontextprotocol/clientCapabilities"

	// HeaderMCPProtocolVersion is the HTTP header carrying the Modern protocol
	// version.
	HeaderMCPProtocolVersion = "MCP-Protocol-Version"
	// HeaderMCPSessionID is the HTTP header carrying the Legacy session id.
	HeaderMCPSessionID = "Mcp-Session-Id"
	// HeaderMCPMethod is the HTTP header carrying the JSON-RPC method on Modern
	// requests. A real go-sdk v1.7 streamable-HTTP server requires it on every
	// Modern request (see pkg/mcp/revision.go ValidateHeaderConsistency); the
	// ToolHive single-server proxy does not read it, but a Modern backend does.
	HeaderMCPMethod = "Mcp-Method"
	// HeaderMCPName is the HTTP header naming the request's target
	// tool/resource/prompt on name-bearing Modern methods (tools/call,
	// resources/read, prompts/get). Sent as the plain identifier; the draft
	// spec also permits a base64 sentinel encoding, which we do not use.
	HeaderMCPName = "Mcp-Name"
)

The following mirror the reserved wire constants defined in pkg/mcp/revision.go. They are duplicated here rather than imported: this client must stay independent of any MCP library (production or SDK) so it can emit traffic no conforming implementation would ever produce.

View Source
const ServerReadyTimeoutEnv = "TOOLHIVE_E2E_SERVER_READY_TIMEOUT"

ServerReadyTimeoutEnv overrides how long specs wait for a freshly started workload to reach the running state.

Variables

This section is empty.

Functions

func CheckTHVBinaryAvailable

func CheckTHVBinaryAvailable(config *TestConfig) error

CheckTHVBinaryAvailable checks if the thv binary is available

func CreateAndTrackGroup added in v0.2.9

func CreateAndTrackGroup(config *TestConfig, groupName string, createdGroups *[]string)

CreateAndTrackGroup creates a group and tracks it for cleanup

func CreateFakeBrowserDir added in v0.26.1

func CreateFakeBrowserDir(tempDir string) (string, error)

CreateFakeBrowserDir writes stub open/xdg-open scripts into a "fakebin" subdirectory of tempDir. The stubs GET the auth URL without following the redirect, so the OIDC mock server receives the request and populates authRequestChan while CompleteAuthRequest drives the callback. Returns the directory so callers can prepend it to PATH.

func DebugServerState

func DebugServerState(config *TestConfig, serverName string)

DebugServerState prints debugging information about a server

func ExpectMCPServersRunning added in v0.41.0

func ExpectMCPServersRunning(config *TestConfig, serverNames ...string)

ExpectMCPServersRunning waits for each named workload to reach the running state within ServerReadyTimeout, and fails naming the workload that did not.

All workloads are polled concurrently so that the total wait is bounded by the slowest workload rather than the sum of all waits.

func FindWorkload added in v0.41.0

func FindWorkload(newCmd func(args ...string) *THVCommand, serverName string) (*core.Workload, error)

FindWorkload returns the named workload's record as reported by `thv list`, or nil if it is not listed. newCmd builds each list invocation, so a spec that runs thv under an isolated config/home/data env passes its own builder and observes the workloads created in that state rather than the real config.

--all is required so that workloads which have not reached running yet (starting, error) are visible rather than filtered out.

func GenerateUniqueServerName added in v0.6.17

func GenerateUniqueServerName(prefix string) string

GenerateUniqueServerName creates a unique server name for tests

func GetMCPServerURL

func GetMCPServerURL(config *TestConfig, serverName string) (string, error)

GetMCPServerURL gets the URL for an MCP server

func GetServerLogs

func GetServerLogs(config *TestConfig, serverName string) (string, error)

GetServerLogs gets the logs for a server to help with debugging

func IsServerRunning added in v0.2.9

func IsServerRunning(config *TestConfig, serverName string) bool

IsServerRunning checks if an MCP server is running

func NewBatchBody added in v0.41.0

func NewBatchBody(reqs ...*RawRequest) ([]byte, error)

NewBatchBody renders a JSON-RPC batch: a JSON array of the wire bodies of reqs, in order. Combine with RawMCPClient.SendRaw to send it. Each element is validated the same as a standalone RawRequest; a deliberately malformed batch payload (bad JSON, wrong array shape) must be hand-built and sent via SendRaw directly.

func RemoveGroup added in v0.2.9

func RemoveGroup(config *TestConfig, groupName string) error

RemoveGroup removes a group by name

func ServerReadyTimeout added in v0.41.0

func ServerReadyTimeout() time.Duration

ServerReadyTimeout returns the budget a spec should give a freshly started workload to reach the running state. Set ServerReadyTimeoutEnv to any Go duration (e.g. "3m") to raise it on a slow machine.

func StartDockerCommand added in v0.0.40

func StartDockerCommand(args ...string) *exec.Cmd

StartDockerCommand starts a docker command with proper environment setup and returns the command

func StartLongRunningTHVCommand added in v0.0.40

func StartLongRunningTHVCommand(config *TestConfig, args ...string) *exec.Cmd

StartLongRunningTHVCommand starts a long-running ToolHive command and returns the process

func StopAndRemoveMCPServer

func StopAndRemoveMCPServer(config *TestConfig, serverName string) error

StopAndRemoveMCPServer stops and removes an MCP server This function is designed for cleanup and tolerates servers that don't exist

func WaitForMCPServer

func WaitForMCPServer(config *TestConfig, serverName string, timeout time.Duration) error

WaitForMCPServer waits for an MCP server to be running.

The status is read from the named workload's own record in `thv list --all --format json`. Substring-matching the text table instead would accept "this name appears somewhere and some workload is running", which can return while the named workload is still starting. A timeout reports the last status observed and dumps the server state, so a CI log shows whether the workload was still starting, had errored, or never appeared at all.

func WaitForMCPServerReady

func WaitForMCPServerReady(config *TestConfig, serverURL string, mode string, timeout time.Duration) error

WaitForMCPServerReady waits for an MCP server to be ready and responsive

func WaitForVMCPHealthReady added in v0.24.1

func WaitForVMCPHealthReady(healthURL string, timeout time.Duration) error

WaitForVMCPHealthReady polls the vMCP /health endpoint until it returns 200 OK or the timeout is reached. Use this instead of WaitForMCPServerReady when incoming auth is configured (MCP Initialize would fail with 401 for unauthenticated probes).

func WaitForWorkloadUnhealthy added in v0.2.7

func WaitForWorkloadUnhealthy(config *TestConfig, serverName string, timeout time.Duration) error

WaitForWorkloadUnhealthy waits for a workload to be marked as unhealthy

Types

type AuthRequest added in v0.0.40

type AuthRequest struct {
	ClientID      string
	RedirectURI   string
	State         string
	CodeChallenge string
	ResponseType  string
	Scope         string
}

AuthRequest contains the parameters from an OAuth authorization request

type GatewayRequest added in v0.26.1

type GatewayRequest struct {
	Method string
	Path   string
	// Bearer is the token value stripped from the Authorization header,
	// or empty if no Authorization header was present.
	Bearer string
}

GatewayRequest records a single request received by the mock LLM gateway.

type LLMGatewayMock added in v0.26.1

type LLMGatewayMock struct {
	// contains filtered or unexported fields
}

LLMGatewayMock is a server that responds to OpenAI-compatible requests (/v1/models, /v1/chat/completions) and records every request it receives.

Use NewLLMGatewayMock for HTTPS (self-signed cert) or NewLLMGatewayMockHTTP for plain HTTP. The HTTP variant is simpler for e2e tests where the thv subprocess cannot be easily configured to trust a self-signed cert.

func NewLLMGatewayMock added in v0.26.1

func NewLLMGatewayMock(port int) (*LLMGatewayMock, error)

NewLLMGatewayMock creates a mock LLM gateway that serves HTTPS with a self-signed certificate. Use CertPEM / TLSClientConfig to build a trusting HTTP client. Call Start to begin serving.

func (*LLMGatewayMock) CertPEM added in v0.26.1

func (m *LLMGatewayMock) CertPEM() []byte

CertPEM returns the PEM-encoded self-signed certificate. Use this to build a custom *http.Client or x509.CertPool that trusts the mock gateway.

func (*LLMGatewayMock) LastBearerToken added in v0.26.1

func (m *LLMGatewayMock) LastBearerToken() string

LastBearerToken returns the Bearer token from the most recent request, or empty string if no requests have been received or none carried a token.

func (*LLMGatewayMock) Start added in v0.26.1

func (m *LLMGatewayMock) Start() error

Start begins serving requests. It blocks briefly until the port is open.

func (*LLMGatewayMock) Stop added in v0.26.1

func (m *LLMGatewayMock) Stop() error

Stop shuts down the mock gateway.

func (*LLMGatewayMock) URL added in v0.26.1

func (m *LLMGatewayMock) URL() string

URL returns the base URL of the mock gateway (https:// or http:// depending on how the mock was created).

type MCPClientHelper

type MCPClientHelper struct {
	// contains filtered or unexported fields
}

MCPClientHelper provides high-level MCP client operations for e2e tests

func NewMCPClientForSSE

func NewMCPClientForSSE(config *TestConfig, serverURL string) (*MCPClientHelper, error)

NewMCPClientForSSE creates a new MCP client for SSE transport

func NewMCPClientForStreamableHTTP added in v0.0.48

func NewMCPClientForStreamableHTTP(config *TestConfig, serverURL string) (*MCPClientHelper, error)

NewMCPClientForStreamableHTTP creates a new MCP client for streamable HTTP transport

func NewMCPClientForStreamableHTTPWithToken added in v0.24.1

func NewMCPClientForStreamableHTTPWithToken(config *TestConfig, serverURL, token string) (*MCPClientHelper, error)

NewMCPClientForStreamableHTTPWithToken creates a new MCP client for streamable HTTP transport that sends an Authorization Bearer token on every request. Use this when the vMCP server has OIDC incoming auth enabled.

func (*MCPClientHelper) CallTool

func (h *MCPClientHelper) CallTool(
	ctx context.Context, toolName string, arguments map[string]interface{},
) (*mcp.CallToolResult, error)

CallTool calls a specific tool with the given arguments

func (*MCPClientHelper) Close

func (h *MCPClientHelper) Close() error

Close closes the MCP client connection

func (*MCPClientHelper) ExpectToolCall

func (h *MCPClientHelper) ExpectToolCall(
	ctx context.Context, toolName string, arguments map[string]interface{},
) *mcp.CallToolResult

ExpectToolCall verifies that a tool can be called successfully

func (*MCPClientHelper) ExpectToolExists

func (h *MCPClientHelper) ExpectToolExists(ctx context.Context, toolName string)

ExpectToolExists verifies that a tool with the given name exists

func (*MCPClientHelper) Initialize

func (h *MCPClientHelper) Initialize(ctx context.Context) error

Initialize initializes the MCP connection

func (*MCPClientHelper) ListTools

func (h *MCPClientHelper) ListTools(ctx context.Context) (*mcp.ListToolsResult, error)

ListTools lists all available tools from the MCP server

func (*MCPClientHelper) Ping

func (h *MCPClientHelper) Ping(ctx context.Context) error

Ping sends a ping to test connectivity

type OIDCMockOption added in v0.25.0

type OIDCMockOption struct {
	// contains filtered or unexported fields
}

OIDCMockOption is a unified option type for configuring the OIDC mock server. Use WithClientAudience for client-registration settings and WithAccessTokenLifespan (or other fosite-level helpers) for token-lifecycle settings. A single constructor accepts both, so tests needing both a custom token lifetime and a specific audience no longer require separate constructors.

func WithAccessTokenLifespan added in v0.0.48

func WithAccessTokenLifespan(d time.Duration) OIDCMockOption

WithAccessTokenLifespan sets the lifespan of access tokens for the OIDC mock server.

func WithClientAudience added in v0.24.1

func WithClientAudience(audiences ...string) OIDCMockOption

WithClientAudience sets the allowed audience(s) on the registered test client. Use this when the vMCP OIDC config requires a specific audience claim in tokens.

type OIDCMockServer added in v0.0.40

type OIDCMockServer struct {
	// contains filtered or unexported fields
}

OIDCMockServer represents a lightweight OIDC server using Ory Fosite

func NewOIDCMockServer added in v0.0.40

func NewOIDCMockServer(port int, clientID, clientSecret string, opts ...OIDCMockOption) (*OIDCMockServer, error)

NewOIDCMockServer creates a new OIDC mock server using Ory Fosite. Use WithClientAudience to set client-level options and WithAccessTokenLifespan for Fosite-level settings. Both option kinds may be mixed in a single call.

func (*OIDCMockServer) CompleteAuthRequest added in v0.0.40

func (*OIDCMockServer) CompleteAuthRequest(authReq *AuthRequest) error

CompleteAuthRequest automatically completes an OAuth request by making a callback

func (*OIDCMockServer) EnableAutoComplete added in v0.0.40

func (m *OIDCMockServer) EnableAutoComplete()

EnableAutoComplete enables automatic OAuth flow completion for testing

func (*OIDCMockServer) Start added in v0.0.40

func (m *OIDCMockServer) Start() error

Start starts the OIDC mock server

func (*OIDCMockServer) Stop added in v0.0.40

func (m *OIDCMockServer) Stop() error

Stop stops the OIDC mock server

func (*OIDCMockServer) WaitForAuthRequest added in v0.0.40

func (m *OIDCMockServer) WaitForAuthRequest(ctx context.Context, timeout time.Duration) (*AuthRequest, error)

WaitForAuthRequest waits for an OAuth authorization request and returns its parameters. The call returns as soon as ctx is cancelled, the timeout elapses, or an auth request arrives — whichever comes first.

type RawMCPClient added in v0.41.0

type RawMCPClient struct {
	// contains filtered or unexported fields
}

RawMCPClient sends raw MCP-over-HTTP JSON-RPC requests with byte-level control over the wire shape. It wraps a single *http.Client (and its connection pool), shared and safe for concurrent use across goroutines — construct one RawMCPClient per test suite, not one per request.

func NewRawMCPClient added in v0.41.0

func NewRawMCPClient(timeout time.Duration) (*RawMCPClient, error)

NewRawMCPClient creates a RawMCPClient. Every request made through it is bounded by timeout, so a stuck proxy fails fast instead of hanging the test.

func (*RawMCPClient) Send added in v0.41.0

func (c *RawMCPClient) Send(ctx context.Context, url string, req *RawRequest) (*RawResponse, error)

Send marshals req and POSTs it to url. No Accept header is set by default: the ToolHive proxy returns a plain JSON body without it, and switches to an SSE response body whenever Accept lists text/event-stream. A request bound for a REAL streamable-HTTP MCP backend -- which rejects a POST lacking that Accept with HTTP 400 -- should opt in via req.WithStreamableAccept().

Either way the JSON-RPC envelope is parsed into the RawResponse: a Content-Type: text/event-stream body has its response event extracted first (see sseResponsePayload). Some servers answer a POST as SSE regardless of the request's Accept header -- mcpcompat's cross-replica rehydration path does, because go-sdk v1.7.0-pre.3 does not export the transport-level JSONResponse knob (StreamableServerTransport.jsonResponse) that its top-level handler options set -- so this parsing cannot be opt-in.

func (*RawMCPClient) SendRaw added in v0.41.0

func (c *RawMCPClient) SendRaw(ctx context.Context, url string, headers map[string]string, body []byte) (*RawResponse, error)

SendRaw POSTs body verbatim to url with the given headers — for batches built with NewBatchBody, or any payload (truncated, oversized, garbage) not expressible via RawRequest.

type RawRPCError added in v0.41.0

type RawRPCError struct {
	Code    int64
	Message string
	Data    json.RawMessage
}

RawRPCError is the JSON-RPC "error" object of a RawResponse.

type RawRequest added in v0.41.0

type RawRequest struct {
	// contains filtered or unexported fields
}

RawRequest builds a single JSON-RPC request for MCP-over-HTTP, or carries a raw pre-built body that bypasses the builder entirely. Construct one with NewLegacyRequest, NewLegacyInitializeRequest, or NewModernRequest, then chain the With*/Set*/Delete* methods to adjust it. The zero value is not usable.

Finish building before sending: Send/SendRaw only read a RawRequest, so a fully-built one is safe to share read-only across goroutines (e.g. one RawRequest fanned out to many concurrent senders), but mutating it with Set*/With* concurrently with a send is a data race.

func NewLegacyInitializeRequest added in v0.41.0

func NewLegacyInitializeRequest(clientName, clientVersion string) *RawRequest

NewLegacyInitializeRequest builds a standard Legacy "initialize" request.

func NewLegacyRequest added in v0.41.0

func NewLegacyRequest(method string, params map[string]any) (*RawRequest, error)

NewLegacyRequest builds a Legacy (2025-11-25) JSON-RPC request for the given method. params is cloned; the caller's map is never mutated by the returned RawRequest.

func NewModernRequest added in v0.41.0

func NewModernRequest(method string, params map[string]any) (*RawRequest, error)

NewModernRequest builds a Modern (2026-07-28) JSON-RPC request emitting the full conformant wire shape a real go-sdk v1.7 client produces: the MCP-Protocol-Version header, the reserved _meta keys protocolVersion and clientCapabilities (clientInfo is optional per the draft schema and is omitted by default), the Mcp-Method header (always), and the Mcp-Name header for name-bearing methods (tools/call, resources/read, prompts/get). A Modern backend rejects a request missing these headers (-32020); the ToolHive single-server proxy ignores them. Each field can be independently overridden or removed via SetHeader/DeleteHeader and SetMeta/DeleteMeta.

func (*RawRequest) DeleteHeader added in v0.41.0

func (r *RawRequest) DeleteHeader(key string) *RawRequest

DeleteHeader removes a header, e.g. to omit MCP-Protocol-Version entirely.

func (*RawRequest) DeleteMeta added in v0.41.0

func (r *RawRequest) DeleteMeta(key string) *RawRequest

DeleteMeta removes a params._meta key, e.g. to make clientInfo absent.

func (*RawRequest) SetHeader added in v0.41.0

func (r *RawRequest) SetHeader(key, value string) *RawRequest

SetHeader sets an arbitrary HTTP header on the request.

func (*RawRequest) SetMeta added in v0.41.0

func (r *RawRequest) SetMeta(key string, value any) *RawRequest

SetMeta sets a params._meta key to an arbitrary value, including a non-object value for a key the spec requires to be an object (to trigger error paths). It lazily creates the _meta object if none exists yet.

func (*RawRequest) WithClientInfo added in v0.41.0

func (r *RawRequest) WithClientInfo(name, version string) *RawRequest

WithClientInfo sets the _meta clientInfo object.

func (*RawRequest) WithID added in v0.41.0

func (r *RawRequest) WithID(id any) *RawRequest

WithID overrides the JSON-RPC id, e.g. to collide two requests' ids or to use a value outside the int64-safe float64 range.

func (*RawRequest) WithRawBody added in v0.41.0

func (r *RawRequest) WithRawBody(body []byte) *RawRequest

WithRawBody replaces the entire request body with body, sent verbatim. Use this for truncated, oversized, or garbage bodies. body is cloned; mutating the caller's slice afterward has no effect on the request.

func (*RawRequest) WithSessionID added in v0.41.0

func (r *RawRequest) WithSessionID(sessionID string) *RawRequest

WithSessionID sets the Mcp-Session-Id header, including a foreign/spoofed session id.

func (*RawRequest) WithStreamableAccept added in v0.41.0

func (r *RawRequest) WithStreamableAccept() *RawRequest

WithStreamableAccept sets "Accept: application/json, text/event-stream". A real go-sdk streamable-HTTP server rejects a POST without this header (HTTP 400), so requests bound for a real backend (e.g. the k8s tier) must set it. Requests to the ToolHive proxy still omit it by convention: the proxy does not require it, and omitting it keeps responses plain JSON (the client parses SSE responses too -- see sseResponsePayload -- and flipping proxy-bound requests to the conformant Accept is tracked in #6104).

type RawResponse added in v0.41.0

type RawResponse struct {
	StatusCode int
	Headers    http.Header
	Body       []byte // raw response body, always populated
	JSONRPC    string // echoed "jsonrpc" version tag; "" if absent or unparsable
	ID         any    // echoed "id"; nil if absent, JSON null, or unparsable
	Result     json.RawMessage
	Error      *RawRPCError
}

RawResponse is the parsed result of sending a single (non-batch) JSON-RPC request over MCP-over-HTTP. For batch responses (a JSON array), inspect Body directly instead of JSONRPC/ID/Result/Error.

type Server added in v0.8.0

type Server struct {
	// contains filtered or unexported fields
}

Server represents a running API server instance for testing. It runs `thv serve` as a subprocess.

func NewServer added in v0.8.0

func NewServer(config *ServerConfig) (*Server, error)

NewServer creates and starts a new API server instance by running `thv serve` as a subprocess.

func StartServer added in v0.8.0

func StartServer(config *ServerConfig) *Server

StartServer is a helper function that creates and starts an API server and registers cleanup in the Ginkgo AfterEach

func (*Server) BaseURL added in v0.8.0

func (s *Server) BaseURL() string

BaseURL returns the base URL of the API server.

func (*Server) Get added in v0.8.0

func (s *Server) Get(path string) (*http.Response, error)

Get performs a GET request to the specified path.

func (*Server) GetStderr added in v0.8.1

func (s *Server) GetStderr() string

GetStderr returns the accumulated stderr output from the server process.

func (*Server) GetStdout added in v0.8.1

func (s *Server) GetStdout() string

GetStdout returns the accumulated stdout output from the server process.

func (*Server) Stop added in v0.8.0

func (s *Server) Stop() error

Stop stops the API server subprocess.

func (*Server) WaitForReady added in v0.8.0

func (s *Server) WaitForReady() error

WaitForReady waits for the API server to be ready to accept requests.

type ServerConfig added in v0.8.0

type ServerConfig struct {
	Address        string
	StartTimeout   time.Duration
	RequestTimeout time.Duration
	DebugMode      bool
	// ExtraEnv adds "KEY=VALUE" environment variables to the `thv serve`
	// subprocess, for tests that need to opt into experimental or
	// feature-gated behavior without changing it for every other E2E test.
	ExtraEnv []string
}

ServerConfig holds configuration for the API server in tests

func NewServerConfig added in v0.8.0

func NewServerConfig() *ServerConfig

NewServerConfig creates a new API server configuration with defaults

type THVCommand

type THVCommand struct {
	// contains filtered or unexported fields
}

THVCommand represents a ToolHive CLI command execution

func NewTHVCommand

func NewTHVCommand(config *TestConfig, args ...string) *THVCommand

NewTHVCommand creates a new ToolHive command

func (*THVCommand) ExpectFailure

func (c *THVCommand) ExpectFailure() (string, string, error)

ExpectFailure runs the command and expects it to fail

func (*THVCommand) ExpectSuccess

func (c *THVCommand) ExpectSuccess() (string, string)

ExpectSuccess runs the command and expects it to succeed

func (*THVCommand) Interrupt added in v0.6.12

func (c *THVCommand) Interrupt() error

Interrupt interrupts the command and does NOT wait for it to exit.

func (*THVCommand) Run

func (c *THVCommand) Run() (string, string, error)

Run executes the ToolHive command and returns stdout, stderr, and error

func (*THVCommand) RunWithTimeout

func (c *THVCommand) RunWithTimeout(timeout time.Duration) (string, string, error)

RunWithTimeout executes the ToolHive command with a specific timeout

func (*THVCommand) WithEnv

func (c *THVCommand) WithEnv(env ...string) *THVCommand

WithEnv adds environment variables to the command

func (*THVCommand) WithStdin added in v0.2.9

func (c *THVCommand) WithStdin(stdin string) *THVCommand

WithStdin sets the stdin input for the command

type TestConfig

type TestConfig struct {
	THVBinary    string
	TestTimeout  time.Duration
	CleanupAfter bool
}

TestConfig holds configuration for e2e tests

func NewTestConfig

func NewTestConfig() *TestConfig

NewTestConfig creates a new test configuration with defaults

Directories

Path Synopsis
Package images provides centralized container image references for e2e tests.
Package images provides centralized container image references for e2e tests.
thv-operator
testutil
Package testutil provides shared helpers for operator E2E tests.
Package testutil provides shared helpers for operator E2E tests.
virtualmcp
Package virtualmcp provides helper functions for VirtualMCP E2E tests.
Package virtualmcp provides helper functions for VirtualMCP E2E tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL