Files
WP-LIT-MCP/tests/Integration/pods-compat-smoke.sh
T
Jan-Niclas LoosenandClaude Opus 5.5 1a294b7881 feat(pods): Verhaltensprobe als Prüfschritt
Beim Prüfen schreibt und liest PodsProbe auf einem Wegwerf-Entwurf ein Textfeld genau so, wie LIT MCP es tut, vergleicht die Werte und löscht den Entwurf wieder. Bevorzugt von Pods angelegte Typen; ohne Pod mit Textfeld gilt der Schritt als grün mit Hinweis „nicht geprüft“. Ein roter Schritt sperrt Pods über die Berichtssperre. Liegengebliebene Probe-Einträge räumt der Load-Hook des Reiters auf. Pods-Smoke um Probe und Bereinigung erweitert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:33:26 +01:00

155 lines
11 KiB
Bash

#!/usr/bin/env bash
# End-to-end checks of the Pods profile of the compatibility layer (plan 2026-11-06-kompatibilitaetsschicht, 1.25):
# status, check route /pods-check, tab "Compatibility", release (refused for a tested version, simulated for an
# untested one) and the lock by a red stored report. Runs INSIDE the DDEV web container:
# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/pods-compat-smoke.sh
# Temporarily activates the installed Pods plugin (restored afterwards); removes the lit_mcp_pods_* options, the
# fixture pod and its entry again. The minimum version of the profile is not changeable from outside, so the lock is
# triggered the way production does it: a red check report for the current state (lit_mcp_pods_check_report).
set -euo pipefail
cd /var/www/html
# shellcheck source=lib.sh
source wp-content/plugins/WP-LIT-MCP/tests/Integration/lib.sh
ADMIN_LOGIN=$(wp user list --role=administrator --field=user_login | head -1)
PODS_WAS_ACTIVE=no
wp plugin is-active pods && PODS_WAS_ACTIVE=yes
CREATED_POSTS=()
BASE=http://localhost
OPTIONS=(lit_mcp_pods_check_report lit_mcp_pods_releases lit_mcp_pods_check_lock)
restore() {
for id in "${CREATED_POSTS[@]}"; do
wp post delete "$id" --force >/dev/null 2>&1 || true
done
wp option delete "${OPTIONS[@]}" >/dev/null 2>&1 || true
wp eval-file "$PLUGIN/tests/Integration/pods-fixture.php" delete >/dev/null 2>&1 || true
[ "$PODS_WAS_ACTIVE" = "no" ] && wp plugin deactivate pods >/dev/null 2>&1 || true
cleanup_app_passwords_for "$ADMIN_LOGIN"
}
trap 'rc=$?; restore; guard_exit "$rc" || exit $?' EXIT
pods_component() { tool lit-site-status '{}' | jq -c '.components[] | select(.id == "pods")'; }
stored_option() { wp option get "$1" --format=json 2>/dev/null || true; }
# Runs the admin-post handler of the tab (pods-compat-release.php); prints the notice of its redirect.
handler() { wp eval-file "$PLUGIN/tests/Integration/pods-compat-release.php" "$1" "$2" 2>/dev/null | tail -1; }
# Stores a red report for the current Pods state (what a failed admin check leaves behind).
write_report() { # $1 = red|green
RESULT="$1" wp eval '
$profile = LIT\MCP\Integrations\Pods\PodsCompatibility::profile();
$problems = getenv("RESULT") === "red" ? ["Smoke: Pods-Aufruf fehlt"] : [];
LIT\MCP\Compatibility\CheckReportStore::forProfile($profile)->save(new LIT\MCP\Compatibility\CheckReport(
"smoke-report", gmdate("c"), 1, (string) PODS_VERSION, LIT\MCP\Plugin::VERSION, "",
[new LIT\MCP\Compatibility\CheckStep("manifest", $problems, false, "Pods-Funktionen")]
));' >/dev/null || fail "could not write the $1 report"
}
wp plugin activate pods >/dev/null
wp eval-file "$PLUGIN/tests/Integration/pods-fixture.php" create >/dev/null
connect "$ADMIN_LOGIN"
# 1. Pods active: version, no problems
pods_component | jq -e '.active == true and .version == "3.3.9.2" and .problems == []' >/dev/null \
|| fail "site status must show Pods 3.3.9.2 active without problems: $(pods_component)"
pass "lit-site-status: Pods 3.3.9.2 active, no problems"
# 2. Check route (application password instead of the cookie nonce, as in divi-release-smoke.sh)
APP_PASSWORD=$(wp user application-password create "$ADMIN_LOGIN" lit-mcp-integration-test --porcelain)
HEADERS=$(curl -s -o /dev/null -D - -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' -u "$ADMIN_LOGIN:$APP_PASSWORD" "$BASE/wp-json/lit-mcp/v1/pods-check")
echo "$HEADERS" | grep -qi '^HTTP/[0-9.]* 303' || fail "check route must answer 303: $HEADERS"
echo "$HEADERS" | grep -qi '^location:.*tab=compatibility.*lit_mcp_notice=compat_checked' || fail "redirect to the tab: $HEADERS"
REPORT=$(stored_option lit_mcp_pods_check_report)
echo "$REPORT" | jq -e '(.steps | length) >= 1 and all(.steps[]; .problems == [] and .skipped == false)
and .version == "3.3.9.2" and (has("divi_version") | not)' >/dev/null || fail "the stored report must be green with key version: $REPORT"
[ -z "$(stored_option lit_mcp_pods_check_lock)" ] || fail "the check lock must be released"
# The behaviour probe is part of the report (green, not skipped, its label is the real probe, not "not probed") ...
echo "$REPORT" | jq -e '[.steps[] | select(.key == "probe")] | length == 1 and (.[0] | .problems == [] and .skipped == false
and (.label | contains("nicht geprüft") | not))' >/dev/null || fail "the report must contain the green probe step: $REPORT"
# ... and leaves no entry behind (it ran on lit_person; any status, any marked entry).
LEFT=$(wp post list --post_type=any --post_status=any --meta_key=_lit_mcp_check_entry --format=count)
[ "$LEFT" = "0" ] || fail "the probe left $LEFT entries behind"
[ "$(wp post list --post_type=lit_person --post_status=any --format=count)" = "0" ] || fail "the probe left a lit_person entry"
UNAUTH=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' "$BASE/wp-json/lit-mcp/v1/pods-check")
[ "$UNAUTH" = "401" ] || [ "$UNAUTH" = "403" ] || fail "anonymous check must be refused, got $UNAUTH"
pass "/pods-check: 303 to the tab, green report with key version and probe step, no probe entry left, lock released, anonymous refused"
# 3. The tab renders the Pods section and, as 3.3.9.2 is tested, offers no release
TAB=$(wp --user="$ADMIN_LOGIN" eval '
require_once ABSPATH . "wp-admin/includes/template.php";
$_GET["tab"] = "compatibility";
LIT\MCP\Plugin::settingsPage()->render();')
echo "$TAB" | grep -q 'id="lit-mcp-compat-pods"' || fail "tab Compatibility must have a Pods section"
echo "$TAB" | grep -q '3\.3\.9\.2' || fail "the Pods section must show the version"
echo "$TAB" | grep -q 'freigegeben' || fail "the Pods section must show the state"
echo "$TAB" | grep -q 'lit-mcp-compat-check-pods' || fail "the Pods section must offer the check"
echo "$TAB" | grep -q 'lit-mcp-compat-release-pods' && fail "no release button for a tested version"
pass "tab Compatibility: Pods section with version and state, check button, no release button"
# 3b. Orphan cleanup: an old marked probe entry (a dead check) is deleted on the tab's profile-load hook, a young one and
# an unmarked entry with the same title are not.
ORPHANS=$(wp --user="$ADMIN_LOGIN" eval '
$make = static fn (string $date, bool $marked): int => (int) wp_insert_post([
"post_type" => "lit_person", "post_status" => "draft", "post_title" => LIT\MCP\Integrations\Pods\PodsProbe::TITLE . " smoke",
"post_date" => get_date_from_gmt($date), "post_date_gmt" => $date,
"meta_input" => $marked ? [LIT\MCP\Integrations\Pods\PodsProbe::MARKER => "1"] : [],
]);
$old = $make(gmdate("Y-m-d H:i:s", time() - 3600), true);
$young = $make(gmdate("Y-m-d H:i:s"), true);
$unmarked = $make(gmdate("Y-m-d H:i:s", time() - 3600), false);
do_action(LIT\MCP\Admin\CompatibilityTab::LOAD_ACTION, "pods");
echo json_encode([$old, $young, $unmarked, get_post_status($old), get_post_status($young), get_post_status($unmarked)]);')
OLD_ID=$(echo "$ORPHANS" | jq -r '.[0]'); YOUNG_ID=$(echo "$ORPHANS" | jq -r '.[1]'); UNMARKED_ID=$(echo "$ORPHANS" | jq -r '.[2]')
CREATED_POSTS+=("$OLD_ID" "$YOUNG_ID" "$UNMARKED_ID")
echo "$ORPHANS" | jq -e '.[3] == false and .[4] == "draft" and .[5] == "draft"' >/dev/null \
|| fail "orphan cleanup must delete only the old marked entry: $ORPHANS"
pass "orphan cleanup: old marked probe entry deleted, young and unmarked ones kept"
# 4. Release: refused for the tested version; for an untested one (simulated profile) it is only documented
[ "$(handler release real)" = "compat_release_refused" ] || fail "releasing a tested version must be refused"
[ -z "$(stored_option lit_mcp_pods_releases)" ] || fail "a refused release must write nothing"
[ "$(handler release untested)" = "compat_released" ] || fail "releasing an untested version after a green report must work"
stored_option lit_mcp_pods_releases | jq -e 'length == 1 and .[0].version == "3.3.9.2" and (.[0] | has("divi_version") | not)' >/dev/null \
|| fail "the release must be stored with key version: $(stored_option lit_mcp_pods_releases)"
pods_component | jq -e '.problems == []' >/dev/null || fail "a release must not change the Pods status"
tool lit-content-types '{"post_type":"lit_person"}' | jq -e '.post_types[0].fields[] | select(.key == "role") | .status == "writable"' >/dev/null \
|| fail "a release must not lock or free anything"
[ "$(handler withdraw untested)" = "compat_withdrawn" ] || fail "withdrawing the release must work"
stored_option lit_mcp_pods_releases | jq -e 'length == 0' >/dev/null || fail "the withdrawn release must be gone"
pass "release: refused for tested 3.3.9.2; simulated untested one stored and withdrawn, no lock effect"
# 5. Lock: a red report for the current state locks Pods (all of its capabilities); a green one frees it again
PERSON=$(tool lit-content-create '{"post_type":"lit_person","title":"Smoke Sperre","fields":{"role":"Leitung"}}')
PERSON_ID=$(echo "$PERSON" | jq -r '.changed[0].id // empty')
[ -n "$PERSON_ID" ] || fail "fixture entry: $PERSON"
CREATED_POSTS+=("$PERSON_ID")
REV=$(tool lit-content-get "{\"post_id\":$PERSON_ID}" | jq -r '.rev')
write_report red
pods_component | jq -e '(.problems | length) >= 1 and (.problems | any(contains("Prüfung")))' >/dev/null \
|| fail "a red report must show as a problem: $(pods_component)"
tool lit-content-types '{"post_type":"lit_person"}' | jq -e '[.post_types[0].fields[] | select(.key == "role")] | all(.status == "readonly")' >/dev/null \
|| fail "locked: a Pods field must be read-only: $(tool lit-content-types '{"post_type":"lit_person"}')"
BLOCKED=$(tool lit-content-update "{\"post_id\":$PERSON_ID,\"rev\":\"$REV\",\"fields\":{\"role\":\"Gesperrt\"}}")
echo "$BLOCKED" | jq -e 'has("__error")' >/dev/null || fail "locked: a Pods field must not be writable: $BLOCKED"
[ "$(wp post meta get "$PERSON_ID" role)" = "Leitung" ] || fail "locked: the value must be unchanged"
pass "red report: status shows the problem, Pods field read-only, update refused ($(error_code "$BLOCKED"))"
wp option delete lit_mcp_pods_check_report >/dev/null
pods_component | jq -e '.problems == []' >/dev/null || fail "without the report Pods must be free: $(pods_component)"
tool lit-content-types '{"post_type":"lit_person"}' | jq -e '[.post_types[0].fields[] | select(.key == "role")] | all(.status == "writable")' >/dev/null \
|| fail "free again: the Pods field must be writable"
FREE=$(tool lit-content-update "{\"post_id\":$PERSON_ID,\"rev\":\"$REV\",\"fields\":{\"role\":\"Frei\"}}")
echo "$FREE" | jq -e '.ok == true' >/dev/null || fail "free again: update must work: $FREE"
pass "report removed: status clean, field writable, update works"
# 6. Nothing is left behind
restore
for option in "${OPTIONS[@]}"; do
[ -z "$(stored_option "$option")" ] || fail "option $option is left behind"
done
[ "$(wp plugin is-active pods && echo yes || echo no)" = "$PODS_WAS_ACTIVE" ] || fail "Pods state not restored"
[ "$(wp post list --post_type=lit_person --post_status=any --format=count 2>/dev/null || echo 0)" = "0" ] || fail "fixture entry left"
pass "cleanup: options, entry, fixture pod gone; Pods state restored"
finish