From 4300205f1762d133334dfc80610c465a68b7fe46 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Mon, 19 Oct 2026 18:15:18 +0200 Subject: [PATCH] =?UTF-8?q?test:=20Smoke=20f=C3=BCr=20Theme-Optionen=20und?= =?UTF-8?q?=20Schriften-Upload=20gegen=20Divi=205.15.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- tests/Integration/divi-theme-fonts-smoke.sh | 143 ++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100755 tests/Integration/divi-theme-fonts-smoke.sh diff --git a/tests/Integration/divi-theme-fonts-smoke.sh b/tests/Integration/divi-theme-fonts-smoke.sh new file mode 100755 index 0000000..6ddf411 --- /dev/null +++ b/tests/Integration/divi-theme-fonts-smoke.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# End-to-end checks of Etappe 4d (Divi theme options: read, change, unknown font with suggestions, reset to default, +# snapshot + restore; custom font upload from a public URL) 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-theme-fonts-smoke.sh +# Needs network access from the container (fetches one small woff2 from fonts.gstatic.com). The EXIT trap puts the +# options et_divi and et_uploaded_fonts back byte-exact (serialized, base64 through the environment), removes the +# uploaded font with Divi's own FontsUtility::font_remove (files included) and fails the script when a restore fails. +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)-$$" +FONT_NAME="LIT Smoke $RUN" +FONT_URL="https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2" +# $1 = option name. An option that does not exist is captured as the sentinel string, so restore() can delete it again +# (update_option(false) would leave an empty row behind, and Divi's font_add then fails on that ''). +capture() { wp eval "echo base64_encode(serialize(get_option('$1', '__lit_absent__')));"; } +ORIGINAL_THEME=$(capture et_divi) +ORIGINAL_FONTS=$(capture et_uploaded_fonts) +[ -n "$ORIGINAL_THEME" ] || { echo "FAIL: could not capture et_divi"; exit 1; } +[ -n "$ORIGINAL_FONTS" ] || { echo "FAIL: could not capture et_uploaded_fonts"; exit 1; } +curl -fsS -m 20 -o /dev/null "$FONT_URL" || { echo "FAIL: $FONT_URL is not reachable from the container"; exit 1; } + +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; } +} +cleanup() { + local failed=0 + # Remove the font through Divi (deletes the files), then put the option back exactly as it was. + LIT_FONT="$FONT_NAME" wp eval 'ET\Builder\VisualBuilder\Fonts\FontsUtility::font_remove(getenv("LIT_FONT"));' >/dev/null 2>&1 || true + restore et_uploaded_fonts LIT_FONTS || failed=1 + restore et_divi LIT_THEME || 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_FONTS="$ORIGINAL_FONTS" +trap 'rc=$?; cleanup || rc=1; guard_exit "$rc" || exit $?' EXIT + +connect "$ADMIN_LOGIN" + +# confirm : dry run must answer confirm_required; then the confirmed call; prints the result. +confirm() { + local dry rev + dry=$(ability "$1" "$2") + [ "$(error_code "$dry")" = "confirm_required" ] || fail "$1 dry run: $dry" + rev=$(echo "$dry" | jq -r '.__error | fromjson | .design_rev') + ability "$1" "$(echo "$2" | jq -c --arg r "$rev" '. + {confirm: true, design_rev: $r}')" +} +theme_option() { # $1 = key -> the stored value ('' when the key is not set) + wp eval "\$o = get_option('et_divi'); echo array_key_exists('$1', \$o) ? var_export(\$o['$1'], true) : '';" +} + +# 1. Read: the tool lists the options with effective values and a design_rev. +GET=$(ability lit/divi-theme-options-get '{}') +echo "$GET" | jq -e '.design_rev | type == "string"' >/dev/null || fail "theme-options-get without design_rev: $GET" +echo "$GET" | jq -e '[.. | objects | select(has("effective"))] | length >= 20' >/dev/null \ + || fail "theme-options-get does not list the 20 options with effective values: $GET" +pass "theme options listed" + +# 2. Change two options (snapshot is taken), stale design_rev refused, values stored with Divi's types. +UPDATE=$(jq -cn '{set: {body_font_size: 18, content_width: "1200"}}') +DONE=$(confirm lit/divi-theme-options-update "$UPDATE") +[ "$(echo "$DONE" | jq -r '.ok')" = "true" ] || fail "theme options update: $DONE" +THEME_SNAPSHOT=$(echo "$DONE" | jq -r '.snapshot_id') +[ "$(theme_option body_font_size)" = "18" ] || fail "body_font_size not stored" +[ "$(theme_option content_width)" = "1200" ] || fail "content_width not stored as an integer" +# A different value: an unchanged set is answered as "no change" before design_rev is looked at. +STALE=$(ability lit/divi-theme-options-update "$(echo "$UPDATE" | jq -c '.set.body_font_size = 19 | . + {confirm: true, design_rev: "veraltet"}')") +[ "$(error_code "$STALE")" = "rev_mismatch" ] || fail "stale design_rev: $STALE" +pass "theme options changed ($THEME_SNAPSHOT), stale design_rev refused" + +# 3. Validation. Range and unknown keys are already refused by the input schema (before the plugin runs); unknown font +# names and "none" reach the plugin, which collects them with suggestions. Nothing is written in any case. +BAD=$(ability lit/divi-theme-options-update '{"set": {"heading_font": "Robotto", "body_font": "none"}}') +[ "$(error_code "$BAD")" = "validation_failed" ] || fail "invalid input accepted: $BAD" +echo "$BAD" | grep -q 'Roboto' || fail "no font suggestion in: $BAD" +echo "$BAD" | grep -q 'body_font' || fail "none not refused with its path: $BAD" +BAD=$(ability lit/divi-theme-options-update '{"set": {"body_font_size": 99}}') +echo "$BAD" | jq -e 'has("__error")' >/dev/null || fail "out-of-range size accepted: $BAD" +BAD=$(ability lit/divi-theme-options-update '{"set": {"colour": 1}}') +echo "$BAD" | jq -e 'has("__error")' >/dev/null || fail "unknown key accepted: $BAD" +[ "$(theme_option body_font_size)" = "18" ] || fail "invalid call changed body_font_size" +[ "$(theme_option heading_font)" = "" ] || fail "invalid call changed heading_font" +pass "unknown font with suggestions, none, range and unknown key refused" + +# 4. A known Google font is accepted; null resets an option to Divi's default (key removed). +DONE=$(confirm lit/divi-theme-options-update '{"set": {"heading_font": "Roboto", "content_width": null}}') +[ "$(echo "$DONE" | jq -r '.ok')" = "true" ] || fail "font + reset: $DONE" +[ "$(theme_option heading_font)" = "'Roboto'" ] || fail "heading_font not stored" +[ "$(theme_option content_width)" = "" ] || fail "content_width not removed by null" +pass "Google font stored, null resets to Divi's default" + +# 5. Restore the snapshot of step 2: body_font_size and content_width are back as before step 2; heading_font too. +LIST=$(ability lit/divi-design-snapshots '{"area": "theme_options"}') +echo "$LIST" | jq -e --arg s "$THEME_SNAPSHOT" 'any(.snapshots[]; .id == $s)' >/dev/null || fail "snapshot not listed: $LIST" +DONE=$(confirm lit/divi-design-restore "$(jq -cn --arg s "$THEME_SNAPSHOT" '{snapshot_id: $s}')") +[ "$(echo "$DONE" | jq -r '.ok')" = "true" ] || fail "restore: $DONE" +# The snapshot of step 2 was taken before any change of this run: the three keys must equal their original state +# (present with the same typed value, or absent) – compared against the captured et_divi, not against assumptions. +for KEY in body_font_size content_width heading_font; do + LIT_KEY="$KEY" wp eval '$o = unserialize(base64_decode(getenv("LIT_THEME"))); $n = get_option("et_divi"); $k = getenv("LIT_KEY"); exit(array_key_exists($k, $o) === array_key_exists($k, $n) && ($o[$k] ?? null) === ($n[$k] ?? null) ? 0 : 1);' \ + || fail "$KEY not restored to its state before the first change" +done +pass "theme_options snapshot restored (body_font_size, content_width, heading_font as before)" + +# 6. Font upload: Probelauf names the file (format, bytes, sha256) and installs nothing. +UPLOAD=$(jq -cn --arg n "$FONT_NAME" --arg u "$FONT_URL" '{name: $n, files: {woff2: $u}, weights: "all"}') +DRY=$(ability lit/divi-fonts-upload "$UPLOAD") +[ "$(error_code "$DRY")" = "confirm_required" ] || fail "font dry run: $DRY" +echo "$DRY" | grep -q 'woff2' || fail "dry run does not name the format: $DRY" +LIT_FONT="$FONT_NAME" wp eval 'exit(isset(get_option("et_uploaded_fonts", [])[getenv("LIT_FONT")]) ? 1 : 0);' \ + || fail "dry run installed the font" +pass "font dry run installs nothing" + +# 7. Confirmed upload: the font is in et_uploaded_fonts, the file exists, the name is now reserved. +DONE=$(ability lit/divi-fonts-upload "$(echo "$UPLOAD" | jq -c '. + {confirm: true}')") +[ "$(echo "$DONE" | jq -r '.ok')" = "true" ] || fail "font upload: $DONE" +LIT_FONT="$FONT_NAME" wp eval '$f = get_option("et_uploaded_fonts", [])[getenv("LIT_FONT")] ?? null; exit($f && isset($f["font_url"]["woff2"]) ? 0 : 1);' \ + || fail "font not stored in et_uploaded_fonts" +AGAIN=$(ability lit/divi-fonts-upload "$(echo "$UPLOAD" | jq -c '. + {confirm: true}')") +[ "$(error_code "$AGAIN")" != "" ] || fail "a second upload of the same name was accepted: $AGAIN" +pass "font installed through Divi, same name refused" + +# 8. The uploaded font is a valid theme font now; Arial (web-safe) is refused as a new font name. +DONE=$(confirm lit/divi-theme-options-update "$(jq -cn --arg n "$FONT_NAME" '{set: {body_font: $n}}')") +[ "$(echo "$DONE" | jq -r '.ok')" = "true" ] || fail "uploaded font as body_font: $DONE" +# A taken name (web-safe, Google, uploaded) is a conflict, see DiviFontsUploadAbility. +RESERVED=$(ability lit/divi-fonts-upload "$(jq -cn --arg u "$FONT_URL" '{name: "Arial", files: {woff2: $u}, weights: "all"}')") +[ "$(error_code "$RESERVED")" = "conflict" ] || fail "web-safe name accepted: $RESERVED" +pass "uploaded font usable as theme font, web-safe name refused" + +# 9. A file whose content does not match its format key is refused (a PNG offered as woff2). +WRONG=$(ability lit/divi-fonts-upload "$(jq -cn --arg n "$FONT_NAME Falsch" '{name: $n, files: {woff2: "https://s.w.org/images/wmark.png"}, weights: "all"}')") +[ "$(error_code "$WRONG")" = "font_invalid" ] || fail "wrong content accepted: $WRONG" +pass "content/format mismatch refused (font_invalid)" + +finish