#!/usr/bin/env bash
# End-to-end checks of Etappe 4e (loop content: loop_* options inside a Divi loop, refused outside and in a loop of
# another kind, dynamic sources per node, move warning, real frontend render) through the OAuth-protected endpoint
# against the real Divi. Runs INSIDE the DDEV web container:
# cd /home/janniclas/Projekte/WordPress && timeout 900 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-loop-smoke.sh
# Runs only with the plugins postlos and asynclistry deactivated (Monolog 2 / deprecation output of the WP-CLI calls).
# Creates one draft page with a post loop, a plain row and a term loop (and one temporary published post when the site
# has none). The EXIT trap deletes the page and the temporary post by exact ID, removes their change-log rows and puts
# the options et_divi and et_divi_global_variables back byte-exact (serialized, base64 through the environment).
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)
RUN="$(date +%s)-$$"
# $1 = option name. An option that does not exist is captured as the sentinel string, so restore() can delete it again.
capture() { wp eval "echo base64_encode(serialize(get_option('$1', '__lit_absent__')));"; }
ORIGINAL_THEME=$(capture et_divi)
ORIGINAL_VARIABLES=$(capture et_divi_global_variables)
# Abort before any change: without a capture there is nothing to restore.
[ -n "$ORIGINAL_THEME" ] || { echo "FAIL: could not capture et_divi"; exit 1; }
[ -n "$ORIGINAL_VARIABLES" ] || { echo "FAIL: could not capture et_divi_global_variables"; exit 1; }
PAGE_ID=""
TEMP_POST_ID=""
restore() { # $1 = option name, $2 = name of the environment variable that holds the capture
local LIT_CAPTURE="$2"; export LIT_CAPTURE
LIT_OPTION="$1" wp eval '$n = getenv("LIT_OPTION"); $v = unserialize(base64_decode(getenv(getenv("LIT_CAPTURE")))); if ($v === "__lit_absent__") { delete_option($n); exit(get_option($n, "__lit_absent__") === $v ? 0 : 1); } update_option($n, $v); exit(serialize(get_option($n)) === serialize($v) ? 0 : 1);' >/dev/null 2>&1 \
|| { echo "FAIL: $1 not restored"; return 1; }
}
delete_content() { # deletes the page and the temporary post by exact ID (with revisions), then their change-log rows
local ids="$PAGE_ID $TEMP_POST_ID"
for id in $ids; do
wp post delete "$id" --force >/dev/null 2>&1 || true
done
LIT_IDS="$ids" wp eval '
global $wpdb;
$table = $wpdb->prefix . "lit_mcp_log";
foreach (array_filter(explode(" ", (string) getenv("LIT_IDS"))) as $id) {
$wpdb->delete($table, ["object_id" => $id]);
}' >/dev/null 2>&1 || true
}
cleanup() {
local failed=0
delete_content
restore et_divi LIT_THEME || failed=1
restore et_divi_global_variables LIT_VARIABLES || failed=1
wp eval 'ET_Core_PageResource::remove_static_resources("all", "all", true);' >/dev/null 2>&1 || true
cleanup_app_passwords_for "$ADMIN_LOGIN"
return "$failed"
}
export LIT_THEME="$ORIGINAL_THEME" LIT_VARIABLES="$ORIGINAL_VARIABLES"
trap 'rc=$?; cleanup || rc=1; guard_exit "$rc" || exit $?' EXIT
[ "$(wp eval 'echo get_template();')" = "Divi" ] || fail "Divi must be the active theme"
connect "$ADMIN_LOGIN"
# --- helpers -----------------------------------------------------------------------------------------------------
# Draft page: s0.r0 = column c0 with a POST loop (queryType post_types, post type "post") and one static text module,
# s0.r1 = column c0 WITHOUT a loop and one static text module (below the loop), s0.r2 = column c0 with a TERM loop
# (queryType post_taxonomies, taxonomy category) and one static text module. Loop attributes exactly as Divi's own
# builder stores them (module.advanced.loop.desktop.value, enable "on").
create_loop_page() { # prints the ID
LIT_TITLE="LIT Smoke $RUN Schleife" wp --user="$ADMIN_LOGIN" eval '
$b = static fn (string $n, array $a, array $k = []): array => ["blockName" => $n, "attrs" => $a, "innerBlocks" => $k,
"innerHTML" => "", "innerContent" => array_fill(0, count($k), null)];
$text = static fn (string $t): array => $b("divi/text", ["content" => ["innerContent" => ["desktop" => ["value" => $t]]]]);
$width = ["module" => ["decoration" => ["sizing" => ["desktop" => ["value" => ["flexType" => "24_24"]]]]]];
$loop = static fn (string $id, string $type, string $sub, string $label): array => array_replace_recursive($width, [
"module" => ["advanced" => ["loop" => ["desktop" => ["value" => ["enable" => "on", "loopId" => $id,
"queryType" => $type, "subTypes" => [["value" => $sub, "label" => $label]], "postPerPage" => "3"]]]]]]);
$row = static fn (array $column): array => $b("divi/row", [], [$column]);
$section = $b("divi/section", [], [
$row($b("divi/column", $loop("loop-litsmoke01", "post_types", "post", "Post"), [$text("