Skip to content

Commit 2373dc9

Browse files
Add tests for config.StdinServerConfig.UnmarshalJSON error paths
Cover previously untested branches in UnmarshalJSON: - invalid JSON input returns error - connect_timeout with non-integer value returns error - tool_timeout with non-integer value returns error - connect_timeout/tool_timeout as floats return error - null for connect_timeout decodes to pointer to 0 - both camelCase and snake_case timeout fields present simultaneously - only snake_case tool_timeout sets toolTimeoutFieldName correctly - unknown fields are collected in AdditionalProperties Coverage improvement: UnmarshalJSON 79.2% → 91.7% Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4552921 commit 2373dc9

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package config
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// TestStdinServerConfig_UnmarshalJSON_ErrorPaths tests the error branches of
11+
// StdinServerConfig.UnmarshalJSON that are not covered by existing tests.
12+
func TestStdinServerConfig_UnmarshalJSON_ErrorPaths(t *testing.T) {
13+
t.Run("invalid JSON returns error", func(t *testing.T) {
14+
var server StdinServerConfig
15+
err := server.UnmarshalJSON([]byte(`not valid json`))
16+
require.Error(t, err)
17+
})
18+
19+
t.Run("connect_timeout with non-integer value returns error", func(t *testing.T) {
20+
// The legacy snake_case alias connect_timeout must be a valid integer.
21+
// Passing a non-integer triggers the assignLegacyIntAlias error path.
22+
data := []byte(`{
23+
"type": "http",
24+
"url": "https://example.com/mcp",
25+
"connect_timeout": "not-a-number"
26+
}`)
27+
28+
var server StdinServerConfig
29+
err := server.UnmarshalJSON(data)
30+
require.Error(t, err)
31+
assert.Contains(t, err.Error(), "connect_timeout")
32+
})
33+
34+
t.Run("tool_timeout with non-integer value returns error", func(t *testing.T) {
35+
// The legacy snake_case alias tool_timeout must be a valid integer.
36+
// Passing a non-integer triggers the assignLegacyIntAlias error path.
37+
data := []byte(`{
38+
"type": "http",
39+
"url": "https://example.com/mcp",
40+
"tool_timeout": "not-a-number"
41+
}`)
42+
43+
var server StdinServerConfig
44+
err := server.UnmarshalJSON(data)
45+
require.Error(t, err)
46+
assert.Contains(t, err.Error(), "tool_timeout")
47+
})
48+
49+
t.Run("connect_timeout as float returns error", func(t *testing.T) {
50+
// Floats are not valid integers for connect_timeout.
51+
data := []byte(`{
52+
"type": "http",
53+
"url": "https://example.com/mcp",
54+
"connect_timeout": 3.14
55+
}`)
56+
57+
var server StdinServerConfig
58+
err := server.UnmarshalJSON(data)
59+
require.Error(t, err)
60+
assert.Contains(t, err.Error(), "connect_timeout")
61+
})
62+
63+
t.Run("tool_timeout as float returns error", func(t *testing.T) {
64+
// Floats are not valid integers for tool_timeout.
65+
data := []byte(`{
66+
"type": "http",
67+
"url": "https://example.com/mcp",
68+
"tool_timeout": 1.5
69+
}`)
70+
71+
var server StdinServerConfig
72+
err := server.UnmarshalJSON(data)
73+
require.Error(t, err)
74+
assert.Contains(t, err.Error(), "tool_timeout")
75+
})
76+
77+
t.Run("connect_timeout as null is treated as zero", func(t *testing.T) {
78+
// json.Unmarshal decodes null into an int as 0, so the field gets a pointer to 0.
79+
data := []byte(`{
80+
"type": "http",
81+
"url": "https://example.com/mcp",
82+
"connect_timeout": null
83+
}`)
84+
85+
var server StdinServerConfig
86+
err := server.UnmarshalJSON(data)
87+
require.NoError(t, err)
88+
require.NotNil(t, server.ConnectTimeout, "null connect_timeout decodes to a pointer to 0")
89+
assert.Equal(t, 0, *server.ConnectTimeout)
90+
})
91+
92+
t.Run("both legacy timeout fields present simultaneously", func(t *testing.T) {
93+
// When both snake_case and camelCase are present the camelCase value wins
94+
// because it is decoded first by the embedded alias struct.
95+
data := []byte(`{
96+
"type": "http",
97+
"url": "https://example.com/mcp",
98+
"connectTimeout": 60,
99+
"toolTimeout": 300,
100+
"connect_timeout": 30,
101+
"tool_timeout": 120
102+
}`)
103+
104+
var server StdinServerConfig
105+
err := server.UnmarshalJSON(data)
106+
require.NoError(t, err)
107+
require.NotNil(t, server.ConnectTimeout)
108+
require.NotNil(t, server.ToolTimeout)
109+
// camelCase fields are decoded by the embedded alias; snake_case aliases
110+
// are only applied when the primary pointer is nil.
111+
assert.Equal(t, 60, *server.ConnectTimeout)
112+
assert.Equal(t, 300, *server.ToolTimeout)
113+
})
114+
115+
t.Run("only tool_timeout snake_case sets toolTimeoutFieldName", func(t *testing.T) {
116+
// When toolTimeout (camelCase) is absent and tool_timeout (snake_case) is
117+
// present, toolTimeoutFieldName should be set to "tool_timeout".
118+
data := []byte(`{
119+
"type": "http",
120+
"url": "https://example.com/mcp",
121+
"tool_timeout": 90
122+
}`)
123+
124+
var server StdinServerConfig
125+
err := server.UnmarshalJSON(data)
126+
require.NoError(t, err)
127+
assert.Equal(t, "tool_timeout", server.toolTimeoutField())
128+
})
129+
130+
t.Run("toolTimeout camelCase keeps default toolTimeoutFieldName", func(t *testing.T) {
131+
// When toolTimeout (camelCase) is present, toolTimeoutFieldName stays as
132+
// "toolTimeout" (the default set in UnmarshalJSON).
133+
data := []byte(`{
134+
"type": "http",
135+
"url": "https://example.com/mcp",
136+
"toolTimeout": 90
137+
}`)
138+
139+
var server StdinServerConfig
140+
err := server.UnmarshalJSON(data)
141+
require.NoError(t, err)
142+
assert.Equal(t, "toolTimeout", server.toolTimeoutField())
143+
})
144+
145+
t.Run("unknown fields are collected in AdditionalProperties", func(t *testing.T) {
146+
data := []byte(`{
147+
"type": "stdio",
148+
"container": "ghcr.io/example/server:latest",
149+
"customField": "customValue",
150+
"anotherExtra": 42
151+
}`)
152+
153+
var server StdinServerConfig
154+
err := server.UnmarshalJSON(data)
155+
require.NoError(t, err)
156+
assert.Equal(t, "customValue", server.AdditionalProperties["customField"])
157+
assert.Equal(t, float64(42), server.AdditionalProperties["anotherExtra"])
158+
// Known fields must not appear in AdditionalProperties.
159+
_, typeExists := server.AdditionalProperties["type"]
160+
assert.False(t, typeExists, "known field 'type' should not appear in AdditionalProperties")
161+
})
162+
}

0 commit comments

Comments
 (0)