返回 DeepSeek-Reasonix
auth_test.go
根目录 / internal / mcpdiag / auth_test.go
1 package mcpdiag
2
3 import "testing"
4
5 func TestDiagnoseAuthRequiredFromFailure(t *testing.T) {
6 got := DiagnoseAuth("http", "failed", "connect: 401 unauthorized", "https://mcp.example.com/mcp", false)
7 if got.Status != AuthRequired {
8 t.Fatalf("status = %q, want %q", got.Status, AuthRequired)
9 }
10 if got.URL != "https://mcp.example.com/mcp" {
11 t.Fatalf("url = %q", got.URL)
12 }
13 }
14
15 func TestDiagnoseAuthPossibleForDeferredHTTPWithoutAuthConfig(t *testing.T) {
16 got := DiagnoseAuth("streamable-http", "deferred", "", "https://mcp.example.com/mcp", false)
17 if got.Status != AuthPossible {
18 t.Fatalf("status = %q, want %q", got.Status, AuthPossible)
19 }
20 if got.URL == "" {
21 t.Fatal("possible remote auth should keep the server URL")
22 }
23 }
24
25 func TestDiagnoseAuthRejectsIneligibleNativeOAuth(t *testing.T) {
26 for _, tc := range []struct {
27 name string
28 transport string
29 url string
30 authConfigured bool
31 }{
32 {name: "stdio", transport: "stdio"},
33 {name: "legacy sse", transport: "sse", url: "https://mcp.example.com/sse"},
34 {name: "static auth", transport: "http", url: "https://mcp.example.com/mcp", authConfigured: true},
35 {name: "invalid url", transport: "http", url: "not-a-url"},
36 } {
37 t.Run(tc.name, func(t *testing.T) {
38 got := DiagnoseAuth(tc.transport, "failed", "authentication required", tc.url, tc.authConfigured)
39 if got.Status != AuthNone || got.URL != "" {
40 t.Fatalf("diagnosis = %+v, want no native OAuth action", got)
41 }
42 })
43 }
44 }
45
46 func TestHasAuthConfig(t *testing.T) {
47 if !HasAuthConfig(map[string]string{"Authorization": "Bearer ${TOKEN}"}, nil, "") {
48 t.Fatal("authorization header should count as auth config")
49 }
50 if !HasAuthConfig(nil, map[string]string{"DIDA_TOKEN": "${DIDA_TOKEN}"}, "") {
51 t.Fatal("auth-like env key should count as auth config")
52 }
53 if HasAuthConfig(nil, map[string]string{"DEBUG": "1"}, "https://mcp.example.com/mcp") {
54 t.Fatal("unrelated env should not count as auth config")
55 }
56 for _, tc := range []struct {
57 name string
58 headers map[string]string
59 url string
60 }{
61 {name: "url userinfo", url: "https://user:pass@mcp.example.com/mcp"},
62 {name: "signed query", url: "https://mcp.example.com/mcp?sig=abc"},
63 {name: "api key query", url: "https://mcp.example.com/mcp?key=abc"},
64 {name: "subscription header", headers: map[string]string{"X-Subscription-Key": "abc"}, url: "https://mcp.example.com/mcp"},
65 } {
66 t.Run(tc.name, func(t *testing.T) {
67 if !HasAuthConfig(tc.headers, nil, tc.url) {
68 t.Fatalf("HasAuthConfig(%v, %q) = false, want true", tc.headers, tc.url)
69 }
70 })
71 }
72 }
73
74 func TestCanUseHTTPMCPOAuthOnlyAllowsSecureOrLoopbackHTTP(t *testing.T) {
75 for _, tc := range []struct {
76 name string
77 url string
78 want bool
79 }{
80 {name: "https", url: "https://mcp.example.com/mcp", want: true},
81 {name: "localhost", url: "http://localhost:8787/mcp", want: true},
82 {name: "ipv4 loopback", url: "http://127.0.0.1:8787/mcp", want: true},
83 {name: "ipv6 loopback", url: "http://[::1]:8787/mcp", want: true},
84 {name: "remote http", url: "http://10.0.0.8/mcp", want: false},
85 {name: "userinfo", url: "https://user:pass@mcp.example.com/mcp", want: false},
86 {name: "signed query", url: "https://mcp.example.com/mcp?sig=abc", want: false},
87 } {
88 t.Run(tc.name, func(t *testing.T) {
89 if got := CanUseHTTPMCPOAuth("http", tc.url, false); got != tc.want {
90 t.Fatalf("CanUseHTTPMCPOAuth(%q) = %v, want %v", tc.url, got, tc.want)
91 }
92 })
93 }
94 }
95
96 func TestClearAuthConfigRemovesOnlyAuthMaterial(t *testing.T) {
97 headers, env, rawURL, changed := ClearAuthConfig(
98 map[string]string{
99 "Authorization": "Bearer ${TOKEN}",
100 "X-Org": "team",
101 },
102 map[string]string{
103 "DIDA_TOKEN": "${DIDA_TOKEN}",
104 "DEBUG": "1",
105 },
106 "https://mcp.example.com/mcp?access_token=abc&workspace=main",
107 )
108 if !changed {
109 t.Fatal("ClearAuthConfig should report changed")
110 }
111 if _, ok := headers["Authorization"]; ok {
112 t.Fatalf("auth header should be removed: %v", headers)
113 }
114 if headers["X-Org"] != "team" {
115 t.Fatalf("ordinary header should be preserved: %v", headers)
116 }
117 if _, ok := env["DIDA_TOKEN"]; ok {
118 t.Fatalf("auth env should be removed: %v", env)
119 }
120 if env["DEBUG"] != "1" {
121 t.Fatalf("ordinary env should be preserved: %v", env)
122 }
123 if rawURL != "https://mcp.example.com/mcp?workspace=main" {
124 t.Fatalf("url = %q", rawURL)
125 }
126 _, _, rawURL, changed = ClearAuthConfig(nil, nil, "https://user:pass@mcp.example.com/mcp")
127 if !changed || rawURL != "https://mcp.example.com/mcp" {
128 t.Fatalf("userinfo URL clear = (%q, %v), want credential-free URL", rawURL, changed)
129 }
130 }
131
131 lines GO