Skip to content

Commit f01fedc

Browse files
committed
test(mcp): add tests for server info, all help topics, and cross-tool errors
Covers gaps identified in test review: - Server name/version via InitializeResult - Help topics for all 11 tools (full and short name forms) - Help overview lists all tools and handles empty ProjectID - Unknown topic error includes available tools list - Error translation for 401, 409, 429, 500, 502, 503 across different tools - Cross-tool error scenarios (destinations 500, connections 401, issues 422, attempts 429) https://claude.ai/code/session_01Y2eJZgKG78nDyN6Uw2tWQx
1 parent 484e66a commit f01fedc

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

pkg/gateway/mcp/server_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,3 +1153,194 @@ func TestInput_InvalidJSON(t *testing.T) {
11531153
_, err := parseInput(json.RawMessage(`{invalid`))
11541154
assert.Error(t, err)
11551155
}
1156+
1157+
// ---------------------------------------------------------------------------
1158+
// Server instructions
1159+
// ---------------------------------------------------------------------------
1160+
1161+
func TestServerInfo_NameAndVersion(t *testing.T) {
1162+
client := newTestClient("https://api.hookdeck.com", "test-key")
1163+
session := connectInMemory(t, client)
1164+
1165+
info := session.InitializeResult()
1166+
require.NotNil(t, info)
1167+
assert.Equal(t, "hookdeck-gateway", info.ServerInfo.Name)
1168+
assert.NotEmpty(t, info.ServerInfo.Version)
1169+
}
1170+
1171+
// ---------------------------------------------------------------------------
1172+
// Help tool: all topics return valid content
1173+
// ---------------------------------------------------------------------------
1174+
1175+
func TestHelpTool_AllTopics(t *testing.T) {
1176+
topics := []struct {
1177+
name string
1178+
expectContains string
1179+
}{
1180+
{"hookdeck_projects", "list"},
1181+
{"hookdeck_connections", "pause"},
1182+
{"hookdeck_sources", "list"},
1183+
{"hookdeck_destinations", "HTTP"},
1184+
{"hookdeck_transformations", "JavaScript"},
1185+
{"hookdeck_requests", "raw_body"},
1186+
{"hookdeck_events", "raw_body"},
1187+
{"hookdeck_attempts", "event_id"},
1188+
{"hookdeck_issues", "delivery"},
1189+
{"hookdeck_metrics", "granularity"},
1190+
{"hookdeck_help", "topic"},
1191+
}
1192+
1193+
client := newTestClient("https://api.hookdeck.com", "test-key")
1194+
session := connectInMemory(t, client)
1195+
1196+
for _, tt := range topics {
1197+
t.Run(tt.name, func(t *testing.T) {
1198+
result := callTool(t, session, "hookdeck_help", map[string]any{"topic": tt.name})
1199+
assert.False(t, result.IsError, "help for %s should not be an error", tt.name)
1200+
text := textContent(t, result)
1201+
assert.Contains(t, text, tt.expectContains,
1202+
"help for %s should mention %q", tt.name, tt.expectContains)
1203+
})
1204+
}
1205+
}
1206+
1207+
func TestHelpTool_ShortNames(t *testing.T) {
1208+
shortNames := []string{
1209+
"projects", "connections", "sources", "destinations",
1210+
"transformations", "requests", "events", "attempts",
1211+
"issues", "metrics", "help",
1212+
}
1213+
1214+
client := newTestClient("https://api.hookdeck.com", "test-key")
1215+
session := connectInMemory(t, client)
1216+
1217+
for _, name := range shortNames {
1218+
t.Run(name, func(t *testing.T) {
1219+
result := callTool(t, session, "hookdeck_help", map[string]any{"topic": name})
1220+
assert.False(t, result.IsError, "short name %q should resolve", name)
1221+
assert.Contains(t, textContent(t, result), "hookdeck_"+name)
1222+
})
1223+
}
1224+
}
1225+
1226+
func TestHelpTool_OverviewListsAllTools(t *testing.T) {
1227+
client := newTestClient("https://api.hookdeck.com", "test-key")
1228+
session := connectInMemory(t, client)
1229+
1230+
result := callTool(t, session, "hookdeck_help", map[string]any{})
1231+
assert.False(t, result.IsError)
1232+
text := textContent(t, result)
1233+
1234+
expectedTools := []string{
1235+
"hookdeck_projects", "hookdeck_connections", "hookdeck_sources",
1236+
"hookdeck_destinations", "hookdeck_transformations", "hookdeck_requests",
1237+
"hookdeck_events", "hookdeck_attempts", "hookdeck_issues",
1238+
"hookdeck_metrics", "hookdeck_help",
1239+
}
1240+
for _, tool := range expectedTools {
1241+
assert.Contains(t, text, tool, "overview should list %s", tool)
1242+
}
1243+
}
1244+
1245+
func TestHelpTool_OverviewShowsProjectNotSet(t *testing.T) {
1246+
client := newTestClient("https://api.hookdeck.com", "test-key")
1247+
client.ProjectID = "" // no project set
1248+
session := connectInMemory(t, client)
1249+
1250+
result := callTool(t, session, "hookdeck_help", map[string]any{})
1251+
assert.False(t, result.IsError)
1252+
assert.Contains(t, textContent(t, result), "not set")
1253+
}
1254+
1255+
func TestHelpTool_UnknownTopicListsAvailable(t *testing.T) {
1256+
client := newTestClient("https://api.hookdeck.com", "test-key")
1257+
session := connectInMemory(t, client)
1258+
1259+
result := callTool(t, session, "hookdeck_help", map[string]any{"topic": "bogus"})
1260+
assert.True(t, result.IsError)
1261+
text := textContent(t, result)
1262+
assert.Contains(t, text, "No help found")
1263+
assert.Contains(t, text, "hookdeck_events") // lists available tools
1264+
}
1265+
1266+
// ---------------------------------------------------------------------------
1267+
// Error feedback: 500 server error through HTTP flow
1268+
// ---------------------------------------------------------------------------
1269+
1270+
func TestDestinationsGet_500ServerError(t *testing.T) {
1271+
session := mockAPIWithClient(t, map[string]http.HandlerFunc{
1272+
"/2025-07-01/destinations/des_fail": func(w http.ResponseWriter, r *http.Request) {
1273+
w.WriteHeader(http.StatusInternalServerError)
1274+
json.NewEncoder(w).Encode(map[string]any{"message": "internal server error"})
1275+
},
1276+
})
1277+
1278+
result := callTool(t, session, "hookdeck_destinations", map[string]any{"action": "get", "id": "des_fail"})
1279+
assert.True(t, result.IsError)
1280+
assert.Contains(t, textContent(t, result), "Hookdeck API error")
1281+
}
1282+
1283+
func TestConnectionsGet_401UnauthorizedError(t *testing.T) {
1284+
session := mockAPIWithClient(t, map[string]http.HandlerFunc{
1285+
"/2025-07-01/connections/web_bad": func(w http.ResponseWriter, r *http.Request) {
1286+
w.WriteHeader(http.StatusUnauthorized)
1287+
json.NewEncoder(w).Encode(map[string]any{"message": "invalid api key"})
1288+
},
1289+
})
1290+
1291+
result := callTool(t, session, "hookdeck_connections", map[string]any{"action": "get", "id": "web_bad"})
1292+
assert.True(t, result.IsError)
1293+
assert.Contains(t, textContent(t, result), "Authentication failed")
1294+
}
1295+
1296+
func TestIssuesList_422ValidationError(t *testing.T) {
1297+
session := mockAPIWithClient(t, map[string]http.HandlerFunc{
1298+
"/2025-07-01/issues": func(w http.ResponseWriter, r *http.Request) {
1299+
w.WriteHeader(http.StatusUnprocessableEntity)
1300+
json.NewEncoder(w).Encode(map[string]any{"message": "invalid filter: bad_field"})
1301+
},
1302+
})
1303+
1304+
result := callTool(t, session, "hookdeck_issues", map[string]any{"action": "list"})
1305+
assert.True(t, result.IsError)
1306+
assert.Contains(t, textContent(t, result), "invalid filter")
1307+
}
1308+
1309+
func TestAttemptsList_429RateLimitError(t *testing.T) {
1310+
session := mockAPIWithClient(t, map[string]http.HandlerFunc{
1311+
"/2025-07-01/attempts": func(w http.ResponseWriter, r *http.Request) {
1312+
w.WriteHeader(http.StatusTooManyRequests)
1313+
json.NewEncoder(w).Encode(map[string]any{"message": "too many requests"})
1314+
},
1315+
})
1316+
1317+
result := callTool(t, session, "hookdeck_attempts", map[string]any{"action": "list"})
1318+
assert.True(t, result.IsError)
1319+
assert.Contains(t, textContent(t, result), "Rate limited")
1320+
}
1321+
1322+
// ---------------------------------------------------------------------------
1323+
// Error translation: additional cases
1324+
// ---------------------------------------------------------------------------
1325+
1326+
func TestTranslateAPIError_RetryAfterMessage(t *testing.T) {
1327+
msg := TranslateAPIError(&hookdeck.APIError{StatusCode: 429, Message: "rate limited"})
1328+
assert.Contains(t, msg, "Rate limited")
1329+
assert.Contains(t, msg, "Retry after")
1330+
}
1331+
1332+
func TestTranslateAPIError_GenericClientError(t *testing.T) {
1333+
// A 4xx status not explicitly handled should pass through the message
1334+
msg := TranslateAPIError(&hookdeck.APIError{StatusCode: 409, Message: "conflict on resource"})
1335+
assert.Contains(t, msg, "conflict on resource")
1336+
}
1337+
1338+
func TestTranslateAPIError_502GatewayError(t *testing.T) {
1339+
msg := TranslateAPIError(&hookdeck.APIError{StatusCode: 502, Message: "bad gateway"})
1340+
assert.Contains(t, msg, "Hookdeck API error")
1341+
}
1342+
1343+
func TestTranslateAPIError_503ServiceUnavailable(t *testing.T) {
1344+
msg := TranslateAPIError(&hookdeck.APIError{StatusCode: 503, Message: "service unavailable"})
1345+
assert.Contains(t, msg, "Hookdeck API error")
1346+
}

0 commit comments

Comments
 (0)