79a6c4d724
- Add TMDO_Promotion_Guard: HivePress models read every meta key with an empty-key get_*_meta() and WooCommerce does the same for products (and writes stock/sales with direct SQL), so on those sites native meta must stay authoritative. Promotion to aeav_only is refused in TMDO_Mode_Manager::set() (covers REST, CLI, phases, auto-promoter) for post/user/term/comment with HivePress and post/user with WooCommerce; TMDO_Post_Migration::cleanup() and the cleanup phase refuse too. Demotion stays open; filter tmdo_promotion_guard_allow overrides. - Admin notice lists guarded entities already in aeav_only with the restore-native command (no forced demotion on read). - Verify: PromotionGuardTest 8 OK; unit 668 OK, integration 480 OK, phpcs (phpcs.xml) 0, stan OK; dev36 live: 4 promotions + post-cleanup refused, wpdo_bridge_modes unchanged - PR: PR-053
164 lines
5.7 KiB
PHP
164 lines
5.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Promotion Guard: HivePress / WooCommerce read native meta directly
|
|
* (empty-key get_*_meta(), direct SQL), so on those sites native meta must
|
|
* stay authoritative — no aeav_only, no cleanup of native rows.
|
|
*
|
|
* Tests that declare HivePress / WooCommerce stand-in classes run in their own
|
|
* process so those classes do not leak into other tests.
|
|
*/
|
|
class PromotionGuardTest extends TestCase {
|
|
|
|
protected function setUp(): void {
|
|
if ( ! class_exists( 'TMDO_Promotion_Guard_Notice' ) ) {
|
|
require_once TMDO_PATH . 'admin/class-tmdo-promotion-guard-notice.php';
|
|
}
|
|
// Mode changes log through error_log(); in a separate process PHPUnit
|
|
// treats that stderr output as an error.
|
|
ini_set( 'error_log', '/dev/null' );
|
|
$GLOBALS['_wp_options'] = array();
|
|
$GLOBALS['_wp_filter_callbacks'] = array();
|
|
$GLOBALS['_wp_current_user_can'] = array( 'manage_options' => true );
|
|
TMDO_Mode_Manager::reset_cache();
|
|
}
|
|
|
|
private function modes( array $modes ): void {
|
|
$GLOBALS['_wp_options']['wpdo_bridge_modes'] = $modes;
|
|
TMDO_Mode_Manager::reset_cache();
|
|
}
|
|
|
|
public function test_without_integrations_aeav_only_is_allowed(): void {
|
|
$this->modes( array( 'post' => 'shadow_read' ) );
|
|
|
|
$this->assertTrue( TMDO_Mode_Manager::set( 'post', 'aeav_only' ) );
|
|
$this->assertNull( TMDO_Promotion_Guard::check_cleanup( 'post' ) );
|
|
$this->assertSame( array(), TMDO_Promotion_Guard::violations() );
|
|
}
|
|
|
|
/**
|
|
* @runInSeparateProcess
|
|
* @preserveGlobalState disabled
|
|
*/
|
|
public function test_hivepress_refuses_aeav_only_for_every_entity(): void {
|
|
eval( 'namespace HivePress; class Core {}' );
|
|
|
|
foreach ( array( 'post', 'user', 'term', 'comment' ) as $type ) {
|
|
$this->modes( array( $type => 'shadow_read' ) );
|
|
|
|
$result = TMDO_Mode_Manager::set( $type, 'aeav_only' );
|
|
|
|
$this->assertInstanceOf( WP_Error::class, $result, $type );
|
|
$this->assertSame( 'promotion_guarded', $result->get_error_code(), $type );
|
|
$this->assertStringContainsString( 'HivePress', $result->get_error_message(), $type );
|
|
$this->assertSame( 'shadow_read', TMDO_Mode_Manager::get( $type ), "{$type} mode must not change" );
|
|
$this->assertSame( array( $type => 'shadow_read' ), $GLOBALS['_wp_options']['wpdo_bridge_modes'], "{$type} option must not change" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @runInSeparateProcess
|
|
* @preserveGlobalState disabled
|
|
*/
|
|
public function test_woocommerce_guards_post_and_user_only(): void {
|
|
eval( 'class WooCommerce {}' );
|
|
|
|
$this->modes( array( 'post' => 'shadow_read', 'user' => 'shadow_read', 'term' => 'shadow_read' ) );
|
|
|
|
$this->assertInstanceOf( WP_Error::class, TMDO_Mode_Manager::set( 'post', 'aeav_only' ) );
|
|
$this->assertInstanceOf( WP_Error::class, TMDO_Mode_Manager::set( 'user', 'aeav_only' ) );
|
|
$this->assertTrue( TMDO_Mode_Manager::set( 'term', 'aeav_only' ) );
|
|
}
|
|
|
|
/**
|
|
* @runInSeparateProcess
|
|
* @preserveGlobalState disabled
|
|
*/
|
|
public function test_guard_allows_lower_modes_and_demotion(): void {
|
|
eval( 'namespace HivePress; class Core {}' );
|
|
|
|
$this->modes( array( 'post' => 'disabled' ) );
|
|
$this->assertTrue( TMDO_Mode_Manager::set( 'post', 'dual_write' ) );
|
|
$this->assertTrue( TMDO_Mode_Manager::set( 'post', 'shadow_read' ) );
|
|
|
|
$this->modes( array( 'post' => 'aeav_only' ) );
|
|
$this->assertTrue( TMDO_Mode_Manager::set( 'post', 'dual_write' ), 'the way out of aeav_only stays open' );
|
|
}
|
|
|
|
/**
|
|
* @runInSeparateProcess
|
|
* @preserveGlobalState disabled
|
|
*/
|
|
public function test_filter_can_allow_a_guarded_promotion(): void {
|
|
eval( 'namespace HivePress; class Core {}' );
|
|
add_filter(
|
|
'tmdo_promotion_guard_allow',
|
|
static function ( $allow, $entity_type, $action, $integrations ) {
|
|
return 'user' === $entity_type && 'aeav_only' === $action && array( 'HivePress' ) === $integrations;
|
|
},
|
|
10,
|
|
4
|
|
);
|
|
|
|
$this->modes( array( 'user' => 'shadow_read', 'post' => 'shadow_read' ) );
|
|
|
|
$this->assertTrue( TMDO_Mode_Manager::set( 'user', 'aeav_only' ) );
|
|
$this->assertInstanceOf( WP_Error::class, TMDO_Mode_Manager::set( 'post', 'aeav_only' ) );
|
|
}
|
|
|
|
/**
|
|
* @runInSeparateProcess
|
|
* @preserveGlobalState disabled
|
|
*/
|
|
public function test_cleanup_is_refused_even_in_aeav_only(): void {
|
|
eval( 'namespace HivePress; class Core {}' );
|
|
$this->modes( array( 'post' => 'aeav_only', 'user' => 'aeav_only' ) );
|
|
|
|
$error = TMDO_Promotion_Guard::check_cleanup( 'post' );
|
|
$this->assertInstanceOf( WP_Error::class, $error );
|
|
|
|
try {
|
|
TMDO_Post_Migration::cleanup();
|
|
$this->fail( 'post cleanup must be refused' );
|
|
} catch ( RuntimeException $e ) {
|
|
$this->assertStringContainsString( 'HivePress', $e->getMessage() );
|
|
}
|
|
|
|
$job = array( 'log' => array(), 'options' => array() );
|
|
try {
|
|
( new TMDO_Phase_Cleanup( 'user' ) )->execute( $job );
|
|
$this->fail( 'user cleanup phase must be refused' );
|
|
} catch ( RuntimeException $e ) {
|
|
$this->assertStringContainsString( 'HivePress', $e->getMessage() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @runInSeparateProcess
|
|
* @preserveGlobalState disabled
|
|
*/
|
|
public function test_sites_already_in_aeav_only_are_reported(): void {
|
|
eval( 'class WooCommerce {}' );
|
|
$this->modes( array( 'post' => 'aeav_only', 'term' => 'aeav_only' ) );
|
|
|
|
$this->assertSame( array( 'post' => array( 'WooCommerce' ) ), TMDO_Promotion_Guard::violations() );
|
|
|
|
ob_start();
|
|
TMDO_Promotion_Guard_Notice::render();
|
|
$html = (string) ob_get_clean();
|
|
$this->assertStringContainsString( 'post', $html );
|
|
$this->assertStringContainsString( 'bridge-restore-native post', $html );
|
|
}
|
|
|
|
public function test_notice_is_silent_without_violations(): void {
|
|
$this->modes( array( 'post' => 'aeav_only' ) );
|
|
|
|
ob_start();
|
|
TMDO_Promotion_Guard_Notice::render();
|
|
$this->assertSame( '', ob_get_clean() );
|
|
}
|
|
}
|