Skip to content

Commit edba7f3

Browse files
committed
Add simple tests
1 parent 0f6be05 commit edba7f3

8 files changed

+2736
-0
lines changed

mcp/errors_additional_test.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package mcp
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestUnsupportedProtocolVersionError_Is(t *testing.T) {
12+
err1 := UnsupportedProtocolVersionError{Version: "1.0"}
13+
err2 := UnsupportedProtocolVersionError{Version: "2.0"}
14+
15+
t.Run("matches same type", func(t *testing.T) {
16+
assert.True(t, err1.Is(UnsupportedProtocolVersionError{}))
17+
assert.True(t, err2.Is(UnsupportedProtocolVersionError{Version: "different"}))
18+
})
19+
20+
t.Run("does not match different type", func(t *testing.T) {
21+
assert.False(t, err1.Is(errors.New("different error")))
22+
assert.False(t, err1.Is(ErrMethodNotFound))
23+
})
24+
}
25+
26+
func TestIsUnsupportedProtocolVersion(t *testing.T) {
27+
t.Run("returns true for UnsupportedProtocolVersionError", func(t *testing.T) {
28+
err := UnsupportedProtocolVersionError{Version: "1.0"}
29+
assert.True(t, IsUnsupportedProtocolVersion(err))
30+
})
31+
32+
t.Run("returns false for other errors", func(t *testing.T) {
33+
assert.False(t, IsUnsupportedProtocolVersion(errors.New("other error")))
34+
assert.False(t, IsUnsupportedProtocolVersion(ErrMethodNotFound))
35+
})
36+
37+
t.Run("returns false for wrapped errors", func(t *testing.T) {
38+
// Create a wrapped error - IsUnsupportedProtocolVersion checks direct type, not wrapped
39+
err := UnsupportedProtocolVersionError{Version: "1.0"}
40+
wrapped := errors.New("wrapped: " + err.Error())
41+
assert.False(t, IsUnsupportedProtocolVersion(wrapped))
42+
})
43+
}
44+
45+
func TestJSONRPCErrorDetails_AsError_EmptyMessage(t *testing.T) {
46+
t.Run("with empty message", func(t *testing.T) {
47+
details := JSONRPCErrorDetails{
48+
Code: METHOD_NOT_FOUND,
49+
Message: "",
50+
}
51+
52+
err := details.AsError()
53+
// Should return the sentinel error when message is empty
54+
assert.Equal(t, ErrMethodNotFound, err)
55+
})
56+
57+
t.Run("with message matching sentinel", func(t *testing.T) {
58+
details := JSONRPCErrorDetails{
59+
Code: PARSE_ERROR,
60+
Message: "parse error",
61+
}
62+
63+
err := details.AsError()
64+
assert.Equal(t, ErrParseError, err)
65+
})
66+
}
67+
68+
func TestJSONRPCErrorDetails_AsError_AllCodes(t *testing.T) {
69+
tests := []struct {
70+
name string
71+
code int
72+
message string
73+
sentinel error
74+
shouldMatch bool
75+
}{
76+
{
77+
name: "PARSE_ERROR",
78+
code: PARSE_ERROR,
79+
message: "custom parse error",
80+
sentinel: ErrParseError,
81+
shouldMatch: true,
82+
},
83+
{
84+
name: "INVALID_REQUEST",
85+
code: INVALID_REQUEST,
86+
message: "custom invalid request",
87+
sentinel: ErrInvalidRequest,
88+
shouldMatch: true,
89+
},
90+
{
91+
name: "METHOD_NOT_FOUND",
92+
code: METHOD_NOT_FOUND,
93+
message: "custom method not found",
94+
sentinel: ErrMethodNotFound,
95+
shouldMatch: true,
96+
},
97+
{
98+
name: "INVALID_PARAMS",
99+
code: INVALID_PARAMS,
100+
message: "custom invalid params",
101+
sentinel: ErrInvalidParams,
102+
shouldMatch: true,
103+
},
104+
{
105+
name: "INTERNAL_ERROR",
106+
code: INTERNAL_ERROR,
107+
message: "custom internal error",
108+
sentinel: ErrInternalError,
109+
shouldMatch: true,
110+
},
111+
{
112+
name: "REQUEST_INTERRUPTED",
113+
code: REQUEST_INTERRUPTED,
114+
message: "custom interrupted",
115+
sentinel: ErrRequestInterrupted,
116+
shouldMatch: true,
117+
},
118+
{
119+
name: "RESOURCE_NOT_FOUND",
120+
code: RESOURCE_NOT_FOUND,
121+
message: "custom resource not found",
122+
sentinel: ErrResourceNotFound,
123+
shouldMatch: true,
124+
},
125+
{
126+
name: "unknown code",
127+
code: -99999,
128+
message: "unknown error",
129+
sentinel: nil,
130+
shouldMatch: false,
131+
},
132+
}
133+
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
details := JSONRPCErrorDetails{
137+
Code: tt.code,
138+
Message: tt.message,
139+
}
140+
141+
err := details.AsError()
142+
require.NotNil(t, err)
143+
144+
if tt.shouldMatch {
145+
assert.True(t, errors.Is(err, tt.sentinel))
146+
// Custom message should be wrapped
147+
assert.Contains(t, err.Error(), tt.message)
148+
} else {
149+
// Unknown codes just return the message
150+
assert.Equal(t, tt.message, err.Error())
151+
}
152+
})
153+
}
154+
}
155+
156+
func TestErrorChaining_WithAs(t *testing.T) {
157+
t.Run("errors.As does not work with wrapped sentinel", func(t *testing.T) {
158+
details := &JSONRPCErrorDetails{
159+
Code: METHOD_NOT_FOUND,
160+
Message: "Method 'foo' not found",
161+
}
162+
163+
err := details.AsError()
164+
165+
// Since we wrap with fmt.Errorf, errors.As won't find the exact type
166+
// but errors.Is will work because of the %w verb
167+
assert.True(t, errors.Is(err, ErrMethodNotFound))
168+
})
169+
}
170+
171+
func TestSentinelErrors_Comparison(t *testing.T) {
172+
// Ensure all sentinel errors are distinct
173+
sentinels := []error{
174+
ErrParseError,
175+
ErrInvalidRequest,
176+
ErrMethodNotFound,
177+
ErrInvalidParams,
178+
ErrInternalError,
179+
ErrRequestInterrupted,
180+
ErrResourceNotFound,
181+
}
182+
183+
for i, err1 := range sentinels {
184+
for j, err2 := range sentinels {
185+
if i == j {
186+
assert.True(t, errors.Is(err1, err2), "Same sentinel should match itself")
187+
} else {
188+
assert.False(t, errors.Is(err1, err2), "Different sentinels should not match")
189+
}
190+
}
191+
}
192+
}
193+
194+
func TestUnsupportedProtocolVersionError_Error(t *testing.T) {
195+
err := UnsupportedProtocolVersionError{Version: "3.0"}
196+
assert.Equal(t, `unsupported protocol version: "3.0"`, err.Error())
197+
}
198+
199+
func TestJSONRPCErrorDetails_WithData(t *testing.T) {
200+
details := &JSONRPCErrorDetails{
201+
Code: INVALID_PARAMS,
202+
Message: "Invalid parameter 'foo'",
203+
Data: map[string]any{
204+
"param": "foo",
205+
"expected": "string",
206+
"got": "number",
207+
},
208+
}
209+
210+
err := details.AsError()
211+
212+
// The error should still wrap properly
213+
assert.True(t, errors.Is(err, ErrInvalidParams))
214+
assert.Contains(t, err.Error(), "Invalid parameter 'foo'")
215+
216+
// Data is not included in the error string, but it's preserved in the details
217+
assert.NotNil(t, details.Data)
218+
}

0 commit comments

Comments
 (0)