Skip to content

feat: introduce CLI commands with integration test infrastructure#197

Open
GaryJones wants to merge 16 commits into3.xfrom
refactor/cli-test-strategy
Open

feat: introduce CLI commands with integration test infrastructure#197
GaryJones wants to merge 16 commits into3.xfrom
refactor/cli-test-strategy

Conversation

@GaryJones
Copy link
Contributor

Summary

  • CLI commands (push-post, pull-site, push-all-posts, pull-sitegroup, sites-list, sitegroups-list) wired into the DI container via PluginBootstrapper, each as a thin adapter delegating to PushService or PullService
  • 22 PHPUnit integration tests exercise commands against a real WordPress database, following the testing pyramid from plugin-standards/TESTING.md
  • WpCliStub replaces real WP_CLI during tests, capturing output for assertion without fatal exits on error calls
  • CliTestCase and WpCliOutputCapture provide concise assertion helpers for CLI tests
  • Application contracts (PullServiceInterface, PushServiceInterface) give the CLI layer a stable boundary
  • Behat README documents the CLI testing pyramid strategy

Test plan

  • 22 CLI integration tests pass (composer test:integration -- --group=cli)
  • 246 existing unit tests still pass (composer test:unit)
  • Existing Behat tests still pass (composer behat)
  • CI passes

🤖 Generated with Claude Code

GaryJones and others added 15 commits January 13, 2026 12:32
Add PushService and PullService to orchestrate syndication workflows:
- PushService: handles push syndication with locking, state tracking,
  and support for create/update/delete operations
- PullService: handles pull syndication with import mode, GUID-based
  deduplication, and batch processing

Add immutable DTOs for operation results:
- PushResult: success/failure/skipped states with remote ID tracking
- PullResult: includes partial status for multi-post operations

Create TransportFactoryInterface to enable proper unit testing through
dependency inversion. Services now depend on the interface rather than
the concrete factory class.

All 204 unit tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create Bootstrapper class as the main entry point for the new DDD
architecture. The Bootstrapper:
- Initialises the DI container as a singleton
- Registers PushService and PullService in the container
- Provides hook manager access for WordPress integration
- Includes reset() method for testing support

Add syn_get_container action hook to provide container access
to legacy code during the migration period.

All 215 unit tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ation

Wire up the new DDD architecture in the main plugin file:
- Initialise Bootstrapper after autoloader registration
- Add syndication_container() helper function for legacy code access

Add HookRegistrar to mirror WP_Push_Syndication_Server hook registration:
- Registers WordPress core hooks (init, admin_init, save_post, etc.)
- Empty placeholder handlers with @todo comments for implementation
- on_cron_schedules implemented (adds syn_pull_time_interval)

When ready to migrate:
1. Implement placeholder handlers using new services
2. Remove legacy handlers from WP_Push_Syndication_Server
3. Delete legacy code

All 227 unit tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract post type (syn_site) and taxonomy (syn_sitegroup) registration
into a dedicated PostTypeRegistrar class. This encapsulates the
registration logic previously in WP_Push_Syndication_Server::init()
and makes it available through the DI container.

Key changes:
- PostTypeRegistrar handles all registration with idempotent checks
- HookRegistrar.on_init() now uses PostTypeRegistrar via the container
- Supports syn_syndicate_cap filter for custom capability requirements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive tests for the DDD architecture components:

Unit tests:
- ContainerTest: Tests DI container registration, resolution, caching,
  and get_services_by_interface functionality

Integration tests:
- BootstrapperIntegrationTest: Verifies plugin bootstrap, container
  availability, services, and global helper function
- PostTypeRegistrationTest: Confirms syn_site post type and syn_sitegroup
  taxonomy are registered with correct properties
- HookRegistrationTest: Validates all WordPress hooks are registered
  and functioning correctly

Test counts: 250 unit tests, 31 new integration tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wire up the HookRegistrar handlers to use the new DDD services:

- on_transition_post_status: Saves selected site groups and schedules
  push content via the syn_schedule_push_content action
- on_trash_post: Deletes syndicated content from remote sites using
  PushService when delete_pushed_posts setting is enabled
- on_save_post: Triggers pull job refresh when syn_site posts change
- on_delete_post: Triggers pull job refresh when syn_site posts are deleted
- on_create_term/on_delete_term: Trigger pull job refresh when
  syn_sitegroup terms change

Includes debounced scheduling for pull job refresh to prevent
timeout issues when many sites are configured.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add background processing for push operations to handle large networks:

- on_schedule_push_content: Schedules cron event for immediate background
  execution and spawns cron to minimize delay
- on_push_content: Executes push via PushService in cron context,
  handling both selected sites (push) and removed sites (delete)
- extract_site_ids: Helper to handle both legacy WP_Post objects and
  new integer site IDs for backward compatibility

This ensures pushing to many sites (50+) doesn't block the editor
experience. The push runs as a background task via WordPress cron.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements async pull handlers in HookRegistrar to mirror the existing push architecture. The legacy plugin manages pull operations through scheduled cron jobs that need migration to the new service-based approach.

Adds syn_pull_content hook handler that delegates to PullService for async content pulling from remote sites, with support for configurable update behaviour. Adds syn_refresh_pull_jobs handler to reschedule all pull jobs when site configuration changes, ensuring the cron schedule stays synchronised with site and sitegroup modifications.

Includes helper methods for retrieving configured pull sites from sitegroups and scheduling individual cron jobs per site, replacing the legacy batch scheduling approach.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces granular per-post logging with consolidated per-sync-run entries
to reduce noise and improve log clarity. The previous system logged every
single post operation separately, making it difficult to understand overall
sync outcomes and cluttering the interface with redundant information when
nothing had actually changed.

Key improvements:

- Consolidated logging: One log entry per pull sync run (per site) and one
  entry per push operation (covering all target sites), rather than
  individual entries for every post
- Content hash detection: Skips logging "updated" actions when post content
  hasn't actually changed, eliminating false positives from identical syncs
- Separate admin interfaces: Dedicated Pull Logs and Push Logs pages with
  filtering, search, and visual status badges for easier troubleshooting
- Per-site configuration: Configure default post status for pulled content
  (draft/pending/publish) and customisable log retention limits (1-1000
  entries per site)
- Remote post links: Links now point to front-end URLs rather than admin
  URLs, as syndicated users may not have admin access on remote sites
- Smart legacy handling: Legacy logs page only appears when old logs exist
  (moved to menu priority 20), with clear messaging directing users to new
  log pages

Technical changes:

- Adds SyndicationLog singleton with structured logging methods and status
  constants (success/partial/error/skipped)
- Pull logs stored in post meta per-site, push logs stored in options table
- Updates PullService and PushService to integrate consolidated logging
  throughout sync workflows
- Implements PullLogViewer and PushLogViewer admin interfaces with proper
  list tables, pagination, and filtering
- Updates tests to account for new PullServiceInterface

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The legacy failure handling system used three interconnected classes:
- Syndication_Event_Counter (counting failures)
- Syndication_Site_Failure_Monitor (disabling sites after max failures)
- Failed_Syndication_Auto_Retry (immediate retries before scheduled pulls)

This consolidates all functionality into a single SiteHealthMonitor class
that listens to the same legacy hooks fired by transports. The new class
provides clearer admin notices with dismiss functionality and uses
simpler option-based storage for failure counts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove duplicate syn_pull_content, syn_push_content, syn_schedule_push_content
  handlers from server class (now handled by PluginBootstrapper)
- Remove Syndication_Logger::init() call (no longer needed for writing)
- Delete Syndication_Admin_Notices class (was only used by legacy logger)
- Keep Syndication_Logger for reading existing legacy logs
- Legacy log viewer instantiated directly, only shows when legacy logs exist

This eliminates double processing of cron events and removes unused
admin notice infrastructure while preserving backward compatibility
for viewing existing legacy logs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This class was not referenced anywhere in the codebase.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Eliminates 335 lines of code that duplicated functionality already implemented in the new service-oriented architecture. The removed hook registrations and methods (schedule_push_content, push_content, pull_content, refresh_pull_jobs, and related handlers) were superseded by PluginBootstrapper's event handling and the PushService/PullService implementations.

This continues the architectural migration from the monolithic legacy class towards proper separation of concerns through dependency injection and dedicated service classes. The removed code was entirely unreachable, as the new architecture takes precedence in hook execution order.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The cron_add_pull_time_interval method in WP_Push_Syndication_Server duplicated functionality now provided by PluginBootstrapper::on_cron_schedules(). Removed the duplicate method and filter registration to centralise cron schedule management in the bootstrapper, eliminating 20 lines of redundant code and ensuring a single source of truth for custom cron intervals.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CLI commands (push-post, pull-site, push-all-posts, pull-sitegroup,
sites-list, sitegroups-list) are wired into the DI container via
PluginBootstrapper. Each command is a thin adapter that delegates to
PushService or PullService, keeping business logic in the application
layer where it is already tested.

The test strategy follows the pyramid from TESTING.md: 22 PHPUnit
integration tests exercise commands against a real WordPress database,
while Behat is reserved for CLI contract verification (argument
parsing, output format). A WpCliStub replaces the real WP_CLI class
during integration tests, preventing the fatal exit on error calls
and capturing output for assertion. CliTestCase and WpCliOutputCapture
provide the assertion helpers that keep individual test bodies concise.

Application contracts (PullServiceInterface, PushServiceInterface)
give the CLI layer a stable boundary to programme against,
independent of transport implementation details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@GaryJones GaryJones requested a review from a team as a code owner January 29, 2026 11:31
@GaryJones GaryJones self-assigned this Jan 29, 2026
The WP_Error stub used the `mixed` type hint which is only available
in PHP 8.0+, causing a fatal error on PHP 7.4 CI runs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.

1 participant