fair-plugin/ai/plans/tests/implementation-order.md
Chuck Adams 6a0ec912da
docs: consolidate totals table, move deferred items to Phase 14
- Single 'Current totals' table at top covering all 287 tests + 486 mutants
- All inline deferred items replaced with ↳ pointer to Phase 14
- Phase 14 organized by layer: unit, integration, HTTP, browser, CI, quality
- Removed duplicate 'Current totals' tables scattered through doc

Signed-off-by: Chuck Adams <chaz@chaz.works>
2026-06-09 15:10:39 -06:00

14 KiB
Raw Permalink Blame History

Implementation Order

The plan is designed to be executed incrementally. Each phase produces runnable tests before moving on.


Current totals

Layer Count Assertions Key Metric Runner
Unit 248 430 1 skipped composer run test:unit (local)
Integration 12 38 2 skipped bin/run-integration.sh (Docker)
HTTP 12 38 all pass bin/run-integration.sh (Docker)
Browser 15 all pass npm run test:browser (Playwright)
Mutation 486 mutants 97% covered MSI composer run infection (Docker)
Total 287 tests / 486 mutants 506+

Phase 1: Infrastructure migration

  • Rename tests/phpunit/tests/unit/
  • Move phpunit.xml.disttests/unit/phpunit.xml, adjust paths
  • Move tests/phpunit/multisite.xmltests/unit/multisite.xml
  • Move tests/phpunit/tests/Packages/*tests/unit/tests/Packages/* (preserve existing tests)
  • Move tests/phpunit/tests/SampleTest.phptests/unit/tests/SampleTest.php
  • Update composer.json scripts: testtest:unit, pointing at tests/unit/phpunit.xml. Also test:multisitetest:unit:multisite. Updated coverage:* paths.
  • Update package.json npm scripts to reference new composer script names
  • Update tests/unit/README.md with new paths
  • Bump .wp-env.json PHP version from 7.4 → 8.0 (hard floor per AGENTS.md)
  • Add "platform": {"php": "8.0"} to composer.json config → ran composer update → lock file resolves all deps for PHP 8.0
  • Fix test:php:install-deps npm script: add --ignore-platform-req=ext-gmp (wp-env PHP 8.0 image lacks gmp)
  • Verify existing tests still pass: 19 tests, 30 assertions

Remaining known issue: simplito/elliptic-php (dependency of fairpm/did-manager) requires ext-gmp, which is missing from the wp-env PHP 8.0 Docker image. Workaround: --ignore-platform-req=ext-gmp in the install-deps script. A proper fix would be installing gmp in the Docker image or upstreaming an ext-gmp change to the wp-env image.

Phase 2: Fixtures & factories

  • Create tests/fixtures/ directory
  • Create all fixture JSON files (did-doc, metadata-doc, release-doc variants — 13 files total)
  • Create tests/Factory/MetadataDocumentFactory.php (full, minimal, from_fixture, builder, error paths)
  • Create tests/Factory/ReleaseDocumentFactory.php (full, with_version, with_requirements, builder, error paths)
  • Add autoload-dev PSR-4 entry for FAIR\Tests\tests/ in composer.json
  • Verify existing tests still pass: 19 tests, 30 assertions

Phase 3: DID pipeline pure functions (Priority A)

  • GetDidHashTest.php — 8 tests: hex string, deterministic, different DIDs, error propagation, empty string, 32-char, long DID, non-plc method
  • GetLanguagePriorityListTest.php — 11 tests: full locale first, underscore conversion, lowercase, prefix decomposition, x- subtag skip, doubled primary code, defaults, simple locale, filter hook, filter override, zh-Hans-CN order
  • PickArtifactByLangTest.php — 10 tests: exact match, specificity preference, no-match fallback, empty artifacts, single artifact, doubled primary code, en-us default, filter hook, filter override, underscore locale
  • PickReleaseTest.php — 7 tests: latest default, specific version, version not found, sort correctness, single release, empty releases, null version
  • VersionRequirementsTest.php — 9 tests: requires_php, requires_wp, tested_to, all three, empty, caret/tilde strip, non-env ignore, missing requires, missing suggests
  • GetUnmetRequirementsTest.php — 6 tests (+ data provider): all met, unmet PHP, unmet WP, empty, multi-unmet joined, invalid specifiers, unknown env, operator comparison
  • CheckRequirementsTest.php — 4 tests: all met, PHP unmet, WP unmet, empty requires
  • GetIconsTest.php — 7 tests: 1x/2x, wporg SVG default, non-wporg SVG, empty, no valid sizes, only 1x, only 2x
  • GetBannersTest.php — 5 tests: low/high, empty, no valid sizes, only low, only high
  • GetHashedFilenameTest.php — 5 tests: plugin slug with hash, theme slug, no double-append, deterministic, different DIDs
  • ValidatePackageAliasTest.php — 9 tests (cached + uncached): cache hit, cache set, unique keys, no aliases, non-fair aliases, multiple aliases, invalid domain, no TLD, excessively long domain, missing alsoKnownAs, non-string aliases

Bug fixed: pick_release() — added empty-array guard to prevent TypeError on reset() returning false with ?ReleaseDocument return type. Note: get_site_transient converts null''; alias cache test accounts for this.

Phase 4: DTO validation

  • MetadataDocumentTest.php — 13 tests (from_data): all fields, minimal, 5 missing mandatory fields, missing releases, invalid release propagation, optional fields null, multiple releases parsed, security array. 5 tests (from_response): valid response, invalid JSON, valid JSON + invalid data, empty body, null body (TypeError note)
  • ReleaseDocumentTest.php — 10 tests: all fields, with requirements, specific version, missing version, missing artifacts, optional fields null, minimal artifacts, builder with unset fields

Phase 5: DID pipeline transient/HTTP functions (Priority B)

  • CacheUpdateErrorTest.php — 7 tests: cache error, timestamp, lifetime, clear, idempotent, DID isolation
  • GetDidDocumentTest.php — 3 tests: cache hit, cached error, parse error+cache
  • FetchMetadataDocTest.php — 6 tests: cache hit, HTTP failure caching, non-200, cache on success, metadata from HTTP
  • FetchPackageMetadataTest.php — 6 tests: no service, ID mismatch, success, DID error propagation + get_latest_release_from_did (2 tests: success, no keys)
  • PipelineWPTest.php — 20 tests: add_package_to_release_cache (4), maybe_add_accept_header (5), search_by_did (6), get_plugin_information (3)

Uses pre_http_request filter + pre-seeded transients instead of real HTTP calls.

Phase 6: Updater unit tests

  • UpdaterTest.php — 14 tests: register/get plugins (3), register/get themes (2), unknown DID (2), overwrite, get_plugins/get_themes, empty, independent plugin/theme, get_plugin_by_file, unknown file + 8 tests for should_run_on_current_page (plugins, themes, update-core, update, plugin-install, admin-ajax, edit.php, post.php)
  • PackageTest.php — 8 tests: PluginPackage construct+version, slug, relative path, deep nesting, version override + ThemePackage construct+version, slug, type distinction
  • GetTrustedKeysTest.php — 5 tests: no cached DID, fetch failure, no keys, empty verificationMethod, non-fair filtering
  • DisplayPluginUpdateErrorTest.php — 6 tests: no error, non-error transient, error row output, active class, HTML sanitization, colspan
  • GetPackagesTest.php — 4 tests: Plugin ID header, multiple plugins, no header, keys structure

Phase 6b: Updater edge cases

  • GetTrustedKeysTest.php — included above
  • DisplayPluginUpdateErrorTest.php — included above
  • GetPackagesTest.php — included above
  • SignatureVerificationTest.php, RegisterPluginRowHooksTest.php → Phase 14

Uses temp file creation (wp_mkdir_p + file_put_contents) so constructors' get_file_data() calls resolve successfully.

Phase 7: Supplementary module unit tests

  • AvatarsTest.php — 18 tests: should_replace_url (5), generate_default_avatar (8; 1 skipped for color hook bug), get_avatar_alt (4)
  • PingsTest.php — 11 tests: remove_pingomatic (5), get_indexnow_key (4), register_query_vars (2)
  • SaltsTest.php — 9 tests: replace_salt_api (3), define_keynames (1), generate_salt (3), response_body (2), get_response (1)
  • DefaultRepoAndVersionCheckTest.php — 6 tests: default repo domain (2), version-check constants (4) ↳ Compatibility/PolyfillTest.php, Settings/*, Upgrades/* → Phase 14

Bugs found: generate_default_avatar() uses add_filter() instead of apply_filters() for fair_avatars_default_color (1 test skipped). esc_attr() can expand salt strings beyond 64 chars.

Phase 8: Integration harness

  • tests/sites/ephemeral/integration/docker-compose.yml — self-contained WP 6.4 + PHP 8.0 + MariaDB + wp-cli + mock-server
  • tests/sites/ephemeral/Dockerfile.wp — custom WP image
  • tests/mock-server/ — Dockerfile + PHP built-in server emulating PLC Directory and FAIR Repository APIs
  • tests/mock-server/index.php — file-based request log, fixture-driven responses
  • tests/integration/bootstrap.php — loads WP directly (no WP_UnitTestCase needed)
  • tests/integration/phpunit.xml — PHPUnit config
  • tests/sites/ephemeral/integration/seed.php — registers test plugin with DID header
  • bin/run-integration.sh — full lifecycle with trap EXIT teardown guarantee
  • FAIR_PLC_DIRECTORY_URL constant (minimal production change for testability)

Phase 9: Integration tests

  • DidResolutionIntegrationTest.php — 4 tests: mock health, full pipeline, log check (skipped), unknown DID error
  • PackageDataIntegrationTest.php — 5 tests: complete response, _fair metadata, no-service error, unknown DID, caching
  • UpdateTransientIntegrationTest.php — 3 tests: valid transient, seeded plugin (skipped), empty registry ↳ SignatureVerificationIntegrationTest.php, WpCliCompatTest.php → Phase 14

12 integration tests (10 pass, 2 skipped), 38 assertions.

Phase 10: HTTP test harness & tests

  • tests/http/bootstrap.php — loads WP + plugin with admin includes
  • tests/http/phpunit.xml — PHPUnit config
  • SaltApiHttpTest.php — 3 tests: salt API URL interception, 64-char values, passthrough
  • DefaultRepoHttpTest.php — 5 tests: domain config, non-WP.org passthrough, filter registration, plugins/themes API interception
  • AvatarHttpTest.php — 4 tests: should_replace_url, SVG default avatar, alt text for users ↳ AdminAjaxTest.php, PluginsApiTest.php, UpdateTransientShapeTest.php → Phase 14

12 HTTP tests + 12 integration = 24 Docker tests, 76 assertions.

Phase 11: Browser test harness & tests

  • tests/browser/package.json — isolated @playwright/test deps
  • tests/browser/playwright.config.ts — chromium, auth state, CI retries
  • tests/browser/global-setup.ts — login as browser_admin, save storage
  • tests/browser/specs/direct-install.spec.ts — 9 tests: label, pattern, required, submit focus, validation, thickbox role/label/iframe/close (all pass)
  • tests/browser/specs/search-did.spec.ts — 6 tests: searchbox label, DID result, no-results, install button, hostname, heading (all pass) ↳ install-activate-update.spec.ts, avatar-upload.spec.ts, update-error-row.spec.ts → Phase 14

15 tests pass, 0 skipped, 0 failed. Run: npm run test:browser (needs npm run test:browser:docker:start first)


Phase 12: CI workflow

  • .github/workflows/integration-tests.yml — new workflow (complements existing phpunit-tests.yml)
    • docker-tests job: PHP 8.0/8.4 × WP 5.4/latest matrix, runs bin/run-integration.sh (integration + HTTP)
    • browser-tests job: Playwright fast tests, runs on PRs only when labeled run-browser-tests, always on push to main/dev/release
    • browser-slow job: @slow tests (install/activate/update flow), push to main/release only
    • All Docker jobs use trap-based teardown + explicit cleanup verification ↳ Coverage reporting, Slack/Discord notification → Phase 14

Existing phpunit-tests.yml (PHP matrix unit tests) and coding-standards.yml (PHPCS + PHPStan) are preserved unchanged.


Phase 13: Coverage baseline & Infection

  • infection.json — source in inc/, excludes admin/wp-cli/compatibility/settings/upgrades
  • tests/sites/ephemeral/mutation/Dockerfile — PHP 8.5-cli-alpine + mysqli + xdebug
  • tests/sites/ephemeral/mutation/docker-compose.yml — MySQL 8.0 + mutation container
  • tests/infection-bootstrap.php — FAIR autoloader + WordPress class/function stubs
  • composer run infection — runs mutation testing in Docker

Results: 486 mutations, 45 killed, 440 uncovered, 1 escaped, 0 errors, 0 timeouts

  • Covered Code MSI: 97% (excellent test quality for covered code)
  • Overall MSI: 9% (rooms for improvement in uncovered modules)
  • Escaped mutant: ArrayItemRemoval on ReleaseDocument::$optional — minor gap

Phase 14: Deferred & future work

Unit tests

  • SignatureVerificationTest.php — needs real crypto operations, better suited for manual/integration testing
  • RegisterPluginRowHooksTest.php — hook registration verified implicitly through admin_init flow
  • Compatibility/PolyfillTest.php — low priority, polyfills are self-evident
  • Settings/* — heavily WP-hook dependent, needs WP test suite stub improvements
  • Upgrades/* — heavily WP-hook dependent, needs WP test suite stub improvements

Integration tests

  • SignatureVerificationIntegrationTest.php — needs real crypto operations
  • WpCliCompatTest.php — theme support is marked TODO in production code

HTTP tests

  • AdminAjaxHttpTest.php — needs stable admin-ajax endpoint responses from mock server
  • PluginsApiHttpTest.php — covered by browser DID search tests
  • UpdateTransientShapeHttpTest.php — covered in integration update transient tests

Browser tests

  • install-activate-update.spec.ts (@slow) — needs stable plugin data in mock server
  • avatar-upload.spec.ts — needs file upload mock/page in Playwright
  • update-error-row.spec.ts — needs seeded update-error cache state

CI & tooling

  • Coverage reporting in CI (via composer run coverage:full in Docker)
  • Slack/Discord notification on CI failure
  • Regular infection runs in CI on main/RC branches
  • Set minMsi threshold in infection.json once baseline reaches target (e.g. 20%)

Test quality

  • Raise overall MSI by expanding unit test coverage to admin/settings/upgrades modules
  • Run infection against the full inc/ tree (remove exclusions) and kill escaped mutants

Parallelizable work

Phases 37 (unit tests) are largely independent per module and can be parallelized across contributors. Phases 811 are sequential — each builds on the previous. Phase 12 depends on all tests being in place. Phase 14 items are independent and can be tackled in any order.