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

253 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
# End-to-end checks of Etappe 4b (assign presets to modules, overrides, mass assignment, detach, delete) 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-presets-assign-smoke.sh
# Runs only with the plugins postlos and asynclistry deactivated (Monolog 2 / deprecation output of the WP-CLI calls).
# Creates presets named "LIT Smoke <run> …" and two draft pages with one button each; the EXIT trap restores the
# preset inventory taken before the first change and deletes the pages (with revisions) by exact ID.
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)-$$"
PREFIX="LIT Smoke $RUN"
ORIGINAL_PRESETS=$(wp eval 'echo wp_json_encode(ET\Builder\Packages\GlobalData\GlobalPreset::get_data());' 2>/dev/null)
PAGE1=""
PAGE2=""
NODE="s0.r0.c0.m0"
CONTENT='<!-- wp:divi/placeholder --><!-- wp:divi/section --><!-- wp:divi/row --><!-- wp:divi/column {"module":{"decoration":{"sizing":{"desktop":{"value":{"flexType":"24_24"}}}}}} --><!-- wp:divi/button {"button":{"innerContent":{"desktop":{"value":{"text":"Los"}}}}} /--><!-- /wp:divi/column --><!-- /wp:divi/row --><!-- /wp:divi/section --><!-- /wp:divi/placeholder -->'
cleanup() {
wp eval '$d = json_decode(getenv("LIT_PRESETS"), true); et_update_option(ET\Builder\Packages\GlobalData\GlobalPreset::option_name(), $d, false, "", "", true);' \
--user="$ADMIN_LOGIN" >/dev/null 2>&1 || echo "WARN: preset inventory not restored"
for id in $PAGE1 $PAGE2; do
wp post delete "$id" --force >/dev/null 2>&1 || true
done
# Change-log rows about the test pages.
LIT_IDS="$PAGE1 $PAGE2" wp eval '
global $wpdb;
$table = $wpdb->prefix . "lit_mcp_log";
foreach (array_filter(explode(" ", (string) getenv("LIT_IDS"))) as $id) {
$wpdb->delete($table, ["object_type" => "page", "object_id" => $id]);
}' >/dev/null 2>&1 || true
cleanup_app_passwords_for "$ADMIN_LOGIN"
}
export LIT_PRESETS="$ORIGINAL_PRESETS"
trap 'rc=$?; cleanup; guard_exit "$rc" || exit $?' EXIT
connect "$ADMIN_LOGIN"
create_page() { # $1 = title -> prints the ID of a draft Divi 5 page with one button without preset
LIT_TITLE="$1" LIT_CONTENT="$CONTENT" wp --user="$ADMIN_LOGIN" eval '
$id = wp_insert_post(["post_type" => "page", "post_status" => "draft", "post_title" => getenv("LIT_TITLE"),
"post_content" => wp_slash((string) getenv("LIT_CONTENT"))]);
update_post_meta($id, "_et_pb_use_builder", "on");
update_post_meta($id, "_et_pb_use_divi_5", "on");
echo $id;' 2>/dev/null
}
button_attrs() { # $1 = post ID -> attributes (JSON) of the first divi/button block as stored
LIT_ID="$1" wp eval '
$find = static function (array $blocks) use (&$find) {
foreach ($blocks as $b) {
if ($b["blockName"] === "divi/button") { return $b["attrs"]; }
$r = $find($b["innerBlocks"]);
if ($r !== null) { return $r; }
}
return null;
};
echo wp_json_encode($find(parse_blocks(get_post((int) getenv("LIT_ID"))->post_content)));' 2>/dev/null
}
revisions_of() { # $1 = post ID -> number of revisions
LIT_ID="$1" wp eval 'echo count(wp_get_post_revisions((int) getenv("LIT_ID")));' 2>/dev/null
}
preset_stored() { # $1 = module|group, $2 = divi/<name>, $3 = preset ID -> exit 0 when Divi stores it
LIT_KIND="$1" LIT_SCOPE="$2" LIT_PID="$3" wp eval '
$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data();
exit(isset($d[getenv("LIT_KIND")][getenv("LIT_SCOPE")]["items"][getenv("LIT_PID")]) ? 0 : 1);' 2>/dev/null
}
preset_item() { # $1 = module preset ID -> the stored button preset item (JSON)
LIT_PID="$1" wp eval '
$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data();
echo wp_json_encode($d["module"]["divi/button"]["items"][getenv("LIT_PID")] ?? null);' 2>/dev/null
}
nested_groups() { # $1 = module preset ID -> groupPresets (JSON) of that button preset
LIT_PID="$1" wp eval '
$d = ET\Builder\Packages\GlobalData\GlobalPreset::get_data();
echo wp_json_encode($d["module"]["divi/button"]["items"][getenv("LIT_PID")]["groupPresets"] ?? new stdClass());' 2>/dev/null
}
timed() { # $1 = label; runs the rest of the arguments and prints the duration to stderr
local label=$1 start end
shift
start=$(date +%s.%N)
"$@"
end=$(date +%s.%N)
echo "time - $label: $(awk -v s="$start" -v e="$end" 'BEGIN { printf "%.2f", e - s }') s" >&2
}
details() { # $1 = tool()/ability() result -> details object of an error, or the result itself
echo "$1" | jq -c 'if has("__error") then ((.__error | fromjson?) // {}) else . end'
}
# Runs lit/divi-presets-set with dry run and confirm; sets RESULT. $1 = input JSON without confirm.
presets_set() {
local dry rev
dry=$(ability lit/divi-presets-set "$1")
[ "$(error_code "$dry")" = "confirm_required" ] || fail "presets-set dry run: $dry"
rev=$(echo "$dry" | jq -r '.__error | fromjson | .design_rev')
RESULT=$(ability lit/divi-presets-set "$(echo "$1" | jq -c --arg r "$rev" '. + {confirm: true, design_rev: $r}')")
[ "$(echo "$RESULT" | jq -r '.ok')" = "true" ] || fail "presets-set confirm: $RESULT"
}
preset_id() { # $1 = module|group, $2 = short name ("button", "font"), $3 = preset name -> prints the ID
local id
id=$(ability lit/divi-preset-get "$(jq -cn --arg k "$1" --arg s "$2" --arg p "$3" '{($k): $s, preset: $p}')" | jq -r '.id // empty')
[ -n "$id" ] || fail "preset $3 not found"
echo "$id"
}
tree_rev() { # $1 = post ID
tool lit-divi-outline "{\"post_id\":$1}" | jq -r '.tree_rev'
}
# 1. Two draft pages with one button without preset each.
PAGE1=$(create_page "$PREFIX Seite 1")
PAGE2=$(create_page "$PREFIX Seite 2")
[[ $PAGE1 =~ ^[1-9][0-9]*$ && $PAGE2 =~ ^[1-9][0-9]*$ ]] || fail "pages not created"
for id in "$PAGE1" "$PAGE2"; do
button_attrs "$id" | jq -e '(.modulePreset // null) == null and (.groupPreset // null) == null' >/dev/null \
|| fail "page $id starts with a preset"
done
pass "two draft pages with a button without preset ($PAGE1, $PAGE2)"
# 2. Presets: two button module presets (A, B) and a font group preset (G); B nests G.
presets_set "$(jq -cn --arg a "$PREFIX A" --arg b "$PREFIX B" --arg g "$PREFIX G" '{items: [
{module: "button", name: $a, attrs: {"button.decoration.background": {desktop: {color: "#123456"}}}},
{module: "button", name: $b, attrs: {"button.decoration.background": {desktop: {color: "#654321"}}}},
{group: "font", name: $g, source_module: "button",
attrs: {"button.decoration.font.font": {desktop: {weight: "700"}}}}]}')"
PRESET_A=$(preset_id module button "$PREFIX A")
PRESET_B=$(preset_id module button "$PREFIX B")
PRESET_G=$(preset_id group font "$PREFIX G")
presets_set "$(jq -cn --arg b "$PREFIX B" --arg g "$PREFIX G" '{items: [
{module: "button", preset: $b, group_presets: {font: $g}}]}')"
nested_groups "$PRESET_B" | jq -e --arg g "$PRESET_G" '."button.decoration.font".presetId == [$g]' >/dev/null \
|| fail "preset B does not nest group preset G"
pass "module presets A, B and group preset G created, B nests G"
# 3. A stack and a group preset on page 1 through lit-divi-update.
TREE=$(tree_rev "$PAGE1")
UPDATE=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE1" --arg rev "$TREE" --arg n "$NODE" --arg a "$PREFIX A" \
--arg b "$PREFIX B" --arg g "$PREFIX G" '{post_id: $id, tree_rev: $rev, changes: [
{id: $n, preset: [$a, $b], group_presets: {font: $g}}]}')")
echo "$UPDATE" | jq -e '.ok == true and (.tree_rev | length) > 0 and ((.warnings // []) | map(select(contains("überdecken"))) | length) == 0' \
>/dev/null || fail "update with stack and group preset: $UPDATE"
button_attrs "$PAGE1" | jq -e --arg a "$PRESET_A" --arg b "$PRESET_B" --arg g "$PRESET_G" \
'.modulePreset == [$a, $b] and .groupPreset == {"button.decoration.font": {presetId: [$g], groupName: "divi/font"}}' >/dev/null \
|| fail "stored modulePreset/groupPreset of page 1: $(button_attrs "$PAGE1")"
OUTLINE=$(tool lit-divi-outline "{\"post_id\":$PAGE1}")
echo "$OUTLINE" | jq -e --arg n "$NODE" --arg g "font: $PREFIX G" \
'any(.nodes[]; .id == $n and ((.group_presets // []) | index($g) != null)) and (.outline | contains("Gruppen-Presets: " + $g))' \
>/dev/null || fail "outline lacks the group preset: $OUTLINE"
pass "stack [A, B] and group preset stored; outline shows the group preset"
# 4. Overrides: a differing local value is reported, clear_overrides removes it; a non-boolean value is refused.
TREE=$(tree_rev "$PAGE2")
UPDATE=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE2" --arg rev "$TREE" --arg n "$NODE" --arg a "$PREFIX A" \
'{post_id: $id, tree_rev: $rev, changes: [
{id: $n, preset: $a, attrs: {"button.decoration.background": {desktop: {color: "#abcdef"}}}}]}')")
echo "$UPDATE" | jq -e --arg n "$NODE" --arg a "$PREFIX A" \
'.ok == true and ((.warnings // []) | map(select(contains($n) and contains("überdecken") and contains($a) and contains("clear_overrides"))) | length) == 1' \
>/dev/null || fail "no override warning: $UPDATE"
button_attrs "$PAGE2" | jq -e --arg a "$PRESET_A" '.modulePreset == [$a] and .button.decoration.background != null' >/dev/null \
|| fail "page 2 should keep its local background: $(button_attrs "$PAGE2")"
TREE=$(echo "$UPDATE" | jq -r '.tree_rev')
BAD=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE2" --arg rev "$TREE" --arg n "$NODE" \
'{post_id: $id, tree_rev: $rev, changes: [{id: $n, clear_overrides: "ja"}]}')")
# The input schema (type boolean) refuses it before the tool runs, so the error carries no tool code.
echo "$BAD" | jq -e 'has("__error") and (.__error | contains("clear_overrides"))' >/dev/null \
|| fail "non-boolean clear_overrides: $BAD"
CLEARED=$(tool lit-divi-update "$(jq -cn --argjson id "$PAGE2" --arg rev "$TREE" --arg n "$NODE" --arg a "$PREFIX A" \
'{post_id: $id, tree_rev: $rev, changes: [{id: $n, preset: $a, clear_overrides: true}]}')")
echo "$CLEARED" | jq -e '.ok == true and ((.warnings // []) | map(select(contains("überdecken"))) | length) == 0' >/dev/null \
|| fail "clear_overrides: $CLEARED"
button_attrs "$PAGE2" | jq -e --arg a "$PRESET_A" '.modulePreset == [$a] and (.button.decoration.background // null) == null
and .button.innerContent.desktop.value.text == "Los"' >/dev/null \
|| fail "clear_overrides left or removed the wrong values: $(button_attrs "$PAGE2")"
pass "override warning, clear_overrides removes exactly the covering value, non-boolean refused by the schema"
# 5. Mass assignment over both pages: stack [B] and group preset G.
ASSIGN=$(jq -cn --argjson p1 "$PAGE1" --argjson p2 "$PAGE2" --arg b "$PREFIX B" --arg g "$PREFIX G" \
'{module: "button", preset: $b, group_presets: {font: $g}, post_ids: [$p1, $p2]}')
NONE=$(ability lit/divi-presets-assign "$(echo "$ASSIGN" | jq -c '. + {only_without_preset: true}')")
details "$NONE" | jq -e '(.pages_count // 0) == 0' >/dev/null || fail "only_without_preset should match no page: $NONE"
REVISIONS_BEFORE=$(revisions_of "$PAGE1")
DRY=$(timed "assign dry run" ability lit/divi-presets-assign "$ASSIGN")
[ "$(error_code "$DRY")" = "confirm_required" ] || fail "assign without confirm: $DRY"
details "$DRY" | jq -e --argjson p1 "$PAGE1" --argjson p2 "$PAGE2" \
'.pages_count == 2 and .nodes_count == 2 and ([.pages[].post_id] | sort) == ([$p1, $p2] | sort)
and ((.blocked_ids // []) | length) == 0' >/dev/null || fail "assign dry run: $DRY"
button_attrs "$PAGE1" | jq -e --arg a "$PRESET_A" --arg b "$PRESET_B" '.modulePreset == [$a, $b]' >/dev/null \
|| fail "dry run changed page 1"
[ "$(revisions_of "$PAGE1")" = "$REVISIONS_BEFORE" ] || fail "dry run created a revision"
ASSIGNED=$(timed "assign confirmed" ability lit/divi-presets-assign "$(echo "$ASSIGN" | jq -c '. + {confirm: true}')")
echo "$ASSIGNED" | jq -e --argjson p1 "$PAGE1" --argjson p2 "$PAGE2" \
'.ok == true and (.saved | length) == 2 and .remaining == 0 and ([.saved[].post_id] | sort) == ([$p1, $p2] | sort)' \
>/dev/null || fail "assign with confirm: $ASSIGNED"
for id in "$PAGE1" "$PAGE2"; do
button_attrs "$id" | jq -e --arg b "$PRESET_B" --arg g "$PRESET_G" \
'.modulePreset == [$b] and .groupPreset == {"button.decoration.font": {presetId: [$g], groupName: "divi/font"}}' \
>/dev/null || fail "page $id after mass assignment: $(button_attrs "$id")"
done
REVISIONS_AFTER=$(revisions_of "$PAGE1")
AGAIN=$(ability lit/divi-presets-assign "$ASSIGN")
[ "$(error_code "$AGAIN")" != "confirm_required" ] || fail "second dry run still finds changes: $AGAIN"
details "$AGAIN" | jq -e '(.pages_count // 0) == 0' >/dev/null || fail "second dry run lists pages: $AGAIN"
[ "$(revisions_of "$PAGE1")" = "$REVISIONS_AFTER" ] || fail "second dry run created a revision"
pass "mass assignment: dry run lists 2 pages, confirm saved 2 (remaining 0), second dry run finds nothing"
# 6. detach of the group preset: it is in use, a stale design_rev is refused, then dry run, confirm, delete.
GROUP_ITEM=$(jq -cn --arg g "$PRESET_G" '{items: [{group: "font", preset: $g}]}')
[ "$(error_code "$(ability lit/divi-presets-delete "$GROUP_ITEM")")" = "in_use" ] || fail "delete of a used group preset"
UNKNOWN_VARIABLE=$(ability lit/divi-design-detach '{"variable": "Irgendeine Variable"}')
[ "$(error_code "$UNKNOWN_VARIABLE")" = "not_found" ] || fail "detach of an unknown variable: $UNKNOWN_VARIABLE"
DETACH=$(jq -cn --arg g "$PRESET_G" '{preset: {group: "font", preset: $g}}')
DRY=$(timed "detach dry run" ability lit/divi-design-detach "$DETACH")
[ "$(error_code "$DRY")" = "confirm_required" ] || fail "detach without confirm: $DRY"
DETACH_REV=$(details "$DRY" | jq -r '.design_rev')
details "$DRY" | jq -e '.pages_count == 2 and (.diff | length) > 0' >/dev/null || fail "detach dry run: $DRY"
STALE=$(ability lit/divi-design-detach "$(echo "$DETACH" | jq -c '. + {confirm: true, design_rev: "veraltet"}')")
[ "$(error_code "$STALE")" = "rev_mismatch" ] || fail "stale design_rev: $STALE"
nested_groups "$PRESET_B" | jq -e 'length > 0' >/dev/null || fail "stale detach changed preset B"
button_attrs "$PAGE1" | jq -e '(.groupPreset // null) != null' >/dev/null || fail "stale detach changed page 1"
pass "group preset is in use, unknown variable not found, stale design_rev refused without writing"
echo "info - preset B before detach: $(preset_item "$PRESET_B")" >&2
DETACHED=$(timed "detach confirmed" ability lit/divi-design-detach "$(echo "$DETACH" | jq -c --arg r "$DETACH_REV" '. + {confirm: true, design_rev: $r}')")
echo "$DETACHED" | jq -e '.ok == true and (.saved | length) == 2 and .remaining == 0' >/dev/null \
|| fail "detach with confirm: $DETACHED"
nested_groups "$PRESET_B" | jq -e 'length == 0' >/dev/null || fail "preset B still nests the group preset"
echo "info - preset B after withoutNestedRef: $(preset_item "$PRESET_B")" >&2
for id in "$PAGE1" "$PAGE2"; do
button_attrs "$id" | jq -e --arg b "$PRESET_B" '.modulePreset == [$b] and (.groupPreset // null) == null' >/dev/null \
|| fail "page $id after detach: $(button_attrs "$id")"
done
pass "detach removed the nested reference (snapshot pipeline) and the page references"
DRY=$(ability lit/divi-presets-delete "$GROUP_ITEM")
[ "$(error_code "$DRY")" = "confirm_required" ] || fail "delete dry run after detach: $DRY"
DELETE_REV=$(details "$DRY" | jq -r '.design_rev')
DELETED=$(ability lit/divi-presets-delete "$(echo "$GROUP_ITEM" | jq -c --arg r "$DELETE_REV" '. + {confirm: true, design_rev: $r}')")
echo "$DELETED" | jq -e '.ok == true' >/dev/null || fail "delete after detach: $DELETED"
if preset_stored group divi/font "$PRESET_G"; then fail "group preset $PRESET_G is still stored"; fi
pass "group preset deletable after detach"
finish