Skip to content

[test] Add tests for middleware.tryApplyToolResponseFilter, rewriteFilteredTextPayload, and rewriteEnvelopeTextPayload#5696

Merged
lpcox merged 2 commits into
mainfrom
add-filter-rewrite-tests-fff1274e9acc5e00
May 14, 2026
Merged

[test] Add tests for middleware.tryApplyToolResponseFilter, rewriteFilteredTextPayload, and rewriteEnvelopeTextPayload#5696
lpcox merged 2 commits into
mainfrom
add-filter-rewrite-tests-fff1274e9acc5e00

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

Test Coverage Improvement: jqschema filter/rewrite functions

Functions Analyzed

  • Package: internal/middleware
  • Functions: tryApplyToolResponseFilter, rewriteFilteredTextPayload, rewriteEnvelopeTextPayload
  • Previous Coverage: Indirect only (exercised via WrapToolHandler integration tests)
  • New Coverage: All branches directly tested
  • Complexity: High — tryApplyToolResponseFilter has 8+ conditional branches; rewriteEnvelopeTextPayload has a 3-level type-switch with 7 branches

Why These Functions?

These three internal functions in internal/middleware/jqschema.go implement the core tool-response filter and payload-rewrite pipeline. Together they have:

  • tryApplyToolResponseFilter (63 lines): 8 distinct code paths — nil filter, TextContent vs. non-TextContent, JSON parse failure, filter error, marshal error, envelope rewrite, ConvertToCallToolResult error, trailing-content preservation
  • rewriteEnvelopeTextPayload (49 lines): 3-level type switch with 7 branches across []map[string]interface{} and []interface{} content layouts
  • rewriteFilteredTextPayload (25 lines): 3 data-return paths

All were previously reached only through the much heavier WrapToolHandler path, meaning many branches (especially all the error/fallback paths) were not reliably covered.

Tests Added

  • ✅ Happy path test cases (filter succeeds, rewrites result)
  • ✅ Edge cases (empty content, nil filterCode, non-JSON text)
  • ✅ All error/fallback paths (filter error, marshal error, ConvertToCallToolResult error)
  • ✅ All type-switch branches in rewriteEnvelopeTextPayload
  • ✅ Immutability: original slices/maps not mutated
  • ✅ Metadata propagation: IsError and Meta preserved
  • ✅ Trailing content items preserved where applicable

Test Cases Summary

Function Cases
rewriteEnvelopeTextPayload 10
rewriteFilteredTextPayload 5
tryApplyToolResponseFilter 13
Total 28

Generated by Test Coverage Improver

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

Generated by Test Coverage Improver · ● 3.9M ·

…edTextPayload, and rewriteEnvelopeTextPayload

These three internal functions in internal/middleware/jqschema.go had many
branches that were previously covered only indirectly through WrapToolHandler
integration tests.  The new white-box test file directly exercises every branch:

rewriteEnvelopeTextPayload (10 cases):
- non-map data, map without content key
- []map content: empty, non-empty (rewrites first item, immutability)
- []interface{} content: empty, first-item-not-a-map, first-item-is-map (rewrites + immutability)
- unrecognised content type

rewriteFilteredTextPayload (5 cases):
- single content item with successful envelope rewrite
- multiple content items (trailing items preserved)
- envelope rewrite fails, valid JSON filteredText
- envelope rewrite fails, invalid JSON filteredText (original data fallback)
- IsError/Meta propagation

tryApplyToolResponseFilter (13 cases):
- nil filterCode (early return)
- empty Content slice (falls through to data-level filter)
- non-TextContent first item (falls through to data-level filter)
- TextContent with non-JSON text (falls through to data-level filter)
- TextContent filter error (returns original result/data)
- TextContent filter succeeds (rewrites result)
- TextContent trailing items preserved after text-level rewrite
- data-level filter error (returns original result/data)
- ConvertToCallToolResult error (returns original result/data)
- data-level success with trailing items appended
- data-level success single item (no trailing appended)
- IsError propagated for data-level success

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lpcox lpcox marked this pull request as ready for review May 14, 2026 17:39
Copilot AI review requested due to automatic review settings May 14, 2026 17:39
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented May 14, 2026

@copilot fix linting errors

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds direct white-box tests for middleware response filtering and payload rewrite helpers in internal/middleware, improving coverage around jq filter application, envelope rewriting, fallback paths, and content preservation.

Changes:

  • Adds tests for rewriteEnvelopeTextPayload across supported and unsupported envelope shapes.
  • Adds tests for rewriteFilteredTextPayload rewrite/fallback behavior.
  • Adds tests for tryApplyToolResponseFilter success, fallback, and error paths.
Show a summary per file
File Description
internal/middleware/filter_rewrite_test.go New direct unit tests for internal middleware filter/rewrite helpers.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 1/1 changed files
  • Comments generated: 9

Comment on lines +228 to +234
data := []interface{}{}
result := &sdk.CallToolResult{
Content: []sdk.Content{&sdk.TextContent{Text: `"x"`}},
IsError: true,
}
got, _ := rewriteFilteredTextPayload(result, data, `"y"`)
assert.True(t, got.IsError)
Comment on lines +256 to +288
code, err := CompileToolResponseFilter(".")
require.NoError(t, err)
result := &sdk.CallToolResult{Content: []sdk.Content{}}
data := map[string]interface{}{"content": []interface{}{
map[string]interface{}{"type": "text", "text": "hello"},
}}

gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q3")
require.NotNil(t, gotResult)
})

t.Run("non-TextContent first item falls through to data-level filter", func(t *testing.T) {
code, err := CompileToolResponseFilter(".")
require.NoError(t, err)
result := makeNonTextResult()
data := map[string]interface{}{"content": []interface{}{
map[string]interface{}{"type": "text", "text": "hello"},
}}

gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q4")
require.NotNil(t, gotResult)
})

t.Run("TextContent with non-JSON text falls through to data-level filter", func(t *testing.T) {
code, err := CompileToolResponseFilter(".")
require.NoError(t, err)
result := makeTextResult("not valid json")
data := map[string]interface{}{"content": []interface{}{
map[string]interface{}{"type": "text", "text": "hello"},
}}

gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q5")
require.NotNil(t, gotResult)
// tryApplyToolResponseFilter
// ---------------------------------------------------------------------------

func TestTryApplyToolResponseFilter(t *testing.T) {
Comment on lines +418 to +431
result := &sdk.CallToolResult{
Content: []sdk.Content{&sdk.ImageContent{Data: "x", MIMEType: "image/png"}},
IsError: true,
}
data := map[string]interface{}{
"content": []interface{}{
map[string]interface{}{"type": "text", "text": "hi"},
},
}

gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q13")

require.NotNil(t, gotResult)
assert.True(t, gotResult.IsError)
Comment on lines +275 to +276
gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q4")
require.NotNil(t, gotResult)
Comment on lines +280 to +288
code, err := CompileToolResponseFilter(".")
require.NoError(t, err)
result := makeTextResult("not valid json")
data := map[string]interface{}{"content": []interface{}{
map[string]interface{}{"type": "text", "text": "hello"},
}}

gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q5")
require.NotNil(t, gotResult)
Comment on lines +388 to +394
gotResult, _ := tryApplyToolResponseFilter(ctx, code, result, data, "tool", "q11")

require.NotNil(t, gotResult)
// trailing item must be appended because result.Content[0] is TextContent
// and len(result.Content) > 1.
assert.Greater(t, len(gotResult.Content), 1, "trailing content item must be appended")
assert.Equal(t, trailing, gotResult.Content[len(gotResult.Content)-1])
Comment on lines +411 to +412
// Only one content item in original → no extra appended after filter
assert.GreaterOrEqual(t, len(gotResult.Content), 1)
Comment on lines +335 to +337
// The trailing TextContent should be preserved
require.GreaterOrEqual(t, len(gotResult.Content), 2,
"trailing content item must be preserved")
Copy link
Copy Markdown
Contributor

Copilot AI commented May 14, 2026

@copilot fix linting errors

Fixed in cbb9bb1. I updated the new tests to use []byte(...) for sdk.ImageContent.Data, which resolves the lint/vet failure.

Copilot finished work on behalf of lpcox May 14, 2026 17:45
Copilot AI requested a review from lpcox May 14, 2026 17:45
@lpcox lpcox merged commit ff04629 into main May 14, 2026
3 checks passed
@lpcox lpcox deleted the add-filter-rewrite-tests-fff1274e9acc5e00 branch May 14, 2026 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants