Documentation
¶
Overview ¶
Package testutil provides test helpers for gitlab-mcp-server.
It wraps net/http/httptest, the official gitlab.com/gitlab-org/api/client-go client, and shared assertion helpers so every domain test can stand up an isolated MCP server in a few lines. The package is consumed by every tool test under internal/tools and is the only sanctioned way to construct a gitlabclient.Client in tests.
Helper categories ¶
- Client factory: NewTestClient spins up an httptest.Server, wires it into gitlabclient.NewClient, and tears the server down on test exit.
- Response writers: RespondJSON, RespondJSONWithPagination, RespondGraphQL, and RespondGraphQLError keep response shapes consistent across packages.
- Request assertions: AssertRequestMethod, AssertRequestPath, and AssertQueryParam validate inbound HTTP calls in mock handlers.
- Context and logging: CancelledCtx returns a pre-cancelled context; CaptureSlog captures log/slog output to a buffer for assertions.
- Embedded resources: AssertEmbeddedResource toggles the embedded resource global flag and checks MCP call results.
- GraphQL helpers: GraphQLHandler and ParseGraphQLVariables simplify mocking [POST /api/graphql] requests.
Typical usage ¶
func TestListBranches(t *testing.T) {
client := testutil.NewTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
testutil.RespondJSON(w, http.StatusOK, `[{"id":1,"name":"main"}]`)
}))
// ... call the domain handler with client ...
}
Index ¶
- Constants
- func AssertEmbeddedResource(t *testing.T, ctx context.Context, session *mcp.ClientSession, name string, ...)
- func AssertQueryParam(t *testing.T, r *http.Request, key, expected string)
- func AssertRequestMethod(t *testing.T, r *http.Request, expected string)
- func AssertRequestPath(t *testing.T, r *http.Request, expected string)
- func CancelledCtx(t *testing.T) context.Context
- func CaptureSlog(t *testing.T) *bytes.Buffer
- func GraphQLHandler(handlers map[string]http.HandlerFunc) http.Handler
- func NewTestClient(t *testing.T, handler http.Handler) *gitlabclient.Client
- func ParseGraphQLVariables(r *http.Request) (map[string]any, error)
- func RespondGraphQL(w http.ResponseWriter, status int, data string)
- func RespondGraphQLError(w http.ResponseWriter, status int, message string)
- func RespondJSON(w http.ResponseWriter, status int, body string)
- func RespondJSONWithPagination(w http.ResponseWriter, status int, body string, p PaginationHeaders)
- type EmbedToggle
- type PaginationHeaders
Constants ¶
const MsgErrEmptyProjectID = "expected error for empty project_id, got nil"
MsgErrEmptyProjectID is the canonical assertion message for tests that expect an error when the GitLab tool input omits project_id.
Variables ¶
This section is empty.
Functions ¶
func AssertEmbeddedResource ¶
func AssertEmbeddedResource(t *testing.T, ctx context.Context, session *mcp.ClientSession, name string, args map[string]any, wantURI string, toggle EmbedToggle)
AssertEmbeddedResource verifies that an MCP tool behaves correctly under both states of the embedded-resource toggle. It invokes the named tool via session twice:
- With toggle(true) — expects an *mcp.EmbeddedResource block whose URI matches wantURI and whose MIME type is "application/json".
- With toggle(false) — expects no EmbeddedResource blocks.
The toggle is always restored to its enabled (production default) state on test exit via testing.T.Cleanup. The test fails the surrounding run if either subtest fails.
func AssertQueryParam ¶
AssertQueryParam fails the test if the URL query parameter key does not equal expected. Missing parameters are reported as a mismatch with an empty actual value.
func AssertRequestMethod ¶
AssertRequestMethod fails the test if r.Method does not equal expected. It calls testing.T.Helper so failure lines point at the caller.
func AssertRequestPath ¶
AssertRequestPath fails the test if r.URL.Path does not equal expected. It calls testing.T.Helper so failure lines point at the caller.
func CancelledCtx ¶
CancelledCtx returns a context.Context that is already cancelled. Use it to test handler cancellation paths without dealing with real timers.
The returned context returns context.Canceled from context.Context.Err immediately. The associated cancel function is intentionally dropped — callers must not attempt to cancel it again.
func CaptureSlog ¶
CaptureSlog redirects slog output to an in-memory bytes.Buffer for the duration of the test. The original slog.Default logger is restored via testing.T.Cleanup.
CaptureSlog is NOT safe for testing.T.Parallel: it acquires [captureSlogMu] for the lifetime of the test and would deadlock with another parallel CaptureSlog caller.
func GraphQLHandler ¶
func GraphQLHandler(handlers map[string]http.HandlerFunc) http.Handler
GraphQLHandler returns an http.Handler that routes GraphQL POST requests by matching the request's query string against handler keys. The first key that appears as a substring of the query wins; keys are therefore evaluated longest-first so specific mutation names take precedence over shorter operation roots (e.g. "vulnerabilityDismiss" matches before "vulnerability").
The request body is parsed into a [graphqlRequest], then reattached to r so downstream handlers can read it again. Non-POST requests are rejected with 405 Method Not Allowed, malformed JSON with 400 Bad Request, and queries that match no key with 400 Bad Request.
Keys should be GraphQL operation identifiers (field names, type names, or mutation names) that uniquely identify the operation, for example:
testutil.GraphQLHandler(map[string]http.HandlerFunc{
"vulnerabilities": handleListVulnerabilities,
"vulnerabilityDismiss": handleDismissVulnerability,
})
func NewTestClient ¶
NewTestClient creates a gitlabclient.Client pointed at a fresh httptest.Server backed by handler. The server is automatically torn down when the test finishes via testing.T.Cleanup, so callers never need to manage its lifecycle.
The client uses a static token ("test-token"), TLS verification disabled, and retries disabled — sufficient for most handler unit tests but not for exercising the transport retry policy. NewTestClient calls testing.T.Helper and testing.T.Fatalf if client construction fails.
The returned client is safe for concurrent use; the httptest.Server that backs it is goroutine-safe by construction.
func ParseGraphQLVariables ¶
ParseGraphQLVariables reads r's body and returns the Variables map from the GraphQL request, or an error if the body cannot be read or is not valid JSON. The body is restored on r so subsequent handlers can re-read it. The returned map is nil when the request omits a variables field.
ParseGraphQLVariables is intended for assertions inside test handlers — production code should rely on the client-go GraphQL transport.
func RespondGraphQL ¶
func RespondGraphQL(w http.ResponseWriter, status int, data string)
RespondGraphQL writes a GraphQL JSON envelope response with the given data payload. It wraps data in the standard {"data": ...} envelope expected by the GitLab GraphQL API client. For example:
testutil.RespondGraphQL(w, http.StatusOK, `{"project":{"name":"foo"}}`)
produces the body {"data":{"project":{"name":"foo"}}}. Callers remain responsible for escaping any quotes embedded in data.
func RespondGraphQLError ¶
func RespondGraphQLError(w http.ResponseWriter, status int, message string)
RespondGraphQLError writes a GraphQL error envelope with the given message. It sets data to null and emits a single error entry, mirroring the shape returned by GitLab when a query partially or fully fails. For example:
testutil.RespondGraphQLError(w, http.StatusOK, "not found")
produces {"data":null,"errors":[{"message":"not found"}]}. The message is interpolated directly into JSON — callers must escape any embedded quotes.
func RespondJSON ¶
func RespondJSON(w http.ResponseWriter, status int, body string)
RespondJSON writes a JSON response with the given HTTP status and raw body. It sets Content-Type to "application/json". body is written verbatim — callers are responsible for producing valid JSON. Write errors are intentionally ignored because the only writer in tests is an httptest.ResponseRecorder, which never fails.
func RespondJSONWithPagination ¶
func RespondJSONWithPagination(w http.ResponseWriter, status int, body string, p PaginationHeaders)
RespondJSONWithPagination writes a JSON response with GitLab pagination headers attached. Headers whose PaginationHeaders field is empty are omitted, matching GitLab's behavior on pages without a next/previous pointer.
Types ¶
type EmbedToggle ¶
type EmbedToggle func(bool)
EmbedToggle is the signature of the [toolutil.EnableEmbeddedResources] setter. testutil reproduces the type locally so callers can drive the embedded-resource global flag without importing toolutil (which would introduce an import cycle through the tool sub-packages).
type PaginationHeaders ¶
type PaginationHeaders struct {
Page string // X-Page — current page number.
PerPage string // X-Per-Page — items per page in this response.
Total string // X-Total — total items across all pages.
TotalPages string // X-Total-Pages — total page count.
NextPage string // X-Next-Page — next page number, if any.
PrevPage string // X-Prev-Page — previous page number, if any.
}
PaginationHeaders is the set of GitLab pagination response headers returned with list endpoints. Empty fields are omitted from the response so handlers can populate only the headers a given scenario exercises.