quickstart-resources/tests/utils.sh
olaservo 75796e0c98 Cover the Ruby MCP client in the smoke tests
CI installed no Ruby toolchain and the suite ran no Ruby example, so
nothing under mcp-client-ruby or weather-server-ruby was exercised by a
green build. A dependency change there -- #177 removes the base64 gem
now that the anthropic gem declares it -- merges on the strength of a
check that never resolved its Gemfile.

The client is drivable without credentials: it connects before it looks
for ANTHROPIC_API_KEY and exits 0 when there is none, the same shape the
Python and TypeScript clients already rely on for their coverage.

The Ruby weather server stays uncovered. The mcp gem's server does not
emit the resultType field that protocol revision 2026-07-28 makes
mandatory, so the test client rejects its responses; that is a fix for
the gem rather than something an example can work around. The README
said the gem could not negotiate the revision at all, which stopped
being true at 1.1.0 -- it negotiates, then answers incompletely.

Ruby 3.4 rather than the 3.2.0 floor the anthropic gem declares: 3.2
reached end of life in March 2026, and a gem's floor says what it
tolerates, not what an example should tell people to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:11:42 -07:00

153 lines
4.2 KiB
Shell

#!/bin/bash
# Shared utilities for test scripts
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Print functions
print_header() {
echo -e "${YELLOW}==== $1 ====${NC}"
}
print_success() {
echo -e "${GREEN}✓ $1${NC}"
}
print_error() {
echo -e "${RED}✗ $1${NC}"
}
# Get project root directory
get_project_root() {
cd "$(dirname "$0")/.." && pwd
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check dependency and fail the calling test if not found.
#
# Returns rather than exits: a missing toolchain should fail its own test and
# let the rest of the suite run, not abort the whole script before the summary
# is printed.
check_dependency() {
local cmd=$1
if ! command_exists "${cmd}"; then
print_error "${cmd} is not installed"
return 1
fi
}
# The executable suffix for this platform: ".exe" under MSYS/Git Bash, empty
# elsewhere. Windows will not run a binary without it, whatever the file is
# actually named.
exe_suffix() {
case "$(uname -s)" in
MINGW* | MSYS* | CYGWIN*) echo ".exe" ;;
*) echo "" ;;
esac
}
# Echo the path to a built binary, accounting for the .exe suffix on Windows.
# Prints nothing and returns 1 when neither exists.
resolve_binary() {
local path=$1
if [ -f "${path}$(exe_suffix)" ]; then
echo "${path}$(exe_suffix)"
elif [ -f "${path}" ]; then
echo "${path}"
else
return 1
fi
}
# Setup test environment
setup_test() {
local test_name=$1
print_header "Testing ${test_name}"
PROJECT_ROOT=$(get_project_root)
SERVER_DIR="${PROJECT_ROOT}/${test_name}"
CLIENT_DIR="${PROJECT_ROOT}/${test_name}"
TEST_CLIENT="${PROJECT_ROOT}/tests/helpers/build/mcp-test-client.js"
MOCK_SERVER="${PROJECT_ROOT}/tests/helpers/build/mock-mcp-server.js"
}
# Ensure test helpers are built
ensure_helpers_built() {
if [ ! -f "${TEST_CLIENT}" ] || [ ! -f "${MOCK_SERVER}" ]; then
print_error "Test helpers not built"
print_header "Building test helpers..."
cd "${PROJECT_ROOT}/tests/helpers" || return 1
npm install >/dev/null 2>&1
npm run build >/dev/null 2>&1
cd - >/dev/null || return 1
print_success "Test helpers built"
fi
}
# Run a build step, showing its output only if it fails.
#
# Builds used to be run with `>/dev/null 2>&1` unconditionally, which hid
# compile errors: a failed build left no binary behind and the smoke test
# reported the downstream `spawn ... ENOENT` instead of the actual error.
run_build() {
local what=$1
shift
local output
if ! output=$("$@" 2>&1); then
print_error "${what} failed"
echo "${output}"
return 1
fi
}
# Ensure a project directory is built (TypeScript/Rust/Go)
#
# Returns rather than exits, for the same reason check_dependency does: one
# project failing to build should fail its own test, not abort the suite
# before the summary is printed.
ensure_built() {
local dir=$1
cd "${dir}" || return 1
# Install npm dependencies if needed
if [ -f "package.json" ] && [ ! -d "node_modules" ]; then
run_build "npm install in ${dir}" npm install || return 1
fi
# Build TypeScript if needed
if [ -f "tsconfig.json" ] && [ ! -f "build/index.js" ]; then
run_build "npm run build in ${dir}" npm run build || return 1
fi
# Build Rust if needed
if [ -f "Cargo.toml" ] \
&& ! resolve_binary "target/release/weather" >/dev/null \
&& ! resolve_binary "target/debug/weather" >/dev/null; then
run_build "cargo build in ${dir}" cargo build --release || return 1
fi
# Build Go if needed
if [ -f "go.mod" ] && ! resolve_binary "server" >/dev/null; then
run_build "go build in ${dir}" go build -o "server$(exe_suffix)" . || return 1
fi
}
# Ensure a Ruby project directory has its gems installed.
#
# Unlike ensure_built there is no artefact to test for -- a Ruby example has no
# build step and Gemfile.lock is gitignored -- so ask Bundler directly.
ensure_bundled() {
local dir=$1
cd "${dir}" || return 1
if ! bundle check >/dev/null 2>&1; then
run_build "bundle install in ${dir}" bundle install || return 1
fi
}