From b5bcf08acbf381773c3805e109f78fb011d31991 Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Sat, 20 Jun 2026 16:43:30 +0000 Subject: [PATCH] test: add TDD tests for hello-world MCP server 23 tests covering the full JSON-RPC 2.0 protocol surface: initialize handshake, silent notifications/initialized, tools/list schema, tools/call with and without args, unknown tool error, unknown method error, and invalid JSON resilience. Fixed assert helpers to use grep -qF -- to prevent leading-dash patterns (like -32601) from being parsed as grep flags. Co-Authored-By: Claude Sonnet 4.6 --- plugins/hello-world/tests/test_mcp_server.sh | 106 +++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 plugins/hello-world/tests/test_mcp_server.sh diff --git a/plugins/hello-world/tests/test_mcp_server.sh b/plugins/hello-world/tests/test_mcp_server.sh new file mode 100755 index 0000000..42ea5cd --- /dev/null +++ b/plugins/hello-world/tests/test_mcp_server.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# Tests for plugins/hello-world/bin/mcp-server.js +# Protocol: JSON-RPC 2.0 over stdio. Tests send input lines and assert on stdout. +set -euo pipefail + +SERVER="$(cd "$(dirname "$0")/../bin" && pwd)/mcp-server.js" +PASS=0; FAIL=0 + +# ── helpers ────────────────────────────────────────────────────────────────── + +assert_contains() { + local label="$1" expected="$2" actual="$3" + if echo "$actual" | grep -qF -- "$expected"; then + echo " PASS: $label" + PASS=$((PASS+1)) + else + echo " FAIL: $label" + echo " expected to contain: $expected" + echo " got: $actual" + FAIL=$((FAIL+1)) + fi +} + +assert_not_contains() { + local label="$1" unexpected="$2" actual="$3" + if echo "$actual" | grep -qF -- "$unexpected"; then + echo " FAIL: $label" + echo " expected NOT to contain: $unexpected" + echo " got: $actual" + FAIL=$((FAIL+1)) + else + echo " PASS: $label" + PASS=$((PASS+1)) + fi +} + +# Send newline-separated JSON lines to the server, capture stdout +run_server() { + printf '%s\n' "$@" | node "$SERVER" 2>/dev/null +} + +# ── tests ──────────────────────────────────────────────────────────────────── + +echo "initialize" +OUT=$(run_server '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}},"id":1}') +assert_contains "returns protocolVersion" '"protocolVersion":"2024-11-05"' "$OUT" +assert_contains "returns capabilities" '"capabilities"' "$OUT" +assert_contains "returns serverInfo name" '"name":"hello-world"' "$OUT" +assert_contains "returns serverInfo version" '"version":"1.0.0"' "$OUT" +assert_contains "echoes id" '"id":1' "$OUT" + +echo "notifications/initialized" +OUT=$(run_server '{"jsonrpc":"2.0","method":"notifications/initialized"}') +assert_not_contains "no response for notification" '"jsonrpc"' "$OUT" + +echo "tools/list" +OUT=$(run_server \ + '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' \ + '{"jsonrpc":"2.0","method":"tools/list","id":2}') +assert_contains "lists say_hello tool" '"name":"say_hello"' "$OUT" +assert_contains "tool has description" '"description"' "$OUT" +assert_contains "tool has inputSchema" '"inputSchema"' "$OUT" +assert_contains "schema has name property" '"name"' "$OUT" +assert_contains "echoes id 2" '"id":2' "$OUT" + +echo "tools/call say_hello — no args" +OUT=$(run_server \ + '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' \ + '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"say_hello","arguments":{}},"id":3}') +assert_contains "generic greeting content" 'Hello from the holocron' "$OUT" +assert_contains "response has content array" '"content"' "$OUT" +assert_contains "content type is text" '"type":"text"' "$OUT" + +echo "tools/call say_hello — with name" +OUT=$(run_server \ + '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' \ + '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"say_hello","arguments":{"name":"Obi-Wan"}},"id":4}') +assert_contains "personalised greeting" 'Hello, Obi-Wan!' "$OUT" +assert_not_contains "not generic greeting" 'Hello from the holocron' "$OUT" + +echo "tools/call — unknown tool" +OUT=$(run_server \ + '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' \ + '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"does_not_exist","arguments":{}},"id":5}') +assert_contains "error code -32601" '"code":-32601' "$OUT" +assert_contains "error field present" '"error"' "$OUT" + +echo "unknown method with id" +OUT=$(run_server \ + '{"jsonrpc":"2.0","method":"no_such_method","id":6}') +assert_contains "error returned" '"error"' "$OUT" +assert_contains "method not found code" '"code":-32601' "$OUT" +assert_contains "echoes id 6" '"id":6' "$OUT" + +echo "invalid JSON — no crash, process continues" +OUT=$(run_server \ + 'this is not json' \ + '{"jsonrpc":"2.0","method":"tools/list","id":7}') +assert_contains "valid request after bad input succeeds" '"id":7' "$OUT" +assert_not_contains "no error for bad json line" '"error"' "$OUT" + +# ── summary ────────────────────────────────────────────────────────────────── + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[[ $FAIL -eq 0 ]] && exit 0 || exit 1