Files
2meet-data-optimizer/tests/unit/PromotionGuardTest.php
T
wpdev 79a6c4d724 fix(2meet-data-optimizer): refuse aeav_only and cleanup on HivePress / WooCommerce sites
- 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
2026-09-26 18:29:04 +08:00

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() );
}
}