Skip to content

Conversation

@buger
Copy link
Member

@buger buger commented Feb 1, 2026

Summary

Extends the Tyk Gateway test framework with utilities for mocking JSON-RPC 2.0 services, making it easier to write integration tests for the new JSON-RPC/MCP features introduced in #7719.

  • MockJSONRPCServer: A configurable mock server that supports:

    • Static method responses via MockMethod()
    • Dynamic handlers for assertions via MockMethodHandler()
    • Request recording for verification
    • Configurable behavior for unmocked methods
  • Request builders: Helper functions to construct JSON-RPC payloads:

    • BuildToolsCallRequest() - for tools/call
    • BuildResourcesReadRequest() - for resources/read
    • BuildPromptsGetRequest() - for prompts/get
    • BuildJSONRPCRequest() - generic builder
  • Integration tests: Demonstrates usage patterns for:

    • Rate limiting enforcement per-tool
    • ACL/allowlist enforcement for tools and resources
    • Dynamic handler assertions

Test plan

  • Unit tests for MockJSONRPCServer pass
  • Unit tests for request builders pass
  • Gateway package builds successfully
  • Integration tests pass with Redis available

🤖 Generated with Claude Code

This extends the test framework with utilities to simplify testing JSON-RPC
endpoints:

- MockJSONRPCServer: A configurable mock server that can return static or
  dynamic responses, record received requests, and support assertions
- Request builders: Helper functions for creating tools/call, resources/read,
  and prompts/get JSON-RPC payloads
- Integration tests: Demonstrates usage for rate limiting and ACL testing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@probelabs
Copy link

probelabs bot commented Feb 1, 2026

This PR introduces a comprehensive test utility, MockJSONRPCServer, to facilitate integration testing of JSON-RPC 2.0 services within the Tyk Gateway. The primary goal is to support robust testing for the new JSON-RPC/MCP (Meta-Control Plane) features.

The utility includes:

  • A configurable MockJSONRPCServer that simulates a JSON-RPC upstream.
  • Support for both static (MockMethod) and dynamic (MockMethodHandler) responses.
  • A mechanism to record incoming requests and their headers for assertions.
  • A suite of helper functions (BuildToolsCallRequest, BuildResourcesReadRequest, etc.) to simplify the construction of JSON-RPC request payloads in tests.
  • Extensive integration tests demonstrating how to use the mock server to validate gateway policies like rate limiting and ACLs for JSON-RPC tools and resources.

Files Changed Analysis

  • gateway/testutil_jsonrpc.go: (Added: 334 lines) Contains the core implementation of the MockJSONRPCServer and its associated request builders.
  • gateway/testutil_jsonrpc_test.go: (Added: 877 lines) A comprehensive suite of unit and integration tests that validate the mock server and demonstrate its usage in conjunction with the Tyk Gateway for various policy enforcement scenarios.

This pull request consists entirely of new files, adding 1,211 lines of code dedicated to enhancing the gateway's test framework without modifying any existing production logic.

Architecture & Impact Assessment

  • What this PR accomplishes: It adds a foundational testing utility to the gateway's test suite, enabling developers to write reliable and isolated integration tests for features that depend on a JSON-RPC upstream. This directly improves the testability and quality of the new MCP functionality.

  • Key technical changes introduced: The core change is the MockJSONRPCServer, which leverages Go's standard httptest package to create a mock HTTP server that handles JSON-RPC 2.0 requests. It uses maps protected by a sync.RWMutex to manage mocked methods and handlers, ensuring thread safety during tests.

  • Affected system components: The changes are confined to the testing framework of the gateway package. There is no direct impact on the runtime or production code; the utility is purely for development and CI environments.

Test Architecture

The following diagram illustrates how MockJSONRPCServer is used in an integration test:

graph TD
    subgraph Test Environment
        A[Gateway Integration Test] --> B(Tyk Gateway Instance);
        B --> C{MockJSONRPCServer};
        A -- asserts on --> C;
    end

    A -- 1. Sends JSON-RPC request --> B;
    B -- 2. Proxies request to upstream --> C;
    C -- 3. Returns mocked response --> B;
    B -- 4. Returns final response --> A;
    A -- 5. Verifies recorded requests --> C;

    style C fill:#f9f,stroke:#333,stroke-width:2px
Loading

Scope Discovery & Context Expansion

This utility is a direct dependency for testing the JSON-RPC/MCP features introduced in PR #7719. The tests included in this PR (e.g., TestJSONRPC_RateLimiting_WithMockUpstream, TestJSONRPC_ACL_WithMockUpstream) confirm its immediate application: validating that existing gateway policies correctly apply to the new MCP primitives (McpTools, McpResources).

To understand the full context, a reviewer should examine the gateway's middleware and proxying logic that handles requests for APIs marked as MCP endpoints (via def.MarkAsMCP()). This utility will be critical for testing any future enhancements to that logic, such as transformations, authentication, or analytics for JSON-RPC traffic.

Metadata
  • Review Effort: 3 / 5
  • Primary Label: enhancement

Powered by Visor from Probelabs

Last updated: 2026-02-01T10:56:06.215Z | Triggered by: pr_updated | Commit: 0307347

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs
Copy link

probelabs bot commented Feb 1, 2026

Security Issues (2)

Severity Location Issue
🟡 Warning gateway/testutil_jsonrpc.go:121-122
Errors from `json.Marshal` are ignored in `MockMethod` (line 121) and `SetDefaultResponse` (line 142). If an object that cannot be marshaled to JSON is provided during test setup, the error is silently ignored. This results in a `nil` mock response being stored, which can cause confusing test failures later instead of failing fast with a clear reason.
💡 SuggestionCheck the error from `json.Marshal` and panic if it is non-nil. Panicking is an acceptable pattern for helper functions to signal fatal errors in test setup, as it provides an immediate and clear failure.
🟡 Warning gateway/testutil_jsonrpc.go:281
The error returned by `json.Unmarshal` in `writeRawResult` is not checked. If a raw mock response (`rawResult`) was configured with malformed JSON, this error is ignored, and the server returns a successful response with a `null` result field. This masks the underlying problem with the test data and can make debugging tests difficult.
💡 SuggestionCheck the error from `json.Unmarshal`. If an error occurs, it indicates a problem with the mocked data. The server should return a JSON-RPC `Internal error` (-32603) to make the test failure explicit and clear.

Architecture Issues (2)

Severity Location Issue
🟡 Warning gateway/testutil_jsonrpc.go:34
The `JSONRPCHandler` function signature accepts a `*testing.T` parameter, which implies it can be used for assertions within the handler. However, the `handleRequest` method calls the handler with `nil` for this parameter, which will cause a panic if `t` is used. This creates a misleading and unsafe API contract.
💡 SuggestionRemove the `*testing.T` parameter from the `JSONRPCHandler` signature to make the API contract clear. Test context can be provided to handlers via closures, as demonstrated in `TestJSONRPC_DynamicHandler`. The new signature should be: `type JSONRPCHandler func(method string, params json.RawMessage) (result any, errCode int, errMsg string)`.
🟡 Warning gateway/testutil_jsonrpc.go:278-294
The `writeRawResult` function unnecessarily unmarshals the `rawResult` (`json.RawMessage`) into an `any` interface, only to have it be immediately marshaled back into JSON as part of the response. This unmarshal/remarshal cycle is inefficient and adds needless complexity.
💡 SuggestionRefactor the function to construct the final JSON response as a byte slice directly, bypassing the intermediate unmarshaling step. This can be achieved by concatenating the static JSON parts with the marshaled ID and the raw result message.

Performance Issues (1)

Severity Location Issue
🟡 Warning gateway/testutil_jsonrpc.go:273-280
The `writeRawResult` function unnecessarily unmarshals the `rawResult` from `json.RawMessage` into an `any` interface, only for it to be immediately marshaled back into JSON by `json.NewEncoder().Encode()`. This decode-then-encode cycle adds performance overhead for each mocked raw response.
💡 SuggestionTo improve performance, avoid the unmarshaling step. The `json.Encoder` can handle `json.RawMessage` fields directly, embedding the raw JSON content without processing it. This can be achieved by using a temporary struct for the response where the `Result` field is of type `json.RawMessage`.

Quality Issues (3)

Severity Location Issue
🟠 Error gateway/testutil_jsonrpc.go:40
The `JSONRPCHandler` signature `func(t *testing.T, ...)` is misleading and unsafe. The implementation in `handleRequest` (line 212) always passes `nil` for the `*testing.T` parameter. Any handler that attempts to use this `t` argument (e.g., for `t.Helper()` or `t.Error()`) will cause a nil pointer dereference, crashing the test. This creates a fragile API that is easy to misuse.
💡 SuggestionThe `*testing.T` parameter should be removed from the `JSONRPCHandler` signature to create a safer and clearer API. Handlers can return an error if they need to signal failure, and assertions on request contents should be performed in the test function itself after the request is made, by inspecting the recorded requests.
🟡 Warning gateway/testutil_jsonrpc.go:120
The error returned by `json.Marshal` is ignored. If marshaling the `result` object fails (e.g., due to an unsupported type or cyclic reference), the mocked method will be associated with a `nil` or empty `json.RawMessage`. This can lead to silent test failures or unexpected behavior, making debugging difficult as the root cause of the incorrect mock is hidden. The same issue exists in `SetDefaultResponse` on line 143.
💡 SuggestionSince this is a test utility, panicking on a setup error is an acceptable way to fail fast and make the configuration error obvious. Check the error and panic if it's not `nil`.
🟡 Warning gateway/testutil_jsonrpc_test.go:774-776
The test `TestJSONRPC_CombinedHeaderTransformations` and its corresponding "Acceptance Criteria" comment state that it verifies the combination of headers defined at both "method and tool level". However, the test implementation only configures and validates headers at the tool level via `McpTools`. This discrepancy between the test's stated intent and its implementation is misleading and can cause confusion.
💡 SuggestionAlign the test's implementation with its description or update the description to match the implementation. Either add method-level header configuration to the test, or rename the test and update the comment to reflect that it only covers tool-level header transformations (e.g., `TestJSONRPC_ToolLevelHeaderTransformations`).

Powered by Visor from Probelabs

Last updated: 2026-02-01T10:56:09.036Z | Triggered by: pr_updated | Commit: 0307347

💡 TIP: You can chat with Visor using /visor ask <your question>

@github-actions
Copy link
Contributor

github-actions bot commented Feb 1, 2026

API Changes

--- prev.txt	2026-02-01 10:55:33.145210574 +0000
+++ current.txt	2026-02-01 10:55:23.208306883 +0000
@@ -9274,6 +9274,18 @@
 func AuthFailed(m TykMiddleware, r *http.Request, token string)
     TODO: move this method to base middleware?
 
+func BuildJSONRPCRequest(method string, params any, id any) map[string]any
+    BuildJSONRPCRequest builds a JSON-RPC 2.0 request payload.
+
+func BuildPromptsGetRequest(name string, arguments map[string]any, id any) map[string]any
+    BuildPromptsGetRequest builds a JSON-RPC request for prompts/get.
+
+func BuildResourcesReadRequest(uri string, id any) map[string]any
+    BuildResourcesReadRequest builds a JSON-RPC request for resources/read.
+
+func BuildToolsCallRequest(toolName string, arguments map[string]any, id any) map[string]any
+    BuildToolsCallRequest builds a JSON-RPC request for tools/call.
+
 func CheckPortWhiteList(w map[string]config.PortWhiteList, listenPort int, protocol string) error
 func CoProcessLog(CMessage, CLogLevel *C.char)
     CoProcessLog is a bridge for using Tyk log from CP.
@@ -9367,6 +9379,9 @@
     WithQuotaKey overrides quota key manually
 
 func WrappedCharsetReader(s string, i io.Reader) (io.Reader, error)
+func ParseJSONRPCResponse(data []byte) (*jsonRPCResponse, error)
+    ParseJSONRPCResponse parses a JSON-RPC response from a byte slice.
+
 
 TYPES
 
@@ -10832,6 +10847,10 @@
 }
     JSONRPCErrorResponse represents a JSON-RPC 2.0 error response.
 
+type JSONRPCHandler func(t *testing.T, method string, params json.RawMessage) (result any, errCode int, errMsg string)
+    JSONRPCHandler is a function that handles a JSON-RPC request and returns a
+    result or error.
+
 type JSONRPCMiddleware struct {
 	*BaseMiddleware
 }
@@ -10858,6 +10877,15 @@
 }
     JSONRPCRequest represents a JSON-RPC 2.0 request structure.
 
+type JSONRPCTestCase struct {
+	Method    string // JSON-RPC method to call
+	Params    any    // Parameters for the method
+	ID        any    // Request ID (default: 1)
+	ExpectErr bool   // Whether to expect a JSON-RPC error
+	ErrCode   int    // Expected error code (if ExpectErr is true)
+}
+    JSONRPCTestCase extends test.TestCase with JSON-RPC specific fields.
+
 type JSVM struct {
 	Spec    *APISpec
 	VM      *otto.Otto `json:"-"`
@@ -11102,6 +11130,53 @@
 
 func (e *MockErrorReader) Read(_ []byte) (n int, err error)
 
+type MockJSONRPCServer struct {
+	Server *httptest.Server
+
+	// Has unexported fields.
+}
+    MockJSONRPCServer provides a configurable mock JSON-RPC 2.0 server for
+    testing. It supports static mock responses, dynamic handlers for assertions,
+    and request recording.
+
+func NewMockJSONRPCServer() *MockJSONRPCServer
+    NewMockJSONRPCServer creates a new mock JSON-RPC server.
+
+func (m *MockJSONRPCServer) Close()
+    Close shuts down the mock server.
+
+func (m *MockJSONRPCServer) MockMethod(method string, result any)
+    MockMethod sets a static response for a JSON-RPC method. The result will be
+    returned in the "result" field of the JSON-RPC response.
+
+func (m *MockJSONRPCServer) MockMethodHandler(method string, handler JSONRPCHandler)
+    MockMethodHandler sets a dynamic handler for a JSON-RPC method. The handler
+    can perform assertions and return dynamic results.
+
+func (m *MockJSONRPCServer) MockMethodRaw(method string, rawResult json.RawMessage)
+    MockMethodRaw sets a raw JSON response for a JSON-RPC method.
+
+func (m *MockJSONRPCServer) ReceivedRequests() []ReceivedJSONRPCRequest
+    ReceivedRequests returns all received JSON-RPC requests.
+
+func (m *MockJSONRPCServer) ReceivedRequestsForMethod(method string) []ReceivedJSONRPCRequest
+    ReceivedRequestsForMethod returns all received requests for a specific
+    method.
+
+func (m *MockJSONRPCServer) Reset()
+    Reset clears all mocked methods, handlers, and recorded requests.
+
+func (m *MockJSONRPCServer) SetDefaultResponse(result any)
+    SetDefaultResponse sets a default response for unmocked methods. If set,
+    errorOnUnmocked is automatically set to false.
+
+func (m *MockJSONRPCServer) SetErrorOnUnmocked(enabled bool)
+    SetErrorOnUnmocked controls whether unmocked methods return an error
+    (default: true).
+
+func (m *MockJSONRPCServer) URL() string
+    URL returns the URL of the mock server.
+
 type MockReadCloser struct {
 	Reader      io.Reader
 	CloseError  error
@@ -11599,6 +11674,15 @@
     ProcessRequest will run any checks on the request on the way through the
     system, return an error to have the chain fail
 
+type ReceivedJSONRPCRequest struct {
+	Method  string
+	Params  json.RawMessage
+	ID      any
+	Headers http.Header // HTTP headers received with the request
+}
+    ReceivedJSONRPCRequest records a received JSON-RPC request for later
+    assertions.
+
 type RedisAnalyticsHandler struct {
 	Store   storage.AnalyticsHandler
 	GeoIPDB *maxminddb.Reader
… transformations

This commit extends the test suite for PR #7723 with five new integration test scenarios:

1. TestJSONRPC_MethodLevelAllowList: Validates that method-level allow lists correctly proxy allowed tools and reject disallowed tools (Acceptance Criteria #1)

2. TestJSONRPC_ToolLevelAllowList: Validates that tool-level allow lists correctly handle multiple allowed tools and reject disallowed ones (Acceptance Criteria #2)

3. TestJSONRPC_MethodLevelRateLimiting: Validates that rate limits at the method level are correctly enforced (Acceptance Criteria #3)

4. TestJSONRPC_ToolLevelIndependentRateLimiting: Validates that rate limits for different tools are applied independently (Acceptance Criteria #4)

5. TestJSONRPC_CombinedHeaderTransformations: Validates that request transformation headers are correctly applied to upstream requests (Acceptance Criteria #5)

Also extends MockJSONRPCServer to record HTTP headers for verification in the header transformation test, and adds TestMockJSONRPCServer_HeaderRecording unit test.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 1, 2026

🚨 Jira Linter Failed

Commit: 0307347
Failed at: 2026-02-01 10:54:52 UTC

The Jira linter failed to validate your PR. Please check the error details below:

🔍 Click to view error details
failed to validate branch and PR title rules: branch name 'jsonrpc-test-framework-extension' must contain a valid Jira ticket ID (e.g., ABC-123)

Next Steps

  • Ensure your branch name contains a valid Jira ticket ID (e.g., ABC-123)
  • Verify your PR title matches the branch's Jira ticket ID
  • Check that the Jira ticket exists and is accessible

This comment will be automatically deleted once the linter passes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants