Skip to content

Commit ee0a2dd

Browse files
authored
test(mcp): add mutex for concurrent access to pathHeaders in McpHeadersSuite (#486)
Signed-off-by: Marc Nuri <marc@marcnuri.com>
1 parent 9564e34 commit ee0a2dd

File tree

1 file changed

+34
-13
lines changed

1 file changed

+34
-13
lines changed

pkg/mcp/mcp_test.go

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mcp
22

33
import (
44
"net/http"
5+
"sync"
56
"testing"
67

78
"github.com/containers/kubernetes-mcp-server/internal/test"
@@ -11,8 +12,9 @@ import (
1112

1213
type McpHeadersSuite struct {
1314
BaseMcpSuite
14-
mockServer *test.MockServer
15-
pathHeaders map[string]http.Header
15+
mockServer *test.MockServer
16+
pathHeaders map[string]http.Header
17+
pathHeadersMux sync.Mutex
1618
}
1719

1820
func (s *McpHeadersSuite) SetupTest() {
@@ -21,7 +23,9 @@ func (s *McpHeadersSuite) SetupTest() {
2123
s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T())
2224
s.pathHeaders = make(map[string]http.Header)
2325
s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
26+
s.pathHeadersMux.Lock()
2427
s.pathHeaders[req.URL.Path] = req.Header.Clone()
28+
s.pathHeadersMux.Unlock()
2529
}))
2630
s.mockServer.Handle(&test.DiscoveryClientHandler{})
2731
s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@@ -51,23 +55,40 @@ func (s *McpHeadersSuite) TestAuthorizationHeaderPropagation() {
5155
for _, header := range cases {
5256
s.InitMcpClient(transport.WithHTTPHeaders(map[string]string{header: "Bearer a-token-from-mcp-client"}))
5357
_, _ = s.CallTool("pods_list", map[string]interface{}{})
54-
s.Require().Greater(len(s.pathHeaders), 0, "No requests were made to Kube API")
58+
s.pathHeadersMux.Lock()
59+
pathHeadersLen := len(s.pathHeaders)
60+
s.pathHeadersMux.Unlock()
61+
s.Require().Greater(pathHeadersLen, 0, "No requests were made to Kube API")
5562
s.Run("DiscoveryClient propagates "+header+" header to Kube API", func() {
56-
s.Require().NotNil(s.pathHeaders["/api"], "No requests were made to /api")
57-
s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api"].Get("Authorization"), "Overridden header Authorization not found in request to /api")
58-
s.Require().NotNil(s.pathHeaders["/apis"], "No requests were made to /apis")
59-
s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/apis"].Get("Authorization"), "Overridden header Authorization not found in request to /apis")
60-
s.Require().NotNil(s.pathHeaders["/api/v1"], "No requests were made to /api/v1")
61-
s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api/v1"].Get("Authorization"), "Overridden header Authorization not found in request to /api/v1")
63+
s.pathHeadersMux.Lock()
64+
apiHeaders := s.pathHeaders["/api"]
65+
apisHeaders := s.pathHeaders["/apis"]
66+
apiV1Headers := s.pathHeaders["/api/v1"]
67+
s.pathHeadersMux.Unlock()
68+
69+
s.Require().NotNil(apiHeaders, "No requests were made to /api")
70+
s.Equal("Bearer a-token-from-mcp-client", apiHeaders.Get("Authorization"), "Overridden header Authorization not found in request to /api")
71+
s.Require().NotNil(apisHeaders, "No requests were made to /apis")
72+
s.Equal("Bearer a-token-from-mcp-client", apisHeaders.Get("Authorization"), "Overridden header Authorization not found in request to /apis")
73+
s.Require().NotNil(apiV1Headers, "No requests were made to /api/v1")
74+
s.Equal("Bearer a-token-from-mcp-client", apiV1Headers.Get("Authorization"), "Overridden header Authorization not found in request to /api/v1")
6275
})
6376
s.Run("DynamicClient propagates "+header+" header to Kube API", func() {
64-
s.Require().NotNil(s.pathHeaders["/api/v1/namespaces/default/pods"], "No requests were made to /api/v1/namespaces/default/pods")
65-
s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api/v1/namespaces/default/pods"].Get("Authorization"), "Overridden header Authorization not found in request to /api/v1/namespaces/default/pods")
77+
s.pathHeadersMux.Lock()
78+
podsHeaders := s.pathHeaders["/api/v1/namespaces/default/pods"]
79+
s.pathHeadersMux.Unlock()
80+
81+
s.Require().NotNil(podsHeaders, "No requests were made to /api/v1/namespaces/default/pods")
82+
s.Equal("Bearer a-token-from-mcp-client", podsHeaders.Get("Authorization"), "Overridden header Authorization not found in request to /api/v1/namespaces/default/pods")
6683
})
6784
_, _ = s.CallTool("pods_delete", map[string]interface{}{"name": "a-pod-to-delete"})
6885
s.Run("kubernetes.Interface propagates "+header+" header to Kube API", func() {
69-
s.Require().NotNil(s.pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"], "No requests were made to /api/v1/namespaces/default/pods/a-pod-to-delete")
70-
s.Equal("Bearer a-token-from-mcp-client", s.pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"].Get("Authorization"), "Overridden header Authorization not found in request to /api/v1/namespaces/default/pods/a-pod-to-delete")
86+
s.pathHeadersMux.Lock()
87+
podDeleteHeaders := s.pathHeaders["/api/v1/namespaces/default/pods/a-pod-to-delete"]
88+
s.pathHeadersMux.Unlock()
89+
90+
s.Require().NotNil(podDeleteHeaders, "No requests were made to /api/v1/namespaces/default/pods/a-pod-to-delete")
91+
s.Equal("Bearer a-token-from-mcp-client", podDeleteHeaders.Get("Authorization"), "Overridden header Authorization not found in request to /api/v1/namespaces/default/pods/a-pod-to-delete")
7192
})
7293

7394
}

0 commit comments

Comments
 (0)