test: end-to-end checks for the WordPress basics; content and workflow guides
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared helpers for the MCP integration scripts (mcp-smoke.sh, wp-basis-smoke.sh,
|
||||
# and Etappe 1b's smoke scripts). Source it from a script that runs INSIDE the DDEV
|
||||
# web container with /var/www/html as its working directory (cd there before
|
||||
# sourcing – HOST below shells out to `wp` immediately).
|
||||
# Requires jq and curl (both present in the DDEV web image).
|
||||
|
||||
PLUGIN=wp-content/plugins/WP-LIT-MCP
|
||||
ENDPOINT=http://localhost/wp-json/mcp/mcp-oauth-server
|
||||
|
||||
# The site's own Host header (without scheme). A request that bypasses normal WP
|
||||
# routing (e.g. the raw discovery rewrite target mcp-smoke.sh probes) needs this
|
||||
# Host header, or WordPress 301s it to the canonical host before it ever reaches
|
||||
# the code under test.
|
||||
HOST=$(wp option get home | sed -E 's#^[a-zA-Z]+://##')
|
||||
|
||||
TOKEN=""
|
||||
SESSION=""
|
||||
INIT_RESPONSE=""
|
||||
REQUEST_ID=100
|
||||
|
||||
fail() { echo "FAIL: $*"; exit 1; }
|
||||
pass() { echo "ok - $*"; }
|
||||
|
||||
# Removes every "lit-mcp-integration-test" application password of a user.
|
||||
cleanup_app_passwords_for() { # $1 = user_login
|
||||
local login="$1"
|
||||
[ -n "$login" ] || return 0
|
||||
local escaped=${login//\\/\\\\}
|
||||
escaped=${escaped//\'/\\\'}
|
||||
wp eval "
|
||||
\$u = get_user_by('login', '${escaped}');
|
||||
if (\$u) {
|
||||
foreach (WP_Application_Passwords::get_user_application_passwords(\$u->ID) as \$pw) {
|
||||
if ((\$pw['name'] ?? '') === 'lit-mcp-integration-test') {
|
||||
WP_Application_Passwords::delete_application_password(\$u->ID, \$pw['uuid']);
|
||||
}
|
||||
}
|
||||
}
|
||||
" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Mints an MCP access token for a user, exactly like mcp-oauth's TokenEndpoint does
|
||||
# after a completed OAuth flow (mint-token.php). Also creates the throwaway
|
||||
# "lit-mcp-integration-test" application password cleanup_app_passwords_for removes.
|
||||
mint_token() { # $1 = user_login -> prints the token
|
||||
wp eval-file "$PLUGIN/tests/Integration/mint-token.php" "$1"
|
||||
}
|
||||
|
||||
# Raw JSON-RPC POST to the MCP endpoint; prints headers+body (-i). $3 (session id)
|
||||
# is optional. Low-level building block: used directly where a script needs the
|
||||
# response headers (e.g. to read Mcp-Session-Id off the initialize response) and by
|
||||
# rpc() below for everything after that.
|
||||
mcp() { # $1 = token, $2 = json body, $3 = session id (optional)
|
||||
local session_header=()
|
||||
[ -n "${3:-}" ] && session_header=(-H "Mcp-Session-Id: $3")
|
||||
curl -s --max-time 20 -i -X POST "$ENDPOINT" \
|
||||
-H "Authorization: Bearer $1" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
"${session_header[@]}" -d "$2"
|
||||
}
|
||||
|
||||
# Mints a token for the user and opens an MCP session. Sets TOKEN, SESSION and
|
||||
# INIT_RESPONSE (raw initialize response incl. headers).
|
||||
connect() { # $1 = user_login
|
||||
TOKEN=$(mint_token "$1")
|
||||
local init
|
||||
init=$(mcp "$TOKEN" '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}')
|
||||
SESSION=$(echo "$init" | grep -i '^mcp-session-id:' | awk '{print $2}' | tr -d '\r' || true)
|
||||
INIT_RESPONSE="$init"
|
||||
echo "$init" | grep -q '"protocolVersion"' || fail "initialize failed: $init"
|
||||
}
|
||||
|
||||
# JSON-RPC call over the session connect() opened; prints the response body only.
|
||||
rpc() { # $1 = method, $2 = params JSON
|
||||
REQUEST_ID=$((REQUEST_ID + 1))
|
||||
local body
|
||||
body=$(jq -cn --arg m "$1" --argjson p "$2" --argjson id "$REQUEST_ID" '{jsonrpc: "2.0", id: $id, method: $m, params: $p}')
|
||||
local session_header=()
|
||||
[ -n "$SESSION" ] && session_header=(-H "Mcp-Session-Id: $SESSION")
|
||||
curl -s --max-time 60 -X POST "$ENDPOINT" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
"${session_header[@]}" -d "$body"
|
||||
}
|
||||
|
||||
# Calls a tool; prints structuredContent, or {"__error": "<text>"} for isError results.
|
||||
tool() { # $1 = tool name, $2 = arguments JSON
|
||||
local response
|
||||
response=$(rpc tools/call "$(jq -cn --arg n "$1" --argjson a "$2" '{name: $n, arguments: $a}')")
|
||||
echo "$response" | jq -e '.result' >/dev/null 2>&1 || fail "$1: protocol error: $response"
|
||||
if [ "$(echo "$response" | jq -r '.result.isError')" = "true" ]; then
|
||||
echo "$response" | jq -c '{__error: .result.content[0].text}'
|
||||
else
|
||||
echo "$response" | jq -c '.result.structuredContent'
|
||||
fi
|
||||
}
|
||||
|
||||
# Runs an indirect ability through mcp-adapter-execute-ability; prints its result or {"__error": …}.
|
||||
ability() { # $1 = ability name (lit/…), $2 = parameters JSON
|
||||
local out
|
||||
out=$(tool mcp-adapter-execute-ability "$(jq -cn --arg n "$1" --argjson p "$2" '{ability_name: $n, parameters: $p}')")
|
||||
if echo "$out" | jq -e 'has("__error")' >/dev/null; then
|
||||
echo "$out"
|
||||
elif [ "$(echo "$out" | jq -r '.success')" = "true" ]; then
|
||||
echo "$out" | jq -c '.data'
|
||||
else
|
||||
echo "$out" | jq -c '{__error: .error}'
|
||||
fi
|
||||
}
|
||||
|
||||
# Prints the lit error code (e.g. rev_mismatch) of a tool()/ability() result, or "".
|
||||
error_code() { # $1 = result JSON
|
||||
echo "$1" | jq -r '(.__error // "") | (try fromjson catch {}) | (.code // "")'
|
||||
}
|
||||
Reference in New Issue
Block a user