feat(cache): WP Fastest Cache statt WP Super Cache
Die Cache-Integration war für WP Super Cache gebaut, eingesetzt wird aber WP Fastest Cache. Profil (wpfc), Listener, Drosselung, lit/cache-clear und Leeren-Probe laufen jetzt über die öffentlichen WPFC-Funktionen wpfc_clear_post_cache_by_id/wpfc_clear_all_cache; die Probe leert nur die Wegwerf-Seite (singleDeleteCache ohne Elternseiten) und prüft, dass WPFC den Hook noch verarbeitet. Smoke fastest-cache-smoke.sh ersetzt den WPSC-Smoke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e1fad3dffe
commit
bee9da3a86
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end checks of the WP Fastest Cache profile of the compatibility layer: status, check route /wpfc-check with
|
||||
# the purge probe (cache off and on), the purge after a write of this plugin without wp_update_post (terms), the
|
||||
# whole-cache purge after a setting, its throttle, the trailing purge by cron and lit/cache-clear. Runs INSIDE the
|
||||
# DDEV web container:
|
||||
# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/fastest-cache-smoke.sh
|
||||
#
|
||||
# SIDE EFFECTS, all undone by the EXIT trap and proven at the end (verify_restored): WP Fastest Cache is activated if
|
||||
# it was not, its option WpFastestCache is switched (only wpFastestCacheStatus, nothing that writes .htaccess) and set
|
||||
# back byte for byte, a test post and two categories are created and deleted. Cache files of the site are emptied by
|
||||
# the whole-cache purges – that is what the run checks; WPFC rebuilds them on the next requests.
|
||||
set -euo pipefail
|
||||
cd /var/www/html
|
||||
# shellcheck source=lib.sh
|
||||
source wp-content/plugins/WP-LIT-MCP/tests/Integration/lib.sh
|
||||
|
||||
ROOT=/var/www/html
|
||||
CACHE_ALL=$ROOT/wp-content/cache/all
|
||||
HTACCESS=$ROOT/.htaccess
|
||||
CRON_PATTERN='lit_mcp_wpfc'
|
||||
OPTION_PATTERN='lit_mcp_wpfc'
|
||||
TRAILING_HOOK=lit_mcp_wpfc_trailing_purge
|
||||
PURGED_AT=lit_mcp_wpfc_purged_at
|
||||
BASE=http://localhost
|
||||
CACHE_ON='{"wpFastestCacheStatus":"on"}'
|
||||
|
||||
ADMIN_LOGIN=$(wp user list --role=administrator --field=user_login | head -1)
|
||||
PREFIX=$(wp db prefix)
|
||||
|
||||
# --- Guards and snapshot of everything the run can change ----------------------------------------------------------
|
||||
wp plugin is-installed wp-fastest-cache || fail "WP Fastest Cache is not installed"
|
||||
sums() { sha256sum "$@" 2>/dev/null | awk '{print $1}'; }
|
||||
WPFC_WAS_ACTIVE=no; wp plugin is-active wp-fastest-cache 2>/dev/null && WPFC_WAS_ACTIVE=yes
|
||||
WPFC_OPTION_BEFORE=$(wp option get WpFastestCache 2>/dev/null || echo __absent__)
|
||||
HTACCESS_BEFORE=absent; [ -e "$HTACCESS" ] && HTACCESS_BEFORE=$(sums "$HTACCESS")
|
||||
list_hooks() { wp cron event list --fields=hook --format=csv | tail -n +2 | grep -E "$CRON_PATTERN" | sort -u || true; }
|
||||
list_options() { wp db query "SELECT option_name FROM ${PREFIX}options WHERE option_name REGEXP '$OPTION_PATTERN' ORDER BY option_name" --skip-column-names; }
|
||||
HOOKS_BEFORE=$(list_hooks)
|
||||
OPTIONS_BEFORE=$(list_options)
|
||||
ACTIVE_PLUGINS_BEFORE=$(wp option get active_plugins --format=json)
|
||||
ORIGINAL_DESCRIPTION=$(wp option get blogdescription)
|
||||
CREATED_POSTS=()
|
||||
CREATED_TERMS=()
|
||||
|
||||
restore() {
|
||||
set +e
|
||||
local id term hook option
|
||||
for id in "${CREATED_POSTS[@]}"; do wp post delete "$id" --force >/dev/null 2>&1; done
|
||||
for term in "${CREATED_TERMS[@]}"; do wp term delete category "$term" >/dev/null 2>&1; done
|
||||
wp option update blogdescription "$ORIGINAL_DESCRIPTION" >/dev/null 2>&1
|
||||
wp transient delete "$PURGED_AT" >/dev/null 2>&1
|
||||
cleanup_app_passwords_for "$ADMIN_LOGIN"
|
||||
if [ "$WPFC_OPTION_BEFORE" = __absent__ ]; then
|
||||
wp option delete WpFastestCache >/dev/null 2>&1
|
||||
else
|
||||
wp option update WpFastestCache "$WPFC_OPTION_BEFORE" >/dev/null 2>&1
|
||||
fi
|
||||
[ "$WPFC_WAS_ACTIVE" = no ] && wp plugin deactivate wp-fastest-cache >/dev/null 2>&1
|
||||
for hook in $(list_hooks); do
|
||||
grep -qx "$hook" <<<"$HOOKS_BEFORE" || wp cron event delete "$hook" >/dev/null 2>&1
|
||||
done
|
||||
for option in $(list_options); do
|
||||
grep -qx "$option" <<<"$OPTIONS_BEFORE" || wp option delete "$option" >/dev/null 2>&1
|
||||
done
|
||||
set -e
|
||||
}
|
||||
|
||||
# Prints the proof; returns non-zero on any difference (used in the trap, so it must not exit).
|
||||
verify_restored() {
|
||||
local ok=0 htaccess_after=absent
|
||||
[ "$(wp option get WpFastestCache 2>/dev/null || echo __absent__)" = "$WPFC_OPTION_BEFORE" ] \
|
||||
|| { echo "FAIL: option WpFastestCache differs"; ok=1; }
|
||||
[ -e "$HTACCESS" ] && htaccess_after=$(sums "$HTACCESS")
|
||||
[ "$htaccess_after" = "$HTACCESS_BEFORE" ] || { echo "FAIL: .htaccess changed ($HTACCESS_BEFORE -> $htaccess_after)"; ok=1; }
|
||||
[ "$(wp option get active_plugins --format=json)" = "$ACTIVE_PLUGINS_BEFORE" ] || { echo "FAIL: active_plugins differs"; ok=1; }
|
||||
[ "$(list_hooks)" = "$HOOKS_BEFORE" ] || { echo "FAIL: cron events differ: $(list_hooks | tr '\n' ' ')"; ok=1; }
|
||||
[ "$(list_options)" = "$OPTIONS_BEFORE" ] || { echo "FAIL: options differ: $(list_options | tr '\n' ' ')"; ok=1; }
|
||||
[ -z "$(wp transient get "$PURGED_AT" 2>/dev/null || true)" ] || { echo "FAIL: transient $PURGED_AT left"; ok=1; }
|
||||
[ "$(wp option get blogdescription)" = "$ORIGINAL_DESCRIPTION" ] || { echo "FAIL: blogdescription not restored"; ok=1; }
|
||||
[ "$(wp post list --post_type=any --post_status=any --meta_key=_lit_mcp_check_entry --format=count)" = 0 ] \
|
||||
|| { echo "FAIL: a probe page is left"; ok=1; }
|
||||
[ "$(wp post list --post_type=post --post_status=any --name=lit-wpfc-smoke --format=count)" = 0 ] \
|
||||
|| { echo "FAIL: the test post is left"; ok=1; }
|
||||
[ "$ok" = 0 ] && pass "restored: WP Fastest Cache active=$WPFC_WAS_ACTIVE with its option as before, .htaccess untouched"
|
||||
return "$ok"
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local rc=$1
|
||||
restore
|
||||
verify_restored || rc=1
|
||||
guard_exit "$rc" || exit $?
|
||||
exit "$rc"
|
||||
}
|
||||
trap 'on_exit $?' EXIT
|
||||
trap 'exit 130' INT TERM HUP
|
||||
|
||||
# --- Helpers ---------------------------------------------------------------------------------------------------------
|
||||
wpfc_component() { tool lit-site-status '{}' | jq -c '.components[] | select(.id == "wpfc")'; }
|
||||
stored_option() { wp option get "$1" --format=json 2>/dev/null || true; }
|
||||
event_count() { wp cron event list --hook="$TRAILING_HOOK" --format=count 2>/dev/null || echo 0; }
|
||||
# The cache file of the test post where WP Fastest Cache keeps it (a purge moves the directory to cache/tmpWpfc).
|
||||
cache_file() { local f="$CACHE_ALL${POST_PATH%/}/index.html"; [ -f "$f" ] && echo "$f" || true; }
|
||||
wait_for() { # $1 = description, rest = command; polls up to 15 s
|
||||
local what=$1 i; shift
|
||||
for i in $(seq 1 15); do "$@" && return 0; sleep 1; done
|
||||
fail "timeout: $what"
|
||||
}
|
||||
cache_file_exists() { [ -n "$(cache_file)" ]; }
|
||||
cache_file_gone() { [ -z "$(cache_file)" ]; }
|
||||
fetch_post() {
|
||||
local code
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $HOST" "$BASE$POST_PATH")
|
||||
[ "$code" = 200 ] || fail "GET $POST_PATH answered $code"
|
||||
}
|
||||
# Requests the test post until WP Fastest Cache has written its file.
|
||||
fill_cache() {
|
||||
local i
|
||||
for i in $(seq 1 10); do
|
||||
fetch_post
|
||||
cache_file_exists && return 0
|
||||
sleep 1
|
||||
done
|
||||
echo "--- end of the response of $POST_PATH and the cache tree:"
|
||||
curl -s -H "Host: $HOST" "$BASE$POST_PATH" | tail -c 300
|
||||
find "$CACHE_ALL" -maxdepth 4 | head -20
|
||||
fail "timeout: cache file of the test post"
|
||||
}
|
||||
|
||||
# --- 0. Active, cache switched off -----------------------------------------------------------------------------------
|
||||
[ "$WPFC_WAS_ACTIVE" = yes ] || wp plugin activate wp-fastest-cache >/dev/null 2>&1 || fail "could not activate WP Fastest Cache"
|
||||
wp option update WpFastestCache '{}' >/dev/null
|
||||
connect "$ADMIN_LOGIN"
|
||||
|
||||
# 1. Status
|
||||
wpfc_component | jq -e '.active == true and .version == "1.5.5" and .problems == []' >/dev/null \
|
||||
|| fail "site status must show WP Fastest Cache 1.5.5 active without problems: $(wpfc_component)"
|
||||
pass "lit-site-status: WP Fastest Cache 1.5.5 active, no problems"
|
||||
|
||||
# 2. Check route with the cache switched off: the probe step is green and says "not checked"
|
||||
APP_PASSWORD=$(wp user application-password create "$ADMIN_LOGIN" lit-mcp-integration-test --porcelain)
|
||||
run_check() {
|
||||
local headers
|
||||
headers=$(curl -s -o /dev/null -D - -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' -u "$ADMIN_LOGIN:$APP_PASSWORD" "$BASE/wp-json/lit-mcp/v1/wpfc-check")
|
||||
echo "$headers" | grep -qi '^HTTP/[0-9.]* 303' || fail "check route must answer 303: $headers"
|
||||
echo "$headers" | grep -qi '^location:.*tab=compatibility.*lit_mcp_notice=compat_checked' || fail "redirect to the tab: $headers"
|
||||
}
|
||||
run_check
|
||||
REPORT=$(stored_option lit_mcp_wpfc_check_report)
|
||||
echo "$REPORT" | jq -e '(.steps | length) == 2 and all(.steps[]; .problems == [] and .skipped == false)
|
||||
and ([.steps[] | select(.key == "probe")] | length == 1 and (.[0].label | contains("Cache ist ausgeschaltet, nicht geprüft")))' >/dev/null \
|
||||
|| fail "cache off: the report must be green and the probe must say it did not check: $REPORT"
|
||||
pass "/wpfc-check with the cache off: green, probe step says 'Cache ist ausgeschaltet, nicht geprüft'"
|
||||
|
||||
# 3. Cache on; the check route runs the real purge probe
|
||||
wp option update WpFastestCache "$CACHE_ON" >/dev/null
|
||||
run_check
|
||||
REPORT=$(stored_option lit_mcp_wpfc_check_report)
|
||||
echo "$REPORT" | jq -e '(.steps | length) == 2 and all(.steps[]; .problems == [] and .skipped == false)
|
||||
and ([.steps[] | select(.key == "probe")] | length == 1 and (.[0].label | contains("nicht geprüft") | not))
|
||||
and .version == "1.5.5"' >/dev/null || fail "cache on: the report must be green with a real probe step: $REPORT"
|
||||
[ -z "$(stored_option lit_mcp_wpfc_check_lock)" ] || fail "the check lock must be released"
|
||||
[ "$(wp post list --post_type=any --post_status=any --meta_key=_lit_mcp_check_entry --format=count)" = 0 ] || fail "the probe left a page behind"
|
||||
[ "$(find "$CACHE_ALL" -maxdepth 3 -path '*lit-mcp-check-*' -print -quit 2>/dev/null)" = "" ] || fail "the probe left a cache directory behind"
|
||||
UNAUTH=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' "$BASE/wp-json/lit-mcp/v1/wpfc-check")
|
||||
[ "$UNAUTH" = 401 ] || [ "$UNAUTH" = 403 ] || fail "anonymous check must be refused, got $UNAUTH"
|
||||
pass "/wpfc-check with the cache on: green with the purge probe, no page or cache directory left, anonymous refused"
|
||||
|
||||
# 4. A published test post, fetched until it is in the cache
|
||||
CAT_A=$(wp term create category "LIT WPFC Smoke A" --porcelain); CREATED_TERMS+=("$CAT_A")
|
||||
CAT_B=$(wp term create category "LIT WPFC Smoke B" --porcelain); CREATED_TERMS+=("$CAT_B")
|
||||
POST_ID=$(wp post create --post_type=post --post_status=publish --post_title="LIT WPFC Smoke" --post_name=lit-wpfc-smoke \
|
||||
--post_content="Testbeitrag fuer den Fastest-Cache-Smoke." --porcelain)
|
||||
CREATED_POSTS+=("$POST_ID")
|
||||
POST_PATH=$(wp eval "echo wp_parse_url(get_permalink($POST_ID), PHP_URL_PATH);")
|
||||
fill_cache
|
||||
pass "test post $POST_ID cached: $(cache_file | sed "s#$ROOT/##")"
|
||||
|
||||
# 5. Control: a term change outside this plugin does not touch the cache (WP Fastest Cache never sees it) ...
|
||||
wp post term set "$POST_ID" category "$CAT_A" >/dev/null
|
||||
sleep 2
|
||||
cache_file_exists || fail "control: WP Fastest Cache itself purged on a plain term change; the next check proves nothing"
|
||||
# ... but the same kind of change through lit-content-update (no wp_update_post) empties the cache of the post.
|
||||
REV=$(tool lit-content-get "{\"post_id\":$POST_ID}" | jq -r '.rev')
|
||||
UPDATED=$(tool lit-content-update "{\"post_id\":$POST_ID,\"rev\":\"$REV\",\"terms\":{\"category\":[$CAT_B]}}")
|
||||
echo "$UPDATED" | jq -e '.ok == true' >/dev/null || fail "lit-content-update (terms only): $UPDATED"
|
||||
wait_for "the cache file of the post is gone after lit-content-update" cache_file_gone
|
||||
[ "$(event_count)" = 0 ] || fail "a single-post purge must not schedule a trailing purge"
|
||||
pass "terms-only lit-content-update empties the cache of the post (a plain term change does not), no trailing event"
|
||||
|
||||
# 6. Whole-cache purge after a setting; the second one inside 30 s is throttled and leaves exactly one cron event
|
||||
fill_cache
|
||||
[ -z "$(wp transient get "$PURGED_AT" 2>/dev/null || true)" ] || fail "transient $PURGED_AT set before the first whole purge"
|
||||
ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPFC Smoke 1"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 1"
|
||||
wait_for "the cache is empty after a setting change" cache_file_gone
|
||||
wait_for "the purge time is stored" bash -c "[ -n \"\$(wp transient get $PURGED_AT 2>/dev/null)\" ]"
|
||||
LAST=$(wp transient get "$PURGED_AT")
|
||||
[ "$(event_count)" = 0 ] || fail "the first whole purge must not schedule a trailing purge"
|
||||
pass "setting change empties the whole cache (purge time stored, no trailing event)"
|
||||
|
||||
fill_cache
|
||||
ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPFC Smoke 2"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 2"
|
||||
wait_for "one trailing purge is scheduled" bash -c "[ \"\$(wp cron event list --hook=$TRAILING_HOOK --format=count)\" = 1 ]"
|
||||
sleep 2
|
||||
cache_file_exists || fail "throttled: the second whole purge ran inside the lock time"
|
||||
ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPFC Smoke 3"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 3"
|
||||
sleep 3
|
||||
[ "$(event_count)" = 1 ] || fail "exactly one trailing event must stay scheduled, found $(event_count)"
|
||||
cache_file_exists || fail "throttled: the third whole purge ran inside the lock time"
|
||||
[ "$(wp transient get "$PURGED_AT")" = "$LAST" ] || fail "throttled purges must not move the purge time"
|
||||
pass "second and third whole purge inside 30 s: throttled (file stays, purge time unchanged), exactly one $TRAILING_HOOK event"
|
||||
|
||||
# 7. Run the trailing purge once the lock is over: the cache is emptied and nothing is scheduled any more
|
||||
WAIT=$((LAST + 32 - $(date +%s))); [ "$WAIT" -gt 0 ] && sleep "$WAIT"
|
||||
wp cron event run "$TRAILING_HOOK" >/dev/null 2>&1 || fail "wp cron event run $TRAILING_HOOK"
|
||||
cache_file_gone || fail "the trailing purge did not empty the cache"
|
||||
[ "$(event_count)" = 0 ] || fail "no trailing event may be left after it ran"
|
||||
[ "$(wp transient get "$PURGED_AT")" -gt "$LAST" ] || fail "the trailing purge must store a new purge time"
|
||||
pass "trailing purge by cron: cache emptied, event gone, purge time renewed"
|
||||
|
||||
# 8. lit/cache-clear: the dry run changes nothing, confirm empties the post's page / the whole cache
|
||||
fill_cache
|
||||
for ARGS in "{\"scope\":\"post\",\"post_id\":$POST_ID}" '{"scope":"all"}'; do
|
||||
DRY=$(ability lit/cache-clear "$ARGS")
|
||||
[ "$(error_code "$DRY")" = confirm_required ] || fail "lit/cache-clear dry run $ARGS must ask for confirm: $DRY"
|
||||
echo "$DRY" | jq -e '(.__error | fromjson | .cache_enabled) == true' >/dev/null || fail "dry run must report the cache as on: $DRY"
|
||||
done
|
||||
sleep 2
|
||||
cache_file_exists || fail "the dry runs of lit/cache-clear emptied the cache"
|
||||
[ "$(event_count)" = 0 ] || fail "a dry run must not schedule anything"
|
||||
pass "lit/cache-clear without confirm: asks for confirmation, reports the cache as on, cache file stays"
|
||||
|
||||
CLEARED=$(ability lit/cache-clear "{\"scope\":\"post\",\"post_id\":$POST_ID,\"confirm\":true}")
|
||||
echo "$CLEARED" | jq -e '.ok == true and .outcome == "purged" and .log_id != null' >/dev/null || fail "lit/cache-clear post: $CLEARED"
|
||||
wait_for "the cache file of the post is gone after lit/cache-clear scope post" cache_file_gone
|
||||
[ "$(event_count)" = 0 ] || fail "a single-post purge must not schedule a trailing purge"
|
||||
pass "lit/cache-clear scope post with confirm: the page of the post is emptied"
|
||||
|
||||
# The whole-cache purge needs the lock time of the earlier steps to be over.
|
||||
WAIT=$(($(wp transient get "$PURGED_AT") + 32 - $(date +%s))); [ "$WAIT" -gt 0 ] && sleep "$WAIT"
|
||||
fill_cache
|
||||
CLEARED=$(ability lit/cache-clear '{"scope":"all","confirm":true}')
|
||||
echo "$CLEARED" | jq -e '.ok == true and .outcome == "purged" and .log_id != null' >/dev/null || fail "lit/cache-clear all: $CLEARED"
|
||||
wait_for "the cache is empty after lit/cache-clear scope all" cache_file_gone
|
||||
sleep 2
|
||||
# Its own change log entry must not purge a second time (that would be throttled and leave a trailing event).
|
||||
[ "$(event_count)" = 0 ] || fail "the change log entry of lit/cache-clear triggered a second purge"
|
||||
LAST=$(wp transient get "$PURGED_AT")
|
||||
pass "lit/cache-clear scope all with confirm: whole cache emptied once, own log entry purges nothing"
|
||||
|
||||
fill_cache
|
||||
THROTTLED=$(ability lit/cache-clear '{"scope":"all","confirm":true}')
|
||||
echo "$THROTTLED" | jq -e '.ok == true and .outcome == "throttled"' >/dev/null || fail "lit/cache-clear all inside the lock time: $THROTTLED"
|
||||
[ "$(event_count)" = 1 ] || fail "a throttled lit/cache-clear must schedule exactly one trailing purge"
|
||||
cache_file_exists || fail "throttled: the cache file must stay"
|
||||
[ "$(wp transient get "$PURGED_AT")" = "$LAST" ] || fail "a throttled purge must not move the purge time"
|
||||
WAIT=$((LAST + 32 - $(date +%s))); [ "$WAIT" -gt 0 ] && sleep "$WAIT"
|
||||
wp cron event run "$TRAILING_HOOK" >/dev/null 2>&1 || fail "wp cron event run $TRAILING_HOOK"
|
||||
cache_file_gone || fail "the trailing purge did not empty the cache"
|
||||
pass "lit/cache-clear scope all inside 30 s: throttled, one trailing event, cron run empties the cache"
|
||||
|
||||
# 9. Everything back as before (the EXIT trap would do it too; here it is part of the verdict)
|
||||
restore
|
||||
verify_restored || fail "the state before the run was not restored"
|
||||
pass "cleanup: test post, categories, options, transient and cron events gone"
|
||||
|
||||
finish
|
||||
Reference in New Issue
Block a user