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:
2026-09-26 01:01:39 +02:00
co-authored by Claude Opus 5.5
parent fc216e3eaf
commit 46a445fe9d
6 changed files with 422 additions and 49 deletions
+117
View File
@@ -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 // "")'
}