From 239d35c198c8277ddea00f2b932fc56bdc98a1f5 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Fri, 6 Nov 2026 14:08:15 +0100 Subject: [PATCH] =?UTF-8?q?test(compat):=20Pods-Smoke=20f=C3=BCr=20Reiter,?= =?UTF-8?q?=20Pr=C3=BCfroute=20und=20Sperre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prüft gegen echtes Pods in DDEV: Status ohne Probleme, /pods-check speichert einen grünen Bericht im Format mit Schlüssel version, Reiter zeigt die Pods-Sektion, Freigabe nur für ungetestete Versionen und ohne Sperrwirkung, ein roter Bericht sperrt Pods-Felder bis zu seiner Entfernung. Stellt den Ausgangszustand wieder her. Co-Authored-By: Claude Opus 5.5 --- tests/Integration/pods-compat-release.php | 59 ++++++++++ tests/Integration/pods-compat-smoke.sh | 128 ++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 tests/Integration/pods-compat-release.php create mode 100644 tests/Integration/pods-compat-smoke.sh diff --git a/tests/Integration/pods-compat-release.php b/tests/Integration/pods-compat-release.php new file mode 100644 index 0000000..724171c --- /dev/null +++ b/tests/Integration/pods-compat-release.php @@ -0,0 +1,59 @@ + + * + * "real" uses the profile as shipped (3.3.9.2 is tested, so releasing is refused); "untested" is the same profile + * with an empty testedVersions list, which simulates a Pods version the plugin was not tested with. + * No strict_types: wp eval-file runs this through eval(). + */ + +defined('WP_CLI') && WP_CLI || exit; + +use LIT\MCP\Admin\AdminPostAction; +use LIT\MCP\Admin\CompatibilityTab; +use LIT\MCP\Compatibility\Compatibility; +use LIT\MCP\Integrations\Pods\PodsCompatibility; + +[$mode, $profileKind] = [$args[0] ?? '', $args[1] ?? '']; +$real = PodsCompatibility::profile(); +$profile = $profileKind === 'untested' + ? new LIT\MCP\Compatibility\Profile( + id: $real->id, + label: $real->label, + tier: $real->tier, + capabilities: $real->capabilities, + manifests: $real->manifests, + active: $real->active, + version: $real->version, + minVersion: $real->minVersion, + testedVersions: [] + ) + : $real; + +$admins = get_users(['role' => 'administrator', 'number' => 1]); +wp_set_current_user($admins[0]->ID); +$action = $mode === 'withdraw' ? CompatibilityTab::WITHDRAW_ACTION : CompatibilityTab::RELEASE_ACTION; +$_POST['profile'] = 'pods'; +$_REQUEST['_wpnonce'] = wp_create_nonce($action . '_pods'); + +$location = ''; +add_filter('wp_redirect', static function ($url) use (&$location) { + $location = (string) $url; + + return false; // do not send anything: the CLI has no response to redirect. +}); +$tab = new CompatibilityTab( + new Compatibility([$profile], report: Compatibility::storedReport(...)), + null, + new AdminPostAction(static function (): void { + }) +); +$mode === 'withdraw' ? $tab->withdraw() : $tab->release(); + +parse_str((string) wp_parse_url($location, PHP_URL_QUERY), $query); +echo $query['lit_mcp_notice'] ?? 'no-redirect', "\n"; diff --git a/tests/Integration/pods-compat-smoke.sh b/tests/Integration/pods-compat-smoke.sh new file mode 100644 index 0000000..7f30025 --- /dev/null +++ b/tests/Integration/pods-compat-smoke.sh @@ -0,0 +1,128 @@ +#!/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" +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, 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" + +# 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