Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions tests/Integration/WpCliFixArchiveTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php
/**
* Tests for the `wp liveblog fix-archive` WP-CLI command.
*
* @package Automattic\Liveblog\Tests\Integration
*/

declare( strict_types=1 );

namespace Automattic\Liveblog\Tests\Integration;

use Yoast\WPTestUtils\WPIntegration\TestCase;
use WPCOM_Liveblog_Entry;
use WPCOM_Liveblog_WP_CLI;

require_once __DIR__ . '/wp-cli-stub.php';

if ( ! class_exists( 'WPCOM_Liveblog_WP_CLI' ) ) {
require_once dirname( __DIR__, 2 ) . '/classes/class-wpcom-liveblog-wp-cli.php';
}

/**
* The fix-archive command test case.
*/
final class WpCliFixArchiveTest extends TestCase {

/**
* Test post ID.
*
* @var int
*/
private int $post_id;

/**
* Command instance under test.
*
* @var WPCOM_Liveblog_WP_CLI
*/
private WPCOM_Liveblog_WP_CLI $command;

/**
* Set up before each test.
*/
public function set_up(): void {
parent::set_up();

\WP_CLI::reset();

$this->command = new WPCOM_Liveblog_WP_CLI();
$this->post_id = self::factory()->post->create();
}

/**
* Create a root liveblog entry comment with no comment meta.
*
* The fix_archive() method treats any comment on the post with zero
* commentmeta rows as a "correct"/root entry, so this must stay meta-free.
*
* @param string $content Comment content.
* @return int Comment ID.
*/
private function create_root_entry( string $content ): int {
return self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_approved' => 'liveblog',
'comment_type' => 'liveblog',
'comment_content' => $content,
)
);
}

/**
* Create an "edit" ghost comment pointing at $replaces via liveblog_replaces meta.
*
* @param int $replaces Comment ID this ghost claims to replace.
* @param string $content Comment content.
* @return int Comment ID.
*/
private function create_edit_entry( int $replaces, string $content ): int {
$comment_id = self::factory()->comment->create(
array(
'comment_post_ID' => $this->post_id,
'comment_approved' => 'liveblog',
'comment_type' => 'liveblog',
'comment_content' => $content,
)
);
add_comment_meta( $comment_id, WPCOM_Liveblog_Entry::REPLACES_META_KEY, $replaces );
return $comment_id;
}

/**
* Build a liveblog with a correctly-pointed edit and a mis-pointed edit.
*
* Mirrors the archived-liveblog corruption fix-archive repairs: a second
* edit whose liveblog_replaces meta points at the first ghost instead of
* collapsing back to the root entry.
*
* @return array{root:int,ghost:int,broken:int} Comment IDs.
*/
private function create_liveblog_with_broken_replaces(): array {
add_post_meta( $this->post_id, 'liveblog', 'enable' );

$root = $this->create_root_entry( 'original content' );
$ghost = $this->create_edit_entry( $root, 'first edit' );
$broken = $this->create_edit_entry( $ghost, 'second edit' );

return array(
'root' => $root,
'ghost' => $ghost,
'broken' => $broken,
);
}

/**
* Test that --dryrun reports intended changes without modifying data.
*/
public function test_fix_archive_dry_run_does_not_modify_data(): void {
$ids = $this->create_liveblog_with_broken_replaces();

$this->command->fix_archive( array(), array( 'dryrun' => true ) );

$this->assertSame(
(string) $ids['ghost'],
get_comment_meta( $ids['broken'], WPCOM_Liveblog_Entry::REPLACES_META_KEY, true ),
'Dry run must not touch liveblog_replaces meta.'
);
}

/**
* Test that a real run corrects a mis-pointed liveblog_replaces value.
*/
public function test_fix_archive_corrects_incorrect_replaces_meta(): void {
$ids = $this->create_liveblog_with_broken_replaces();

$this->command->fix_archive( array(), array() );

$this->assertSame(
(string) $ids['root'],
get_comment_meta( $ids['broken'], WPCOM_Liveblog_Entry::REPLACES_META_KEY, true ),
'fix-archive should repoint the broken edit at the root entry.'
);
$this->assertTrue( \WP_CLI::$success_called );
}

/**
* Test that no liveblog posts at all completes without error.
*/
public function test_fix_archive_handles_no_liveblogs_found(): void {
$this->command->fix_archive( array(), array() );

$this->assertTrue( \WP_CLI::$success_called );
}

/**
* Test that a liveblog with no edited entries completes without error.
*/
public function test_fix_archive_handles_liveblog_with_no_edited_entries(): void {
add_post_meta( $this->post_id, 'liveblog', 'enable' );
$this->create_root_entry( 'only entry, never edited' );

$this->command->fix_archive( array(), array() );

$this->assertTrue( \WP_CLI::$success_called );
}
}
86 changes: 86 additions & 0 deletions tests/Integration/wp-cli-stub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php
/**
* Minimal WP_CLI stubs so wp-cli command classes can be loaded and exercised
* from PHPUnit, where the real WP-CLI runtime isn't present.
*
* @package Automattic\Liveblog\Tests\Integration
*/

// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Stubs for external WP-CLI classes.
// phpcs:disable Squiz.Classes.ClassFileName.NoMatch -- Stub file containing multiple classes.
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound -- Stub file containing multiple classes.
// phpcs:disable Universal.NamingConventions.NoReservedKeywordParameterNames -- Mirrors the real WP_CLI signature.

if ( ! class_exists( 'WP_CLI_Command' ) ) {
/**
* Stub for the WP-CLI base command class.
*/
class WP_CLI_Command {}
}

if ( ! class_exists( 'WP_CLI' ) ) {
/**
* Stub for the WP_CLI static API.
*
* Only implements what `fix_archive()` actually calls. Tracks whether
* success() was reached so tests can assert the command completed
* without a fatal, without needing a full call log.
*/
class WP_CLI {

/**
* Whether success() has been called since the last reset().
*
* @var bool
*/
public static $success_called = false;

/**
* Reset tracked state between tests.
*
* @return void
*/
public static function reset() {
self::$success_called = false;
}

/**
* Stub for WP_CLI::add_command(). No-op.
*
* @param string $name Command name.
* @param mixed $callable Command handler.
* @return void
*/
public static function add_command( $name, $callable ) {}

/**
* Stub for WP_CLI::log(). No-op.
*
* @param string $message Message to log.
* @return void
*/
public static function log( $message ) {}

/**
* Stub for WP_CLI::success(). Records that it was reached.
*
* @param string $message Message to log.
* @return void
*/
public static function success( $message ) {
self::$success_called = true;
}

/**
* Stub for WP_CLI::colorize(). Returns the string unchanged.
*
* @param string $string String to colorize.
* @return string
*/
public static function colorize( $string ) {
return $string;
}
}
}

// phpcs:enable
Loading