Skip to content

Automated coverage for multisite, the runner's unanswered-verdict refusal, live Redis and APCu (#43) - #57

Merged
sean-e-dietrich merged 7 commits into
mainfrom
test/43-coverage
Sep 30, 2026
Merged

sean-e-dietrich merged 7 commits into
mainfrom
test/43-coverage

Conversation

@sean-e-dietrich

@sean-e-dietrich sean-e-dietrich commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #43

Adds CI coverage and tests for the gaps listed in #43, plus the runtime.json uninstall gap found in #56.

  • Redis against a live server. Every integration job now has a Redis service container (redis:7.2-alpine, password-protected). The WP latest/PHP 8.3, multisite and nightly/PHP 8.4 jobs install ext-redis with pecl, because the cimg/php images don't include it, and export BASIC_FIREWALL_TEST_REDIS and BASIC_FIREWALL_TEST_REDIS_PASSWORD.

  • APCu. The same three jobs install ext-apcu and set apc.enable_cli=1 (exporting BASIC_FIREWALL_TEST_APCU=1). WP 6.4/PHP 8.1 and nightly/PHP 8.5 don't install either extension. That's deliberate: they keep running the tests that need the extensions absent (Redis refused on the Storage screen, APCu critical in Site Health). An extension can't be unloaded mid-run, so those tests couldn't run alongside the live ones.

  • Multisite job: integration-multisite-wplatest-php8.3. It sets up a subdirectory network on 127.0.0.1:8080 with a second site, network-activates the plugin, sets BASIC_FIREWALL_MULTISITE=1, and runs the integration, e2e and WP-CLI suites. The job is added to the release job's requires list and filtered with RELEASE_TAGS. New MultisiteTest covers:

    • the early path stepping aside, using the real network's wp-config.php constants
    • the mu-loader staying in place when one subsite deactivates the plugin, whether it's network-active or still active on another site, and being removed by the last site or by network deactivation
    • Site Health's snippet warning on a network, and its healthy state when there's no snippet
    • uninstall removing every site's options and private directory (Uninstall: remove what the plugin wrote outside the default private directory #11)
  • The runner's "early verdict recorded but not answered" refusal (Never serve the page for an exception mode verdict on the early path (#34); require kanopi/firewall 2.34.1 #36), over HTTP. New RunnerRefusalTest runs real WordPress under PHP's built-in server behind a router fixture. The fixture stands in for an early path that records a verdict and returns without answering it. For challenge, redirect and blocked verdicts the test checks:

    • the response is a 503 with Retry-After: 60 and no-store
    • the refusal is logged
    • the report is saved as the last anomaly
    • Site Health reports it as critical

    A solved challenge and a request with no verdict are both served.

  • Decision actions over HTTP (Move CI from GitHub Actions to CircleCI #3's port). The e2e suite installs a recorder mu-plugin that listens for basic_firewall_decision, and checks the behaviour the README describes. allowed is announced at plugins_loaded on both paths. On the mu-plugin path, blocked and challenged are announced at shutdown and marked enforced. On the wp-config.php path, a refusal never reaches WordPress. A match in log mode is announced as blocked, not enforced.

  • uninstall.php now removes runtime.json from a private directory that existed before the plugin (the guarded, filtered case). The fix includes a test and a CHANGELOG entry. A directory the plugin created itself was already removed whole.

  • README CI table updated.

Supporting changes

  • tests/integration/Test_Services.php: Redis, APCu and multisite tests skip when the service is missing, unless the environment says it's there. In that case they fail. This stops a Redis container that didn't start from turning CI green with the Redis tests quietly skipped.
  • BlockListWriteTest: fixed a pre-existing test bug. Blocked_Clients caches its storage for the whole process, so every case of the "every backend" provider had been running against whichever backend the first test built. The test now builds a fresh block list per backend, and adds a live Redis case plus a round-trip check against the server.
  • Single-site assertions that failed on a network now skip there: subdirectory layouts filtered through home, and the early path's evaluation-check reports. The bootstrap check's expectation is inverted on a network. The wp-content fallback uninstall test uses the site's own suffix on a network.
  • The e2e raw-socket request now includes the port in its Host header. A network answers only on the domain it was installed on, and was redirecting the bare host before the firewall loaded.

Testing

Previously skipped tests that now run in CI (on main they skipped in every job, because no image had ext-redis or ext-apcu and there was no network):

Job Now running
integration-wplatest-php8.3, integration-wpnightly-php8.4 SiteSettingsHonouredTest::test_redis_storage, ::test_apcu_cache; RedisStorageTest screen tests (2); UninstallLeftoversTest::test_redis_keys_under_the_plugins_prefixes_are_deleted, ::test_an_unreachable_redis_server_is_noted_and_not_fatal, ::test_apcu_entries_are_deleted; RedisPasswordTest::test_a_live_server_accepts_the_injected_password. New: BlockListWriteTest redis case and live round trip, and two CacheBackendTest APCu tests
integration-multisite-wplatest-php8.3 Everything above, plus UninstallLeftoversTest::test_every_site_of_a_network_is_uninstalled and the 8 new MultisiteTest tests
integration-wp6.4-php8.1, integration-wpnightly-php8.5 Unchanged on purpose: they run the tests that need the extensions absent
all integration jobs New RunnerRefusalTest (5 tests), and the e2e decision-action test (mu-plugin path)

Local runs:

  • unit, composer analyse and composer lint pass

  • integration in DDEV passes, including with a local password-protected redis-server, BASIC_FIREWALL_TEST_APCU=1 and apc.enable_cli=1

  • e2e in DDEV passes on the wp-config.php path

  • tests/cli/commands.sh in DDEV passes

  • check-versions and build-zip pass

  • the integration, e2e and CLI suites also pass against a throwaway local multisite install, a separate database and directory that was removed afterwards

  • Was AI used in this pull request?

…43)

Redis runs as a service container; ext-redis and ext-apcu are installed
with pecl on the WP latest/PHP 8.3, multisite and nightly/PHP 8.4 cells,
with apc.enable_cli=1. The other cells keep running the tests that need
the extensions absent. Tests read BASIC_FIREWALL_TEST_REDIS,
BASIC_FIREWALL_TEST_APCU and BASIC_FIREWALL_MULTISITE through
Test_Services, and fail rather than skip when a declared service is
missing.

BlockListWriteTest gains a live Redis backend case and a round trip
checked on the server, and builds a fresh block list per backend: the
cached storage meant every provider case had been testing one backend.
CacheBackendTest writes to APCu from the compiled file and clears it.
… and per-site uninstall (#43)

MultisiteTest runs on the CI network cell: the bootstrap stepping aside
on a real network's wp-config.php constants, the mu-loader surviving a
subsite deactivating (network-active, or active on another site) and
going with the last site or a network deactivation, Site Health's
snippet warning and healthy state on a network, and uninstall removing
every site's options and private directory.

Single-site assertions that failed on a network now step aside there:
subdirectory layouts filtered through home, the early path's
evaluation-check reports, and the bootstrap check (inverted on a
network). The wp-content fallback uninstall test uses the site's own
suffix on a network and checks a foreign one is left.
…, #43)

RunnerRefusalTest serves real WordPress under PHP's built-in server
behind a router that stands in for an early path which records a
challenge, redirect or block and returns without answering it. Each is
refused with a 503 and Retry-After, logged, saved as the last anomaly
and raised by Site Health; a solved challenge and a request with no
verdict are served.

The e2e raw-socket request now sends the port in its Host header: a
network answers only the domain it was installed on and redirected the
bare host before the firewall loaded.
… README: CI jobs

HttpEvaluationTest installs a recorder mu-plugin for basic_firewall_decision
and checks what the README promises: allowed is announced at
plugins_loaded on either path; on the mu-plugin path blocked and
challenged are announced at shutdown, enforced; on the wp-config.php
path a refusal never reaches WordPress; a log-mode match is announced
as blocked and not enforced.
…43)

With #56 merged, RedisPasswordTest's live-server test reads
BASIC_FIREWALL_TEST_REDIS and BASIC_FIREWALL_TEST_REDIS_PASSWORD. The CI
Redis now requires a password and the extension cells export both; the
block-list and uninstall Redis tests hand it to the plugin through the
settings, as a site would.
@sean-e-dietrich
sean-e-dietrich marked this pull request as ready for review September 30, 2026 19:00
@sean-e-dietrich
sean-e-dietrich merged commit b042d81 into main Sep 30, 2026
14 checks passed
@sean-e-dietrich
sean-e-dietrich deleted the test/43-coverage branch September 30, 2026 19:02
@sean-e-dietrich sean-e-dietrich mentioned this pull request Sep 30, 2026
1 task done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automated coverage for multisite, the runner's unanswered-verdict refusal, live Redis and APCu

1 participant