Skip to content

[test-improver] Improve tests for proxy package#2230

Merged
lpcox merged 1 commit intomainfrom
test-improver/proxy-test-coverage-e87e4d81e8faff98
Mar 21, 2026
Merged

[test-improver] Improve tests for proxy package#2230
lpcox merged 1 commit intomainfrom
test-improver/proxy-test-coverage-e87e4d81e8faff98

Conversation

@github-actions
Copy link
Contributor

File Analyzed

  • Test File: internal/proxy/proxy_test.go
  • Package: internal/proxy
  • Lines of Code: 312 → 509 (+197 lines)

Improvements Made

1. Increased Coverage — Missing Routes in TestMatchRoute

Eight routes defined in router.go had no test cases:

Path pattern Tool Status
/pulls/{n}/comments pull_request_read (get_review_comments) ✅ Added
/git/ref/tags/{tag} get_tag ✅ Added
/git/trees/{path} get_file_contents ✅ Added
/labels (list) list_labels ✅ Added
/actions/workflows actions_list (list_workflows) ✅ Added
/actions/runs actions_list (list_workflow_runs) ✅ Added
/search/repositories search_repositories ✅ Added
/repos/{o}/{r}/unknown get_file_contents (generic fallback) ✅ Added

2. Increased Coverage — Missing GraphQL Patterns in TestMatchGraphQL

Two GraphQL patterns in graphql.go had no test cases:

  • projectV2 query → list_projects
  • ✅ Generic repository(...) info query (no issues/PRs) → get_file_contents

3. New Test Functions for Untested Helper Functions

writeEmptyResponse and copyResponseHeaders in handler.go had zero test coverage:

  • TestWriteEmptyResponse: covers all four branches of the empty-response sentinel logic

    • []interface{}"[]"
    • map[string]interface{} with "data" key (GraphQL) → {"data":null}
    • map[string]interface{} without "data" key (REST) → "{}"
    • nil/unknown → "[]" (safe default)
    • Verifies upstream HTTP status code is preserved
  • TestCopyResponseHeaders: four subtests covering

    • All five rate-limit headers are copied
    • Link (pagination) and X-GitHub-Request-Id are copied
    • Absent headers produce no output
    • Unrelated headers (Content-Type, Authorization, custom) are never copied

4. Better Imports

Added "io", "net/http", and "net/http/httptest" to support the new HTTP-level tests, enabling direct unit testing of response-writing helpers without a live server.

Why These Changes?

proxy_test.go only tested the four pure routing functions (MatchRoute, StripGHHostPrefix, MatchGraphQL, IsGraphQLPath) but left 8 of 27 route entries, 2 of 7 GraphQL patterns, and both response-writing helpers completely untested. The proxy enforces DIFC security policies by routing requests to the correct guard tool — an incorrect route match silently bypasses enforcement, making routing correctness especially important to verify.


Generated by Test Improver Workflow
Focuses on better patterns, increased coverage, and more stable tests

Generated by Test Improver ·

…elpers

Add test cases to proxy_test.go that cover routes and functions defined
in router.go, graphql.go, and handler.go but not previously tested:

Routes added to TestMatchRoute:
- PR review comments (/pulls/{n}/comments → get_review_comments)
- Tag lookup via git ref (/git/ref/tags/{tag} → get_tag)
- Git tree contents (/git/trees/{path} → get_file_contents)
- Label listing (/labels → list_labels)
- GitHub Actions workflows and runs (actions_list)
- Repository search (/search/repositories)
- Generic repo-scoped fallback route

GraphQL patterns added to TestMatchGraphQL:
- projectV2 query → list_projects
- Generic repository info query → get_file_contents

New test functions:
- TestWriteEmptyResponse: covers all four branches of the empty
  response sentinel logic (array, GraphQL data object, plain object,
  nil/unknown) and verifies the upstream status code is preserved
- TestCopyResponseHeaders: verifies allowed headers (rate-limit,
  pagination, request ID) are copied and unrelated headers are not

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lpcox lpcox marked this pull request as ready for review March 21, 2026 15:23
Copilot AI review requested due to automatic review settings March 21, 2026 15:23
@lpcox lpcox merged commit feb1d54 into main Mar 21, 2026
3 checks passed
@lpcox lpcox deleted the test-improver/proxy-test-coverage-e87e4d81e8faff98 branch March 21, 2026 15:24
Copy link
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

Expands test coverage for the internal/proxy package to better validate REST/GraphQL routing and response helper behavior, reducing the risk of enforcement bypass due to incorrect route matching or response handling.

Changes:

  • Adds missing MatchRoute test cases for previously untested REST route patterns (including actions, labels list, tags via git ref, PR review comments, repo search, and generic repo fallback).
  • Adds missing MatchGraphQL test cases for projectV2 and generic repository(...) patterns.
  • Introduces new unit tests for writeEmptyResponse and copyResponseHeaders using httptest.

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

Comment on lines +462 to +493
"X-Ratelimit-Limit": []string{"60"},
"X-Ratelimit-Remaining": []string{"58"},
"X-Ratelimit-Reset": []string{"1609459200"},
"X-Ratelimit-Resource": []string{"core"},
"X-Ratelimit-Used": []string{"2"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, "60", w.Header().Get("X-Ratelimit-Limit"))
assert.Equal(t, "58", w.Header().Get("X-Ratelimit-Remaining"))
assert.Equal(t, "1609459200", w.Header().Get("X-Ratelimit-Reset"))
assert.Equal(t, "core", w.Header().Get("X-Ratelimit-Resource"))
assert.Equal(t, "2", w.Header().Get("X-Ratelimit-Used"))
})

t.Run("copies pagination and request ID headers", func(t *testing.T) {
w := httptest.NewRecorder()
resp := &http.Response{Header: http.Header{
"Link": []string{`<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`},
"X-Github-Request-Id": []string{"abc-123"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, `<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`, w.Header().Get("Link"))
assert.Equal(t, "abc-123", w.Header().Get("X-Github-Request-Id"))
})

t.Run("absent headers are not written", func(t *testing.T) {
w := httptest.NewRecorder()
resp := &http.Response{Header: make(http.Header)}
copyResponseHeaders(w, resp)
assert.Empty(t, w.Header().Get("X-Ratelimit-Limit"))
assert.Empty(t, w.Header().Get("Link"))
assert.Empty(t, w.Header().Get("X-Github-Request-Id"))
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

In this test the upstream headers are set/asserted using canonicalized keys like X-Ratelimit-*, while copyResponseHeaders uses X-RateLimit-*. Go’s http.Header canonicalization makes this pass, but it’s easy to misread and can become brittle if the implementation ever switches from Header.Get to direct map access. Consider using the same header names as in copyResponseHeaders in both the input resp.Header and assertions for clarity.

Suggested change
"X-Ratelimit-Limit": []string{"60"},
"X-Ratelimit-Remaining": []string{"58"},
"X-Ratelimit-Reset": []string{"1609459200"},
"X-Ratelimit-Resource": []string{"core"},
"X-Ratelimit-Used": []string{"2"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, "60", w.Header().Get("X-Ratelimit-Limit"))
assert.Equal(t, "58", w.Header().Get("X-Ratelimit-Remaining"))
assert.Equal(t, "1609459200", w.Header().Get("X-Ratelimit-Reset"))
assert.Equal(t, "core", w.Header().Get("X-Ratelimit-Resource"))
assert.Equal(t, "2", w.Header().Get("X-Ratelimit-Used"))
})
t.Run("copies pagination and request ID headers", func(t *testing.T) {
w := httptest.NewRecorder()
resp := &http.Response{Header: http.Header{
"Link": []string{`<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`},
"X-Github-Request-Id": []string{"abc-123"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, `<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`, w.Header().Get("Link"))
assert.Equal(t, "abc-123", w.Header().Get("X-Github-Request-Id"))
})
t.Run("absent headers are not written", func(t *testing.T) {
w := httptest.NewRecorder()
resp := &http.Response{Header: make(http.Header)}
copyResponseHeaders(w, resp)
assert.Empty(t, w.Header().Get("X-Ratelimit-Limit"))
assert.Empty(t, w.Header().Get("Link"))
assert.Empty(t, w.Header().Get("X-Github-Request-Id"))
"X-RateLimit-Limit": []string{"60"},
"X-RateLimit-Remaining": []string{"58"},
"X-RateLimit-Reset": []string{"1609459200"},
"X-RateLimit-Resource": []string{"core"},
"X-RateLimit-Used": []string{"2"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, "60", w.Header().Get("X-RateLimit-Limit"))
assert.Equal(t, "58", w.Header().Get("X-RateLimit-Remaining"))
assert.Equal(t, "1609459200", w.Header().Get("X-RateLimit-Reset"))
assert.Equal(t, "core", w.Header().Get("X-RateLimit-Resource"))
assert.Equal(t, "2", w.Header().Get("X-RateLimit-Used"))
})
t.Run("copies pagination and request ID headers", func(t *testing.T) {
w := httptest.NewRecorder()
resp := &http.Response{Header: http.Header{
"Link": []string{`<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`},
"X-GitHub-Request-Id": []string{"abc-123"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, `<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`, w.Header().Get("Link"))
assert.Equal(t, "abc-123", w.Header().Get("X-GitHub-Request-Id"))
})
t.Run("absent headers are not written", func(t *testing.T) {
w := httptest.NewRecorder()
resp := &http.Response{Header: make(http.Header)}
copyResponseHeaders(w, resp)
assert.Empty(t, w.Header().Get("X-RateLimit-Limit"))
assert.Empty(t, w.Header().Get("Link"))
assert.Empty(t, w.Header().Get("X-GitHub-Request-Id"))

Copilot uses AI. Check for mistakes.
Comment on lines +478 to +485
resp := &http.Response{Header: http.Header{
"Link": []string{`<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`},
"X-Github-Request-Id": []string{"abc-123"},
}}
copyResponseHeaders(w, resp)
assert.Equal(t, `<https://api.github.com/repos/o/r/issues?page=2>; rel="next"`, w.Header().Get("Link"))
assert.Equal(t, "abc-123", w.Header().Get("X-Github-Request-Id"))
})
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

Similar to the rate-limit headers, this subtest uses X-Github-Request-Id while the production copier uses X-GitHub-Request-Id. It works due to header key canonicalization, but aligning the test’s header names with the production list would make intent clearer and reduce brittleness if the implementation changes.

Copilot uses AI. Check for mistakes.
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.

2 participants