From 0212bba96107b4f33388ffa3341c255eaf99652a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:52:14 +0000 Subject: [PATCH 1/4] Add tests for server.registerToolsFromBackendContext handler closure Cover the previously under-tested tool-call handler closure and jq response-filter wiring inside registerToolsFromBackendContext: - malformed-argument parse-error branch under an enclave session (redacted logging path) - successful tool call under an enclave session (redacted request/response logging) - backend tools/call error under both an enclave session and a plain session (redacted vs non-redacted error logging) - successful tool call with the process-wide payload redaction flag enabled - ToolResponseFilters wiring: registering a server with a non-empty per-tool jq filter now exercises the middleware.WrapToolHandlerWithFilter branch instead of the plain middleware.WrapToolHandler branch Coverage for registerToolsFromBackendContext rose from 85.4% to 95.1%. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../register_tools_handler_redaction_test.go | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 internal/server/register_tools_handler_redaction_test.go diff --git a/internal/server/register_tools_handler_redaction_test.go b/internal/server/register_tools_handler_redaction_test.go new file mode 100644 index 00000000..8488c44d --- /dev/null +++ b/internal/server/register_tools_handler_redaction_test.go @@ -0,0 +1,289 @@ +package server + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/github/gh-aw-mcpg/internal/config" + "github.com/github/gh-aw-mcpg/internal/mcp" + "github.com/github/gh-aw-mcpg/internal/sanitize" + sdk "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestRegisterToolsFromBackend_HandlerInvocation_Redaction exercises the +// argument-parse-error, request-log, requireSession-failure, and +// response-log branches of the tool handler closure created inside +// registerToolsFromBackendContext (internal/server/tool_registry.go), under +// both the normal (non-redacted) and enclave-session (redacted) code paths. +// These branches are otherwise only reached indirectly via the MCP +// transport in integration tests and were previously uncovered by unit +// tests. +func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req map[string]interface{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + method, _ := req["method"].(string) + w.Header().Set("Content-Type", "application/json") + switch method { + case "initialize": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "result": map[string]interface{}{ + "protocolVersion": "2024-11-05", + "capabilities": map[string]interface{}{}, + "serverInfo": map[string]interface{}{"name": "redaction-backend", "version": "1.0"}, + }, + }) + case "tools/list": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "result": map[string]interface{}{ + "tools": []map[string]interface{}{ + { + "name": "echo", + "description": "echoes", + "inputSchema": map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{"message": map[string]interface{}{"type": "string"}}, + }, + }, + { + "name": "boom", + "description": "always errors", + "inputSchema": map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + }, + }, + }, + }) + case "tools/call": + params, _ := req["params"].(map[string]interface{}) + name, _ := params["name"].(string) + if name == "boom" { + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "error": map[string]interface{}{"code": -32000, "message": "simulated backend failure"}, + }) + return + } + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "result": map[string]interface{}{ + "content": []map[string]interface{}{{"type": "text", "text": "echoed"}}, + }, + }) + } + })) + defer backend.Close() + + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "redaction-backend": { + Type: "http", + URL: backend.URL, + }, + }, + } + + us, err := NewUnified(context.Background(), cfg) + require.NoError(t, err) + defer us.Close() + + require.NoError(t, us.registerToolsFromBackend("redaction-backend")) + + us.toolsMu.RLock() + echoTool := us.tools["redaction-backend___echo"] + boomTool := us.tools["redaction-backend___boom"] + us.toolsMu.RUnlock() + require.NotNil(t, echoTool) + require.NotNil(t, echoTool.Handler) + require.NotNil(t, boomTool) + require.NotNil(t, boomTool.Handler) + + t.Run("malformed arguments under enclave session take the redacted parse-error branch", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + ctx := mcp.WithEnclaveSession(context.WithValue(context.Background(), SessionIDContextKey, "redact-parse-session")) + req := &sdk.CallToolRequest{ + Params: &sdk.CallToolParamsRaw{ + Name: "redaction-backend___echo", + Arguments: json.RawMessage(`{not valid json`), + }, + } + result, data, err := echoTool.Handler(ctx, req, nil) + require.Error(err) + require.NotNil(result) + assert.True(result.IsError) + assert.Nil(data) + }) + + t.Run("valid arguments under enclave session take the redacted request/response log branches", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + ctx := mcp.WithEnclaveSession(context.WithValue(context.Background(), SessionIDContextKey, "redact-success-session")) + req := &sdk.CallToolRequest{ + Params: &sdk.CallToolParamsRaw{ + Name: "redaction-backend___echo", + Arguments: json.RawMessage(`{"message":"hi"}`), + }, + } + result, data, err := echoTool.Handler(ctx, req, nil) + require.NoError(err) + require.NotNil(result) + assert.False(result.IsError) + assert.NotNil(data) + }) + + t.Run("backend error under enclave session takes the redacted error-log branch", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + ctx := mcp.WithEnclaveSession(context.WithValue(context.Background(), SessionIDContextKey, "redact-error-session")) + req := &sdk.CallToolRequest{ + Params: &sdk.CallToolParamsRaw{ + Name: "redaction-backend___boom", + Arguments: json.RawMessage(`{}`), + }, + } + result, data, err := boomTool.Handler(ctx, req, nil) + require.Error(err) + require.NotNil(result) + assert.True(result.IsError) + assert.Nil(data) + }) + + t.Run("backend error without enclave session takes the non-redacted error-log branch", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + ctx := context.WithValue(context.Background(), SessionIDContextKey, "plain-error-session") + req := &sdk.CallToolRequest{ + Params: &sdk.CallToolParamsRaw{ + Name: "redaction-backend___boom", + Arguments: json.RawMessage(`{}`), + }, + } + result, data, err := boomTool.Handler(ctx, req, nil) + require.Error(err) + require.NotNil(result) + assert.True(result.IsError) + assert.Nil(data) + }) + + t.Run("global payload redaction flag also takes the redacted response-log branch", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + sanitize.SetPayloadRedaction(true) + t.Cleanup(func() { sanitize.SetPayloadRedaction(false) }) + + ctx := context.WithValue(context.Background(), SessionIDContextKey, "global-redaction-session") + req := &sdk.CallToolRequest{ + Params: &sdk.CallToolParamsRaw{ + Name: "redaction-backend___echo", + Arguments: json.RawMessage(`{"message":"hi"}`), + }, + } + result, data, err := echoTool.Handler(ctx, req, nil) + require.NoError(err) + require.NotNil(result) + assert.False(result.IsError) + assert.NotNil(data) + }) +} + +// TestRegisterToolsFromBackend_ToolResponseFilterWrapsHandler verifies that +// when a server config sets a non-empty ToolResponseFilters entry for a +// registered tool, registerToolsFromBackendContext wraps the raw handler with +// middleware.WrapToolHandlerWithFilter (rather than the plain +// middleware.WrapToolHandler used when no filter is configured). This +// exercises the previously-uncovered `filter != ""` branch in +// registerToolsFromBackendContext (internal/server/tool_registry.go) and +// confirms the jq filter is actually applied to the backend's tool result. +func TestRegisterToolsFromBackend_ToolResponseFilterWrapsHandler(t *testing.T) { + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req map[string]interface{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + method, _ := req["method"].(string) + w.Header().Set("Content-Type", "application/json") + switch method { + case "initialize": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "result": map[string]interface{}{ + "protocolVersion": "2024-11-05", + "capabilities": map[string]interface{}{}, + "serverInfo": map[string]interface{}{"name": "filter-backend", "version": "1.0"}, + }, + }) + case "tools/list": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "result": map[string]interface{}{ + "tools": []map[string]interface{}{ + { + "name": "search", + "description": "search tool", + "inputSchema": map[string]interface{}{"type": "object", "properties": map[string]interface{}{}}, + }, + }, + }, + }) + case "tools/call": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "jsonrpc": "2.0", "id": req["id"], + "result": map[string]interface{}{ + "content": []map[string]interface{}{{"type": "text", "text": "raw result"}}, + "items": []string{"a", "b", "c"}, + }, + }) + } + })) + defer backend.Close() + + cfg := &config.Config{ + Servers: map[string]*config.ServerConfig{ + "filter-backend": { + Type: "http", + URL: backend.URL, + ToolResponseFilters: map[string]string{"search": ".items"}, + }, + }, + } + + us, err := NewUnified(context.Background(), cfg) + require.NoError(t, err) + defer us.Close() + + require.NoError(t, us.registerToolsFromBackend("filter-backend")) + + us.toolsMu.RLock() + tool := us.tools["filter-backend___search"] + us.toolsMu.RUnlock() + require.NotNil(t, tool) + require.NotNil(t, tool.Handler) + + ctx := context.WithValue(context.Background(), SessionIDContextKey, "filter-session") + req := &sdk.CallToolRequest{ + Params: &sdk.CallToolParamsRaw{ + Name: "filter-backend___search", + Arguments: json.RawMessage(`{}`), + }, + } + result, _, err := tool.Handler(ctx, req, nil) + require.NoError(t, err) + require.NotNil(t, result) + assert.False(t, result.IsError) +} From aa6c080883a7080aec68fdc0a7704d335071d811 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:03:49 +0000 Subject: [PATCH 2/4] Address review: verify redaction in logs, restore global flag, fix doc comment Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .../register_tools_handler_redaction_test.go | 97 ++++++++++++++++--- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/internal/server/register_tools_handler_redaction_test.go b/internal/server/register_tools_handler_redaction_test.go index 8488c44d..bc1464f3 100644 --- a/internal/server/register_tools_handler_redaction_test.go +++ b/internal/server/register_tools_handler_redaction_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strings" "testing" "github.com/github/gh-aw-mcpg/internal/config" @@ -15,14 +16,29 @@ import ( "github.com/stretchr/testify/require" ) +// toolCallLogLines returns only the log lines emitted by the +// registerToolsFromBackendContext handler closure itself (the "MCP tool +// call ..." lines), filtering out unrelated lines logged by other +// middleware (e.g. the payload jq middleware also logs the raw backend +// error at debug level for its own bookkeeping). This keeps assertions +// about redaction focused on the behavior under test. +func toolCallLogLines(logOutput string) string { + var kept []string + for _, line := range strings.Split(logOutput, "\n") { + if strings.Contains(line, "MCP tool call") || strings.Contains(line, "Failed to unmarshal tool arguments") { + kept = append(kept, line) + } + } + return strings.Join(kept, "\n") +} + // TestRegisterToolsFromBackend_HandlerInvocation_Redaction exercises the -// argument-parse-error, request-log, requireSession-failure, and -// response-log branches of the tool handler closure created inside -// registerToolsFromBackendContext (internal/server/tool_registry.go), under -// both the normal (non-redacted) and enclave-session (redacted) code paths. -// These branches are otherwise only reached indirectly via the MCP -// transport in integration tests and were previously uncovered by unit -// tests. +// argument-parse-error, request-log, and response-log branches of the tool +// handler closure created inside registerToolsFromBackendContext +// (internal/server/tool_registry.go), under both the normal (non-redacted) +// and enclave-session (redacted) code paths. These branches are otherwise +// only reached indirectly via the MCP transport in integration tests and +// were previously uncovered by unit tests. func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req map[string]interface{} @@ -118,11 +134,19 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { Arguments: json.RawMessage(`{not valid json`), }, } - result, data, err := echoTool.Handler(ctx, req, nil) + var result *sdk.CallToolResult + var data interface{} + var err error + logOutput := captureServerLog(t, func() { + result, data, err = echoTool.Handler(ctx, req, nil) + }) + handlerLog := toolCallLogLines(logOutput) require.Error(err) require.NotNil(result) assert.True(result.IsError) assert.Nil(data) + assert.NotContains(handlerLog, "looking for value", "raw parse-error text must not appear in an enclave-session log") + assert.Contains(handlerLog, "error hmac:", "the redacted error digest marker must be present") }) t.Run("valid arguments under enclave session take the redacted request/response log branches", func(t *testing.T) { @@ -133,14 +157,23 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { req := &sdk.CallToolRequest{ Params: &sdk.CallToolParamsRaw{ Name: "redaction-backend___echo", - Arguments: json.RawMessage(`{"message":"hi"}`), + Arguments: json.RawMessage(`{"message":"top-secret-request-payload"}`), }, } - result, data, err := echoTool.Handler(ctx, req, nil) + var result *sdk.CallToolResult + var data interface{} + var err error + logOutput := captureServerLog(t, func() { + result, data, err = echoTool.Handler(ctx, req, nil) + }) + handlerLog := toolCallLogLines(logOutput) require.NoError(err) require.NotNil(result) assert.False(result.IsError) assert.NotNil(data) + assert.NotContains(handlerLog, "top-secret-request-payload", "raw enclave request payload must not appear in the log") + assert.NotContains(handlerLog, "echoed", "raw enclave response payload must not appear in the log") + assert.Contains(handlerLog, "[REDACTED enclave payload", "the redacted payload marker must be present") }) t.Run("backend error under enclave session takes the redacted error-log branch", func(t *testing.T) { @@ -154,11 +187,19 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { Arguments: json.RawMessage(`{}`), }, } - result, data, err := boomTool.Handler(ctx, req, nil) + var result *sdk.CallToolResult + var data interface{} + var err error + logOutput := captureServerLog(t, func() { + result, data, err = boomTool.Handler(ctx, req, nil) + }) + handlerLog := toolCallLogLines(logOutput) require.Error(err) require.NotNil(result) assert.True(result.IsError) assert.Nil(data) + assert.NotContains(handlerLog, "simulated backend failure", "raw backend error text must not appear in an enclave-session log") + assert.Contains(handlerLog, "error hmac:", "the redacted error digest marker must be present") }) t.Run("backend error without enclave session takes the non-redacted error-log branch", func(t *testing.T) { @@ -172,32 +213,49 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { Arguments: json.RawMessage(`{}`), }, } - result, data, err := boomTool.Handler(ctx, req, nil) + var result *sdk.CallToolResult + var data interface{} + var err error + logOutput := captureServerLog(t, func() { + result, data, err = boomTool.Handler(ctx, req, nil) + }) + handlerLog := toolCallLogLines(logOutput) require.Error(err) require.NotNil(result) assert.True(result.IsError) assert.Nil(data) + assert.Contains(handlerLog, "simulated backend failure", "non-enclave sessions must log the raw backend error text") }) t.Run("global payload redaction flag also takes the redacted response-log branch", func(t *testing.T) { assert := assert.New(t) require := require.New(t) + previousRedaction := sanitize.PayloadRedactionEnabled() sanitize.SetPayloadRedaction(true) - t.Cleanup(func() { sanitize.SetPayloadRedaction(false) }) + t.Cleanup(func() { sanitize.SetPayloadRedaction(previousRedaction) }) ctx := context.WithValue(context.Background(), SessionIDContextKey, "global-redaction-session") req := &sdk.CallToolRequest{ Params: &sdk.CallToolParamsRaw{ Name: "redaction-backend___echo", - Arguments: json.RawMessage(`{"message":"hi"}`), + Arguments: json.RawMessage(`{"message":"top-secret-global-payload"}`), }, } - result, data, err := echoTool.Handler(ctx, req, nil) + var result *sdk.CallToolResult + var data interface{} + var err error + logOutput := captureServerLog(t, func() { + result, data, err = echoTool.Handler(ctx, req, nil) + }) + handlerLog := toolCallLogLines(logOutput) require.NoError(err) require.NotNil(result) assert.False(result.IsError) assert.NotNil(data) + assert.NotContains(handlerLog, "top-secret-global-payload", "raw request payload must not appear in the log when the global redaction flag is set") + assert.NotContains(handlerLog, "echoed", "raw response payload must not appear in the log when the global redaction flag is set") + assert.Contains(handlerLog, "[REDACTED enclave payload", "the redacted payload marker must be present") }) } @@ -282,8 +340,15 @@ func TestRegisterToolsFromBackend_ToolResponseFilterWrapsHandler(t *testing.T) { Arguments: json.RawMessage(`{}`), }, } - result, _, err := tool.Handler(ctx, req, nil) + result, data, err := tool.Handler(ctx, req, nil) require.NoError(t, err) require.NotNil(t, result) assert.False(t, result.IsError) + + // The jq filter ".items" must replace the raw backend result (whose + // "content" text was "raw result") with just the "items" array. + dataJSON, marshalErr := json.Marshal(data) + require.NoError(t, marshalErr) + assert.JSONEq(t, `["a","b","c"]`, string(dataJSON)) + assert.NotContains(t, string(dataJSON), "raw result", "the unfiltered backend content must not survive the jq filter") } From 9be1edbd1a72020937cb56a2e85f2b7ae948a692 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:04:43 +0000 Subject: [PATCH 3/4] Use unique sentinel text and drop redundant assertion in filter test Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/server/register_tools_handler_redaction_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/server/register_tools_handler_redaction_test.go b/internal/server/register_tools_handler_redaction_test.go index bc1464f3..e8f777e9 100644 --- a/internal/server/register_tools_handler_redaction_test.go +++ b/internal/server/register_tools_handler_redaction_test.go @@ -92,7 +92,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { _ = json.NewEncoder(w).Encode(map[string]interface{}{ "jsonrpc": "2.0", "id": req["id"], "result": map[string]interface{}{ - "content": []map[string]interface{}{{"type": "text", "text": "echoed"}}, + "content": []map[string]interface{}{{"type": "text", "text": "echoed-response-sentinel-value"}}, }, }) } @@ -172,7 +172,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { assert.False(result.IsError) assert.NotNil(data) assert.NotContains(handlerLog, "top-secret-request-payload", "raw enclave request payload must not appear in the log") - assert.NotContains(handlerLog, "echoed", "raw enclave response payload must not appear in the log") + assert.NotContains(handlerLog, "echoed-response-sentinel-value", "raw enclave response payload must not appear in the log") assert.Contains(handlerLog, "[REDACTED enclave payload", "the redacted payload marker must be present") }) @@ -254,7 +254,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { assert.False(result.IsError) assert.NotNil(data) assert.NotContains(handlerLog, "top-secret-global-payload", "raw request payload must not appear in the log when the global redaction flag is set") - assert.NotContains(handlerLog, "echoed", "raw response payload must not appear in the log when the global redaction flag is set") + assert.NotContains(handlerLog, "echoed-response-sentinel-value", "raw response payload must not appear in the log when the global redaction flag is set") assert.Contains(handlerLog, "[REDACTED enclave payload", "the redacted payload marker must be present") }) } @@ -350,5 +350,4 @@ func TestRegisterToolsFromBackend_ToolResponseFilterWrapsHandler(t *testing.T) { dataJSON, marshalErr := json.Marshal(data) require.NoError(t, marshalErr) assert.JSONEq(t, `["a","b","c"]`, string(dataJSON)) - assert.NotContains(t, string(dataJSON), "raw result", "the unfiltered backend content must not survive the jq filter") } From a83e32baf35e882f9f48a4e4166e8e3db79f6b78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:05:40 +0000 Subject: [PATCH 4/4] Guard log-line filter against message-text drift with require.NotEmpty Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .../server/register_tools_handler_redaction_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/server/register_tools_handler_redaction_test.go b/internal/server/register_tools_handler_redaction_test.go index e8f777e9..e40f2cca 100644 --- a/internal/server/register_tools_handler_redaction_test.go +++ b/internal/server/register_tools_handler_redaction_test.go @@ -21,7 +21,10 @@ import ( // call ..." lines), filtering out unrelated lines logged by other // middleware (e.g. the payload jq middleware also logs the raw backend // error at debug level for its own bookkeeping). This keeps assertions -// about redaction focused on the behavior under test. +// about redaction focused on the behavior under test. Callers must pair +// this with a require.NotEmpty check on the result so a wording change to +// the filtered log messages fails the test loudly instead of silently +// matching zero lines. func toolCallLogLines(logOutput string) string { var kept []string for _, line := range strings.Split(logOutput, "\n") { @@ -141,6 +144,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { result, data, err = echoTool.Handler(ctx, req, nil) }) handlerLog := toolCallLogLines(logOutput) + require.NotEmpty(handlerLog, "expected at least one tool-call log line; log message text may have drifted from toolCallLogLines' filter") require.Error(err) require.NotNil(result) assert.True(result.IsError) @@ -167,6 +171,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { result, data, err = echoTool.Handler(ctx, req, nil) }) handlerLog := toolCallLogLines(logOutput) + require.NotEmpty(handlerLog, "expected at least one tool-call log line; log message text may have drifted from toolCallLogLines' filter") require.NoError(err) require.NotNil(result) assert.False(result.IsError) @@ -194,6 +199,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { result, data, err = boomTool.Handler(ctx, req, nil) }) handlerLog := toolCallLogLines(logOutput) + require.NotEmpty(handlerLog, "expected at least one tool-call log line; log message text may have drifted from toolCallLogLines' filter") require.Error(err) require.NotNil(result) assert.True(result.IsError) @@ -220,6 +226,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { result, data, err = boomTool.Handler(ctx, req, nil) }) handlerLog := toolCallLogLines(logOutput) + require.NotEmpty(handlerLog, "expected at least one tool-call log line; log message text may have drifted from toolCallLogLines' filter") require.Error(err) require.NotNil(result) assert.True(result.IsError) @@ -249,6 +256,7 @@ func TestRegisterToolsFromBackend_HandlerInvocation_Redaction(t *testing.T) { result, data, err = echoTool.Handler(ctx, req, nil) }) handlerLog := toolCallLogLines(logOutput) + require.NotEmpty(handlerLog, "expected at least one tool-call log line; log message text may have drifted from toolCallLogLines' filter") require.NoError(err) require.NotNil(result) assert.False(result.IsError)