#!/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("

Schleife

")])), $row($b("divi/column", $width, [$text("

Ausserhalb

")])), $row($b("divi/column", $loop("loop-litsmoke02", "post_taxonomies", "category", "Categories"), [$text("

Terme

")])), ]); $id = wp_insert_post(["post_type" => "page", "post_status" => "draft", "post_title" => getenv("LIT_TITLE"), "post_content" => wp_slash(serialize_block($b("divi/placeholder", [], [$section])))]); update_post_meta($id, "_et_pb_use_builder", "on"); update_post_meta($id, "_et_pb_use_divi_5", "on"); echo $id;' 2>/dev/null } stored() { wp post get "$PAGE_ID" --field=post_content; } # Substring tests without a pipe: with pipefail, grep -q closing the pipe early would fail the pipeline (SIGPIPE). stored_has() { local s; s=$(stored); [[ "$s" == *"$1"* ]]; } has_line() { grep -qx -- "$2" <<<"$1"; } # $1 = lines, $2 = exact line has_prefix() { grep -q -- "^$2" <<<"$1"; } tree_rev() { tool lit-divi-outline "{\"post_id\":$PAGE_ID}" | jq -r '.tree_rev'; } # insert : one text module with the given content (short form or $variable string). insert_text() { tool lit-divi-insert "$(jq -cn --argjson p "$PAGE_ID" --arg r "$(tree_rev)" --arg parent "$1" --arg c "$2" \ '{post_id: $p, tree_rev: $r, parent: $parent, nodes: [{type: "text", content: $c}]}')" } # refused_with : the call was refused (an error, not the dry run of anything) and the error names . refused_with() { echo "$1" | jq -e --arg t "$2" 'has("__error") and (.__error | contains($t))' >/dev/null } # variable_ref