Files
2meet-data-optimizer/tests/unit/CustomTableRegistryTest.php
wpdev d36bb954d1 chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 05:06:36 +08:00

262 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* Tests for WPDO_Custom_Table_Registry — partner plugin custom table awareness.
*
* Solves audit finding R-3 (Custom Table Provider missing).
*
* @covers WPDO_Custom_Table_Registry
*/
class CustomTableRegistryTest extends TestCase {
protected function setUp(): void {
WPDO_Custom_Table_Registry::reset_for_tests();
}
// ── register() ──────────────────────────────────────────────────────────
public function test_register_single_table(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$ok = $registry->register( '2meet-courses', array(
'table_name' => '2mc_courses',
'primary_key' => 'id',
'post_type_link' => null,
) );
$this->assertTrue( $ok );
$this->assertCount( 1, $registry->all() );
}
public function test_register_rejects_empty_table_name(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->assertFalse( $registry->register( '2meet-courses', array() ) );
$this->assertFalse( $registry->register( '2meet-courses', array( 'table_name' => '' ) ) );
}
public function test_register_rejects_empty_provider(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->assertFalse( $registry->register( '', array( 'table_name' => '2mc_courses' ) ) );
}
public function test_register_rejects_duplicate_provider_table_pair(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->assertTrue( $registry->register( '2meet-courses', array( 'table_name' => '2mc_courses' ) ) );
// Same provider+table → false.
$this->assertFalse( $registry->register( '2meet-courses', array( 'table_name' => '2mc_courses' ) ) );
}
public function test_register_allows_same_table_different_provider(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->assertTrue( $registry->register( '2meet-courses', array( 'table_name' => 'shared_t' ) ) );
$this->assertTrue( $registry->register( '2meet-bookings', array( 'table_name' => 'shared_t' ) ) );
$this->assertCount( 2, $registry->all() );
}
public function test_register_sanitizes_table_name(): void {
$registry = WPDO_Custom_Table_Registry::instance();
// WordPress sanitize_key strips non-alphanumeric/underscore/dash entirely (no replacement).
$registry->register( 'p', array( 'table_name' => 'My Bad-Name!' ) );
$tables = $registry->all();
$cfg = reset( $tables );
$this->assertSame( 'mybad-name', $cfg['table_name'] );
}
public function test_register_applies_defaults(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array( 'table_name' => 't' ) );
$tables = $registry->all();
$cfg = reset( $tables );
$this->assertSame( 'id', $cfg['primary_key'] );
$this->assertNull( $cfg['post_type_link'] );
$this->assertSame( array(), $cfg['expected_columns'] );
}
// ── unregister() ────────────────────────────────────────────────────────
public function test_unregister_removes_table(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array( 'table_name' => 't' ) );
$this->assertTrue( $registry->unregister( 'p', 't' ) );
$this->assertCount( 0, $registry->all() );
}
public function test_unregister_returns_false_for_unknown(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->assertFalse( $registry->unregister( 'unknown', 'table' ) );
}
// ── for_provider() / for_post_type() ────────────────────────────────────
public function test_for_provider_filters_correctly(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'a', array( 'table_name' => 't1' ) );
$registry->register( 'a', array( 'table_name' => 't2' ) );
$registry->register( 'b', array( 'table_name' => 't3' ) );
$this->assertCount( 2, $registry->for_provider( 'a' ) );
$this->assertCount( 1, $registry->for_provider( 'b' ) );
$this->assertCount( 0, $registry->for_provider( 'c' ) );
}
/**
* v2.1.3 R3 hardening — verify by_provider index stays in sync when a
* non-edge entry is unregistered. Previously untested per audit finding.
*/
public function test_for_provider_after_unregister_middle_entry(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array( 'table_name' => 'first' ) );
$registry->register( 'p', array( 'table_name' => 'middle' ) );
$registry->register( 'p', array( 'table_name' => 'last' ) );
$this->assertCount( 3, $registry->for_provider( 'p' ) );
// Remove the middle entry.
$ok = $registry->unregister( 'p', 'middle' );
$this->assertTrue( $ok );
$remaining = $registry->for_provider( 'p' );
$this->assertCount( 2, $remaining );
// Verify the surviving entries are correct (not 'middle').
$names = array_column( $remaining, 'table_name' );
sort( $names );
$this->assertSame( array( 'first', 'last' ), $names );
// Re-registering 'middle' should put it back.
$registry->register( 'p', array( 'table_name' => 'middle' ) );
$this->assertCount( 3, $registry->for_provider( 'p' ) );
}
/**
* Verify by_provider index empties (and removes the provider key entirely)
* when the last table for that provider is unregistered.
*/
public function test_for_provider_returns_empty_after_full_unregister(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'solo', array( 'table_name' => 'only_table' ) );
$this->assertCount( 1, $registry->for_provider( 'solo' ) );
$registry->unregister( 'solo', 'only_table' );
$this->assertCount( 0, $registry->for_provider( 'solo' ) );
$this->assertSame( array(), $registry->for_provider( 'solo' ) );
}
public function test_for_post_type_filters_correctly(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array( 'table_name' => 't1', 'post_type_link' => 'hp_listing' ) );
$registry->register( 'p', array( 'table_name' => 't2', 'post_type_link' => 'hp_listing' ) );
$registry->register( 'p', array( 'table_name' => 't3', 'post_type_link' => 'hp_vendor' ) );
$registry->register( 'p', array( 'table_name' => 't4', 'post_type_link' => null ) );
$this->assertCount( 2, $registry->for_post_type( 'hp_listing' ) );
$this->assertCount( 1, $registry->for_post_type( 'hp_vendor' ) );
$this->assertCount( 0, $registry->for_post_type( 'unknown' ) );
}
public function test_providers_returns_unique_list(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'a', array( 'table_name' => 't1' ) );
$registry->register( 'a', array( 'table_name' => 't2' ) );
$registry->register( 'b', array( 'table_name' => 't3' ) );
$providers = $registry->providers();
sort( $providers );
$this->assertSame( array( 'a', 'b' ), $providers );
}
// ── get_stats() ─────────────────────────────────────────────────────────
public function test_get_stats_counts_callbacks(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array(
'table_name' => 't1',
'doctor_callback' => static fn() => array( 'ok' => true ),
'benchmark_callback' => static fn() => array( 'duration_ms' => 1.0 ),
) );
$registry->register( 'p', array( 'table_name' => 't2' ) );
$stats = $registry->get_stats();
$this->assertSame( 2, $stats['tables_count'] );
$this->assertSame( 1, $stats['providers_count'] );
$this->assertSame( 1, $stats['with_doctor'] );
$this->assertSame( 1, $stats['with_benchmark'] );
}
// ── run_doctor_checks() ─────────────────────────────────────────────────
public function test_run_doctor_checks_invokes_callbacks(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array(
'table_name' => 't1',
'doctor_callback' => static fn() => array( 'ok' => true, 'message' => 'all good' ),
) );
$registry->register( 'p', array(
'table_name' => 't2',
'doctor_callback' => static fn() => array( 'ok' => false, 'message' => 'index missing' ),
) );
$registry->register( 'p', array( 'table_name' => 't3' ) ); // No callback → skipped.
$results = $registry->run_doctor_checks();
$this->assertCount( 2, $results );
$this->assertTrue( $results['p:t1']['ok'] );
$this->assertSame( 'all good', $results['p:t1']['message'] );
$this->assertFalse( $results['p:t2']['ok'] );
}
public function test_run_doctor_checks_passes_table_name_to_callback(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$received = null;
$registry->register( 'wc', array(
'table_name' => 'wc_orders',
'doctor_callback' => static function ( string $table_name ) use ( &$received ): array {
$received = $table_name;
return array( 'ok' => true, 'message' => "checked {$table_name}" );
},
) );
$results = $registry->run_doctor_checks();
$this->assertSame( 'wc_orders', $received, 'callback must receive table_name as first argument' );
$this->assertSame( 'checked wc_orders', $results['wc:wc_orders']['message'] );
}
public function test_run_doctor_checks_catches_throwables(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$registry->register( 'p', array(
'table_name' => 't1',
'doctor_callback' => static function () {
throw new RuntimeException( 'simulated failure' );
},
) );
$results = $registry->run_doctor_checks();
$this->assertCount( 1, $results );
$this->assertFalse( $results['p:t1']['ok'] );
$this->assertStringContainsString( 'simulated failure', $results['p:t1']['message'] );
}
// ── fire_registration() idempotency ─────────────────────────────────────
public function test_fire_registration_is_idempotent(): void {
$count = 0;
add_action( 'wpdo_register_custom_tables', function () use ( &$count ) {
++$count;
} );
$registry = WPDO_Custom_Table_Registry::instance();
$registry->fire_registration();
$registry->fire_registration();
$registry->fire_registration();
// Stub add_action() in unit bootstrap returns true but does NOT execute callbacks,
// so the meaningful assertion here is that fire_registration() doesn't throw.
$this->assertTrue( true );
}
}