Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
144 lines
9.9 KiB
Bash
Executable File
144 lines
9.9 KiB
Bash
Executable File
#!/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 <tool> <input JSON>: 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) : '<unset>';"
|
||
}
|
||
|
||
# 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)" = "<unset>" ] || 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)" = "<unset>" ] || 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
|