Files
WP-LIT-MCP/tests/Integration/divi-release-smoke.sh
T

220 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# End-to-end checks of the Divi release gate (spec 2026-09-29-divi-absicherung) and its per-feature matrix
# (plan 2026-10-18-divi-faehigkeiten). Runs INSIDE the DDEV web container (Divi 5.15.1 must be the active theme):
# cd /home/janniclas/Projekte/WordPress && timeout 900 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-release-smoke.sh
# Leaves nothing behind: pages, the check report, the check lock, the state transient and the application password
# are removed again. Divi's files and fingerprints.json are never touched: a deviation is simulated by adding a
# mirror problem to the cached state (transient lit_mcp_divi_state, which only ever locks more, never less).
# Only runs with the plugins postlos and asynclistry deactivated (Monolog 2 / deprecation output of WP-CLI).
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)
CREATED_POSTS=()
BASE=http://localhost
cleanup() {
for id in "${CREATED_POSTS[@]}"; do
wp post delete "$id" --force >/dev/null 2>&1 || true
done
wp option delete lit_mcp_divi_check_report lit_mcp_divi_check_lock >/dev/null 2>&1 || true
wp transient delete lit_mcp_divi_state >/dev/null 2>&1 || true
cleanup_app_passwords_for "$ADMIN_LOGIN"
}
trap 'rc=$?; cleanup; guard_exit "$rc" || exit $?' EXIT
check_pages() { wp post list --post_type=page --post_status=any --meta_key=_lit_mcp_check_page --format=count; }
# The ids of the sixteen features (plan decision D1, plan 4c V2, plan 4d W1), sorted.
FEATURE_IDS='["catalog","color-write","design-read","dynamic-sources","font-upload","library-read","library-write","page-read","page-write","preset-write","roles","tb-read","tb-write","theme-option-read","theme-option-write","variable-write"]'
# Adds a problem to one feature in the cached state, as if its mirror check had failed. Needs the cache entry of
# ReleaseResolver (format 2: ["format" => 2, "key" => stateKey, "problems" => list of Problem::toArray()]); the key
# keeps the entry valid for this Divi version. Fails loudly when the cache has another format. The problem is of
# kind "mirror" (hard: locks its feature whatever the release), so it can only lock more, never release.
simulate_problem() { # $1 = feature id, $2 = problem text
FEATURE="$1" PROBLEM="$2" wp eval '
$cache = LIT\MCP\Integrations\Divi\Release\ReleaseResolver::CACHE;
$key = LIT\MCP\Integrations\Divi\Release\ReleaseResolver::stateKey(
(string) LIT\MCP\Integrations\Divi\Release\DiviEnvironment::version()
);
$entry = get_transient($cache);
if (! is_array($entry) || ($entry["format"] ?? null) !== 2 || ($entry["key"] ?? null) !== $key
|| ! is_array($entry["problems"] ?? null)) {
fwrite(STDERR, "state cache missing or in another format: adapt simulate_problem()\n");
exit(1);
}
$entry["problems"][] = ["label" => getenv("PROBLEM"), "kind" => "mirror", "features" => [getenv("FEATURE")]];
set_transient($cache, $entry, 600);' || fail "could not simulate a problem for $1"
}
[ "$(wp eval 'echo get_template();')" = "Divi" ] || fail "Divi must be the active theme"
# 1. Code, registrations and fingerprints match Divi 5.15.1
[ "$(wp eval 'echo wp_json_encode((new LIT\MCP\Integrations\Divi\DiviCompatibility())->problems());')" = "[]" ] \
|| fail "Divi compatibility problems under WP-CLI"
pass "manifests and fingerprints match the installed Divi"
connect "$ADMIN_LOGIN"
STATUS=$(tool lit-site-status '{}')
echo "$STATUS" | jq -e '.components[] | select(.id == "divi") | .problems == [] and .release.state == "released" and .release.source == "code"' >/dev/null \
|| fail "site status must show Divi released from code: $STATUS"
pass "lit-site-status: Divi 5.15.1 released (code), no problems in an MCP request"
# 1b. The matrix: every feature released from code, none with problems, and the tools each feature affects.
echo "$STATUS" | jq -e --argjson ids "$FEATURE_IDS" '
.components[] | select(.id == "divi") | .features
| ([.[].id] | sort) == $ids
and all(.state == "released" and .source == "code" and .problems == [])' >/dev/null \
|| fail "all sixteen features must be released from code without problems: $STATUS"
echo "$STATUS" | jq -e '
.components[] | select(.id == "divi") | .features
| (.[] | select(.id == "page-write") | .tools | any(. == "lit-divi-insert"))
and (.[] | select(.id == "page-read") | .tools | any(. == "lit-divi-outline"))
and (.[] | select(.id == "preset-write") | .tools | any(startswith("lit/divi-presets-")))
and (.[] | select(.id == "page-read") | .label == "Seiten lesen")' >/dev/null \
|| fail "features must name their tools (direct lit-<name>, indirect lit/<name>): $STATUS"
pass "lit-site-status: sixteen features released (code), each with its tools"
# 2. Admin check through the REST route (application password instead of the cookie nonce).
# X-Forwarded-Proto: https is required here: without it the request to http://localhost with the
# site's own Host header never reaches the route (a 404 before any auth check even runs), the same
# header mcp-smoke.sh and tb-smoke.sh already send for their own curl calls against this DDEV site.
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/divi-check")
echo "$HEADERS" | grep -qi '^HTTP/[0-9.]* 303' || fail "check route must answer 303: $HEADERS"
echo "$HEADERS" | grep -qi '^location:.*tab=divi.*lit_mcp_notice=divi_checked' || fail "redirect to the tab: $HEADERS"
REPORT=$(wp option get lit_mcp_divi_check_report --format=json)
echo "$REPORT" | jq -e '[.steps[] | (.problems == [] and .skipped == false)] | all and length == 5' >/dev/null \
|| fail "every check step must be green: $REPORT"
[ "$(check_pages)" = "0" ] || fail "the throwaway page must be gone"
[ "$(wp option get lit_mcp_divi_check_lock 2>/dev/null || true)" = "" ] || fail "the check lock must be released"
pass "admin check (REST): five green steps, throwaway page deleted, lock 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/divi-check")
[ "$UNAUTH" = "401" ] || [ "$UNAUTH" = "403" ] || fail "anonymous check must be refused, got $UNAUTH"
pass "check route refuses anonymous requests"
# 3. One incompatible feature (simulated: a failed check for "page-write" in the state cache) locks exactly the tools
# that need it. First make sure the cache entry exists (a status call fills it), then add the problem.
wp transient delete lit_mcp_divi_state >/dev/null 2>&1 || true
tool lit-site-status '{}' >/dev/null
PROBLEM_TEXT="Smoke: Seiten schreiben geändert"
simulate_problem page-write "$PROBLEM_TEXT"
PAGE_ID=$(wp --user="$ADMIN_LOGIN" post create --post_type=page --post_status=draft --post_title="LIT Freigabe Smoke" --porcelain)
CREATED_POSTS+=("$PAGE_ID")
wp post meta update "$PAGE_ID" _et_pb_use_builder on >/dev/null
wp post meta update "$PAGE_ID" _et_pb_use_divi_5 on >/dev/null
BEFORE=$(wp post get "$PAGE_ID" --field=post_content)
# Reading needs "page-read" only: it runs, without a lock notice (the old blanket warning is gone).
OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE_ID}")
echo "$OUTLINE" | jq -e 'has("__error") | not' >/dev/null || fail "reading must work while only page-write is locked: $OUTLINE"
echo "$OUTLINE" | jq -e '(.warnings // []) | all(test("inkompatibel|ungetestet") | not)' >/dev/null \
|| fail "reading carries no lock notice: $OUTLINE"
for read_tool in lit-divi-catalog lit-divi-design-system; do
READ=$(tool "$read_tool" '{}')
echo "$READ" | jq -e 'has("__error") | not' >/dev/null || fail "$read_tool must keep working: $READ"
done
pass "reading Divi tools run while page-write is locked (outline, catalog, design system)"
INSERT=$(tool lit-divi-insert "{\"post_id\":$PAGE_ID,\"tree_rev\":$(echo "$OUTLINE" | jq '.tree_rev'),\"nodes\":[{\"type\":\"section\",\"modules\":[{\"type\":\"text\",\"content\":\"<p>x</p>\"}]}]}")
[ "$(error_code "$INSERT")" = "divi_incompatible" ] || fail "write must be refused with divi_incompatible: $INSERT"
echo "$INSERT" | jq -e '.__error | fromjson | ((.features // []) | any(.id == "page-write"))
and (((.message // "") + " " + (.hint // "")) | contains("Seiten schreiben"))' >/dev/null \
|| fail "the refusal must name the feature page-write (D6): $INSERT"
[ "$(wp post get "$PAGE_ID" --field=post_content)" = "$BEFORE" ] || fail "nothing may be written"
pass "lit-divi-insert refused with divi_incompatible naming page-write, page unchanged"
CREATE=$(tool lit-content-create '{"post_type":"page","title":"LIT Freigabe Smoke Neu","divi":[{"type":"section","modules":[{"type":"text","content":"<p>x</p>"}]}]}')
[ "$(error_code "$CREATE")" = "divi_incompatible" ] || fail "content-create with divi must be refused: $CREATE"
[ "$(wp post list --post_type=page --post_status=any --title='LIT Freigabe Smoke Neu' --format=count)" = "0" ] \
|| fail "no draft may remain"
pass "lit-content-create with divi refused before a draft exists"
PLAIN=$(tool lit-content-create '{"post_type":"page","title":"LIT Freigabe Smoke Ohne Divi"}')
PLAIN_ID=$(echo "$PLAIN" | jq -r '.changed[0].id // empty')
[ -n "$PLAIN_ID" ] || fail "content-create without divi must keep working: $PLAIN"
CREATED_POSTS+=("$PLAIN_ID")
pass "lit-content-create without divi still works"
STATUS=$(tool lit-site-status '{}')
echo "$STATUS" | jq -e --arg problem "$PROBLEM_TEXT" '
.components[] | select(.id == "divi") | .features
| (.[] | select(.id == "page-write") | .state == "incompatible" and .source == null
and (.problems | any(. == $problem)) and (.tools | any(. == "lit-divi-insert")))
and ([.[] | select(.id != "page-write") | .state] | all(. == "released"))' >/dev/null \
|| fail "site status must show only page-write incompatible: $STATUS"
# wp db query --skip-column-names misreports this as a write query (it treats "insert" anywhere in the SQL,
# even inside a quoted value like 'lit/divi-insert', as an INSERT and prints "Rows affected" instead of the
# result) - wp eval with $wpdb->get_var() runs the same COUNT() reliably.
REFUSED_LOGGED=$(wp eval '
global $wpdb;
echo (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}lit_mcp_log WHERE result = %s AND ability = %s",
"refused",
"lit/divi-insert"
));')
[ "$REFUSED_LOGGED" -ge 1 ] || fail "the refusal must be in the change log"
pass "site status: only page-write incompatible (problem and tools named), refusal logged"
# The admin sees the same table and a notice with a link to the tab (rendered here without a browser).
# The tab is taken from the settings page filter, as the plugin wires it (with the tools of every integration).
NOTICE=$(wp --user="$ADMIN_LOGIN" eval '$tab = null;
foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) {
if ($candidate instanceof LIT\MCP\Integrations\Divi\Admin\DiviTab) {
$tab = $candidate;
}
}
$tab ?? exit(1);
$tab->renderNotice();')
echo "$NOTICE" | grep -q 'Seiten schreiben' || fail "admin notice must name the feature: $NOTICE"
echo "$NOTICE" | grep -q 'tab=divi' || fail "admin notice must link to the Divi tab: $NOTICE"
TAB=$(wp --user="$ADMIN_LOGIN" eval '
require_once ABSPATH . "wp-admin/includes/template.php";
$tab = null;
foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) {
if ($candidate instanceof LIT\MCP\Integrations\Divi\Admin\DiviTab) {
$tab = $candidate;
}
}
$tab ?? exit(1);
$tab->render();')
echo "$TAB" | grep -q 'lit-mcp-divi-features' || fail "Divi tab must list the features: $TAB"
echo "$TAB" | grep -q 'lit-divi-insert' || fail "Divi tab must name the affected tools: $TAB"
echo "$TAB" | grep -q 'lit_mcp_divi_auto_release' || fail "Divi tab must explain the filter: $TAB"
pass "admin notice and Divi tab show the incompatible feature with its tools"
# 4. Back to released: writes work again
wp transient delete lit_mcp_divi_state >/dev/null
OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE_ID}")
INSERT=$(tool lit-divi-insert "{\"post_id\":$PAGE_ID,\"tree_rev\":$(echo "$OUTLINE" | jq '.tree_rev'),\"nodes\":[{\"type\":\"section\",\"modules\":[{\"type\":\"text\",\"content\":\"<p>x</p>\"}]}]}")
echo "$INSERT" | jq -e '.ok == true' >/dev/null || fail "write must work when released: $INSERT"
STATUS=$(tool lit-site-status '{}')
echo "$STATUS" | jq -e '.components[] | select(.id == "divi") | .features | all(.state == "released")' >/dev/null \
|| fail "every feature must be released again: $STATUS"
[ -z "$(wp --user="$ADMIN_LOGIN" eval '$tab = null;
foreach (apply_filters(LIT\MCP\Admin\SettingsPage::TABS_FILTER, []) as $candidate) {
if ($candidate instanceof LIT\MCP\Integrations\Divi\Admin\DiviTab) {
$tab = $candidate;
}
}
$tab ?? exit(1);
$tab->renderNotice();')" ] \
|| fail "no admin notice while every feature is usable"
pass "released again: writing works, matrix green, no admin notice"
# 5. WP-CLI: prints the matrix, checks fresh (a poisoned cache is not trusted) and exits 0 when all is usable
simulate_problem page-write "Smoke: Cache vergiftet"
CLI=$(wp --user="$ADMIN_LOGIN" lit-mcp divi-check 2>&1) || fail "wp lit-mcp divi-check must succeed (exit 0): $CLI"
echo "$CLI" | grep -q '^Fähigkeiten:' || fail "divi-check must print the matrix: $CLI"
echo "$CLI" | grep -q '✓ Seiten schreiben – freigegeben (code)' || fail "divi-check must show page-write released: $CLI"
[ "$(echo "$CLI" | grep -c ' – freigegeben (')" = "16" ] || fail "divi-check must show sixteen released features: $CLI"
[ "$(check_pages)" = "0" ] || fail "the CLI check must delete its page"
pass "wp lit-mcp divi-check prints sixteen released features, recomputes the state and leaves no page"
finish