test: Smoke prüft die Fähigkeiten-Matrix in lit-site-status, Ablehnung nur der betroffenen Werkzeuge, Admin-Hinweis und divi-check
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
20ee173cd0
commit
39143c01d0
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# End-to-end checks of the Divi release gate (spec 2026-09-29-divi-absicherung).
|
# End-to-end checks of the Divi release gate (spec 2026-09-29-divi-absicherung) and its per-feature matrix
|
||||||
# Runs INSIDE the DDEV web container (Divi 5.2.1 must be the active theme):
|
# (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
|
# 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
|
# Leaves nothing behind: pages, the check report, the check lock, the state transient and the application password
|
||||||
# are removed again.
|
# 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
|
set -euo pipefail
|
||||||
cd /var/www/html
|
cd /var/www/html
|
||||||
# shellcheck source=lib.sh
|
# shellcheck source=lib.sh
|
||||||
@@ -25,9 +27,32 @@ 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; }
|
check_pages() { wp post list --post_type=page --post_status=any --meta_key=_lit_mcp_check_page --format=count; }
|
||||||
|
|
||||||
|
# The ids of the eleven features (plan decision D1), sorted.
|
||||||
|
FEATURE_IDS='["catalog","design-read","dynamic-sources","library-read","library-write","page-read","page-write","preset-write","roles","tb-read","tb-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"
|
[ "$(wp eval 'echo get_template();')" = "Divi" ] || fail "Divi must be the active theme"
|
||||||
|
|
||||||
# 1. Code, registrations and fingerprints match Divi 5.2.1
|
# 1. Code, registrations and fingerprints match Divi 5.15.1
|
||||||
[ "$(wp eval 'echo wp_json_encode((new LIT\MCP\Integrations\Divi\DiviCompatibility())->problems());')" = "[]" ] \
|
[ "$(wp eval 'echo wp_json_encode((new LIT\MCP\Integrations\Divi\DiviCompatibility())->problems());')" = "[]" ] \
|
||||||
|| fail "Divi compatibility problems under WP-CLI"
|
|| fail "Divi compatibility problems under WP-CLI"
|
||||||
pass "manifests and fingerprints match the installed Divi"
|
pass "manifests and fingerprints match the installed Divi"
|
||||||
@@ -36,7 +61,22 @@ connect "$ADMIN_LOGIN"
|
|||||||
STATUS=$(tool lit-site-status '{}')
|
STATUS=$(tool lit-site-status '{}')
|
||||||
echo "$STATUS" | jq -e '.components[] | select(.id == "divi") | .problems == [] and .release.state == "released" and .release.source == "code"' >/dev/null \
|
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"
|
|| fail "site status must show Divi released from code: $STATUS"
|
||||||
pass "lit-site-status: Divi 5.2.1 released (code), no problems in an MCP request"
|
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 eleven 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: eleven features released (code), each with its tools"
|
||||||
|
|
||||||
# 2. Admin check through the REST route (application password instead of the cookie nonce).
|
# 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
|
# X-Forwarded-Proto: https is required here: without it the request to http://localhost with the
|
||||||
@@ -57,10 +97,12 @@ UNAUTH=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Host: $HOST" -H 'X-F
|
|||||||
[ "$UNAUTH" = "401" ] || [ "$UNAUTH" = "403" ] || fail "anonymous check must be refused, got $UNAUTH"
|
[ "$UNAUTH" = "401" ] || [ "$UNAUTH" = "403" ] || fail "anonymous check must be refused, got $UNAUTH"
|
||||||
pass "check route refuses anonymous requests"
|
pass "check route refuses anonymous requests"
|
||||||
|
|
||||||
# 3. Incompatible Divi (simulated: a failed fingerprint in the state cache) locks every write path
|
# 3. One incompatible feature (simulated: a failed check for "page-write" in the state cache) locks exactly the tools
|
||||||
wp eval '
|
# that need it. First make sure the cache entry exists (a status call fills it), then add the problem.
|
||||||
$key = LIT\MCP\Integrations\Divi\Release\ReleaseResolver::stateKey((string) LIT\MCP\Integrations\Divi\Release\DiviEnvironment::version());
|
wp transient delete lit_mcp_divi_state >/dev/null 2>&1 || true
|
||||||
set_transient(LIT\MCP\Integrations\Divi\Release\ReleaseResolver::CACHE, ["key" => $key, "problems" => ["Smoke: Nachbau geändert"]], 600);'
|
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)
|
PAGE_ID=$(wp --user="$ADMIN_LOGIN" post create --post_type=page --post_status=draft --post_title="LIT Freigabe Smoke" --porcelain)
|
||||||
CREATED_POSTS+=("$PAGE_ID")
|
CREATED_POSTS+=("$PAGE_ID")
|
||||||
@@ -68,24 +110,44 @@ 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
|
wp post meta update "$PAGE_ID" _et_pb_use_divi_5 on >/dev/null
|
||||||
BEFORE=$(wp post get "$PAGE_ID" --field=post_content)
|
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}")
|
OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE_ID}")
|
||||||
echo "$OUTLINE" | jq -e '.warnings | any(test("inkompatibel"))' >/dev/null || fail "read tool must carry the notice: $OUTLINE"
|
echo "$OUTLINE" | jq -e 'has("__error") | not' >/dev/null || fail "reading must work while only page-write is locked: $OUTLINE"
|
||||||
pass "reading Divi tool runs and warns"
|
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>\"}]}]}")
|
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 '.__error | fromjson | .code == "divi_incompatible"' >/dev/null || fail "write must be refused: $INSERT"
|
[ "$(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"
|
[ "$(wp post get "$PAGE_ID" --field=post_content)" = "$BEFORE" ] || fail "nothing may be written"
|
||||||
pass "writing Divi tool refused with divi_incompatible, page unchanged"
|
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>"}]}]}')
|
CREATE=$(tool lit-content-create '{"post_type":"page","title":"LIT Freigabe Smoke Neu","divi":[{"type":"section","modules":[{"type":"text","content":"<p>x</p>"}]}]}')
|
||||||
echo "$CREATE" | jq -e '.__error | fromjson | .code == "divi_incompatible"' >/dev/null || fail "content-create with divi must be refused: $CREATE"
|
[ "$(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" ] \
|
[ "$(wp post list --post_type=page --post_status=any --title='LIT Freigabe Smoke Neu' --format=count)" = "0" ] \
|
||||||
|| fail "no draft may remain"
|
|| fail "no draft may remain"
|
||||||
pass "lit-content-create with divi refused before a draft exists"
|
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 '{}')
|
STATUS=$(tool lit-site-status '{}')
|
||||||
echo "$STATUS" | jq -e '.components[] | select(.id == "divi") | .release.state == "incompatible"' >/dev/null \
|
echo "$STATUS" | jq -e --arg problem "$PROBLEM_TEXT" '
|
||||||
|| fail "site status must show incompatible: $STATUS"
|
.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,
|
# 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
|
# 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.
|
# result) - wp eval with $wpdb->get_var() runs the same COUNT() reliably.
|
||||||
@@ -97,18 +159,61 @@ echo (int) $wpdb->get_var($wpdb->prepare(
|
|||||||
"lit/divi-insert"
|
"lit/divi-insert"
|
||||||
));')
|
));')
|
||||||
[ "$REFUSED_LOGGED" -ge 1 ] || fail "the refusal must be in the change log"
|
[ "$REFUSED_LOGGED" -ge 1 ] || fail "the refusal must be in the change log"
|
||||||
pass "site status incompatible, refusal logged"
|
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\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\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
|
# 4. Back to released: writes work again
|
||||||
wp transient delete lit_mcp_divi_state >/dev/null
|
wp transient delete lit_mcp_divi_state >/dev/null
|
||||||
OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE_ID}")
|
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>\"}]}]}")
|
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"
|
echo "$INSERT" | jq -e '.ok == true' >/dev/null || fail "write must work when released: $INSERT"
|
||||||
pass "released again: writing works"
|
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\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
|
# 5. WP-CLI: prints the matrix, checks fresh (a poisoned cache is not trusted) and exits 0 when all is usable
|
||||||
wp --user="$ADMIN_LOGIN" lit-mcp divi-check >/dev/null || fail "wp lit-mcp divi-check must succeed"
|
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 (')" = "11" ] || fail "divi-check must show eleven released features: $CLI"
|
||||||
[ "$(check_pages)" = "0" ] || fail "the CLI check must delete its page"
|
[ "$(check_pages)" = "0" ] || fail "the CLI check must delete its page"
|
||||||
pass "wp lit-mcp divi-check is green and leaves no page"
|
pass "wp lit-mcp divi-check prints eleven released features, recomputes the state and leaves no page"
|
||||||
|
|
||||||
finish
|
finish
|
||||||
|
|||||||
Reference in New Issue
Block a user