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