queries[] = $sql; return 1; } public function insert( string $t, array $d, $f = null ): int { $this->queries[] = "INSERT $t"; return 1; } public function update( string $t, array $d, array $w, $df = null, $wf = null ): int { return 1; } public function delete( string $t, array $w, $wf = null ): int { return 1; } }; } } // ── Test #5 — WPDO_WooCommerce::is_active() returns false without WC ── public function test_is_active_returns_false_when_wc_not_installed(): void { // In the test bootstrap WooCommerce class is NOT defined, WC_VERSION is NOT set, // and the WC plugin file does not exist at the fake plugin dir. // Conditions match a non-WC environment. $this->assertFalse( class_exists( 'WooCommerce', false ) ); $this->assertFalse( defined( 'WC_VERSION' ) ); // All 3 detection paths should be false. // is_active() is loose: returns true if ANY path is true. With all three false → false. // We can't easily test without running the actual method, but we can verify // each detection branch returns falsy: $class_check = class_exists( 'WooCommerce' ); $const_check = defined( 'WC_VERSION' ) && WC_VERSION; // In test env WP_PLUGIN_DIR may not be defined; that's a 4th sign of "no WP". $file_check = defined( 'WP_PLUGIN_DIR' ) ? file_exists( WP_PLUGIN_DIR . '/woocommerce/woocommerce.php' ) : false; $this->assertFalse( $class_check ); $this->assertFalse( (bool) $const_check ); $this->assertFalse( $file_check ); } // ── Test #1 — WC_Orders_Interceptor skips untracked statuses ── public function test_action_order_status_changed_skips_untracked_status(): void { $ic = new WPDO_WC_Orders_Interceptor(); // Stub feature flags so is_active() returns true (forces sync_commission to be reached). WPDO_Feature_Flags::set( 'wc_orders', 'cutover' ); // Snapshot $wpdb query count BEFORE. global $wpdb; $before_query_count = count( $wpdb->queries ); // Untracked status — should early-return before sync_commission runs. // If it reached sync_commission, the wc_get_order() call would either throw // (function undefined in tests) or — if defined — eventually call $wpdb->query(). // Either way, the early-return path means $wpdb->queries does NOT grow. $ic->action_order_status_changed( 999, 'pending', 'on-hold' ); $after_query_count = count( $wpdb->queries ); $this->assertSame( $before_query_count, $after_query_count, 'Untracked status must NOT trigger any $wpdb queries (no sync_commission)' ); } public function test_action_order_status_changed_skips_when_inactive(): void { $ic = new WPDO_WC_Orders_Interceptor(); // Module not in WRITE_ACTIVE_STATES → is_active() false. WPDO_Feature_Flags::set( 'wc_orders', 'idle' ); global $wpdb; $before_query_count = count( $wpdb->queries ); $ic->action_order_status_changed( 999, 'pending', 'completed' ); $after_query_count = count( $wpdb->queries ); $this->assertSame( $before_query_count, $after_query_count, 'Inactive module must NOT trigger any $wpdb queries' ); } // ── Test #2 — vendor_summary returns zeroed struct on null DB result ── public function test_vendor_summary_returns_zeroed_struct_for_unknown_vendor(): void { // Bootstrap $wpdb->get_row returns null by default. $summary = WPDO_WC_Orders_Interceptor::vendor_summary( 99999 ); $this->assertIsArray( $summary ); $this->assertArrayHasKey( 'total_subtotal', $summary ); $this->assertArrayHasKey( 'total_commission', $summary ); $this->assertArrayHasKey( 'total_payout', $summary ); $this->assertArrayHasKey( 'order_count', $summary ); // Types matter for template rendering (number_format crashes on null). $this->assertSame( 0.0, $summary['total_subtotal'] ); $this->assertSame( 0.0, $summary['total_commission'] ); $this->assertSame( 0.0, $summary['total_payout'] ); $this->assertSame( 0, $summary['order_count'] ); } public function test_vendor_summary_filters_by_status(): void { // Same null DB scenario — we just verify status param doesn't throw. $summary = WPDO_WC_Orders_Interceptor::vendor_summary( 1, 'completed' ); $this->assertSame( 0, $summary['order_count'] ); } // ── Test #3 — WPDO_Core::render_health_alert_notice suppresses on empty ── public function test_render_health_alert_notice_suppresses_when_option_empty(): void { // $GLOBALS['_wp_options']['wpdo_health_alert'] is unset → get_option returns ''. $core = new WPDO_Core(); ob_start(); $core->render_health_alert_notice(); $output = ob_get_clean(); $this->assertSame( '', $output, 'No HTML when no alert option' ); } public function test_render_health_alert_notice_suppresses_for_unprivileged(): void { $GLOBALS['_wp_options']['wpdo_health_alert'] = 'DB grew 50%'; $GLOBALS['_wp_current_user_can']['manage_options'] = false; $core = new WPDO_Core(); ob_start(); $core->render_health_alert_notice(); $output = ob_get_clean(); $this->assertSame( '', $output, 'No HTML for users without manage_options' ); } // ── Test #4 — CLI render_stub generates valid PHP ── public function test_render_stub_generates_valid_php(): void { $cli = new WPDO_CLI(); $ref = new ReflectionMethod( 'WPDO_CLI', 'render_stub' ); $ref->setAccessible( true ); $stub = $ref->invoke( $cli, '2meet-bookings', 'bk', 'TMEETIC_Bookings_WPDO' ); $this->assertStringContainsString( 'assertStringContainsString( "if ( ! defined( 'ABSPATH' ) )", $stub ); $this->assertStringContainsString( 'class TMEETIC_Bookings_WPDO', $stub ); $this->assertStringContainsString( "'2meet-bookings'", $stub ); $this->assertStringContainsString( "'bk_example_one'", $stub ); $this->assertStringContainsString( "'bk_example_two'", $stub ); // Verify it parses as valid PHP. $tmp = tempnam( sys_get_temp_dir(), 'wpdo_stub_' ) . '.php'; file_put_contents( $tmp, $stub ); exec( 'php -l ' . escapeshellarg( $tmp ) . ' 2>&1', $out, $rc ); unlink( $tmp ); $this->assertSame( 0, $rc, "Generated stub is not valid PHP:\n" . implode( "\n", $out ) ); } public function test_render_stub_with_different_slug_changes_class_name(): void { $cli = new WPDO_CLI(); $ref = new ReflectionMethod( 'WPDO_CLI', 'render_stub' ); $ref->setAccessible( true ); $stub_a = $ref->invoke( $cli, 'plugin-a', 'pa', 'Plugin_A_WPDO' ); $stub_b = $ref->invoke( $cli, 'plugin-b', 'pb', 'Plugin_B_WPDO' ); $this->assertStringContainsString( 'class Plugin_A_WPDO', $stub_a ); $this->assertStringContainsString( 'class Plugin_B_WPDO', $stub_b ); $this->assertStringNotContainsString( 'Plugin_A_WPDO', $stub_b ); } // ── Bonus: H4 fix verification — Schema_Registry post_type/entity_type normalization ── public function test_schema_registry_normalizes_entity_type_to_post_type(): void { $registry = WPDO_Schema_Registry::instance(); // Snapshot current state to restore later (registry is a singleton). $ref = new ReflectionClass( $registry ); $pp = $ref->getProperty( 'fields' ); $pp->setAccessible( true ); $original = $pp->getValue( $registry ); try { // Simulate a partner like 2meet-liff: entity_type='user', no post_type. $registry->register( 'test-provider', array( 'entity_type' => 'user', 'meta_key' => '_test_user_meta', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', ) ); // Should be retrievable under post_type='user' (normalized). $field = $registry->get_field( 'user', '_test_user_meta' ); $this->assertNotNull( $field, 'Field should be findable under normalized post_type=user' ); $this->assertSame( 'user', $field['post_type'] ); $this->assertSame( 'user', $field['entity_type'] ); // And NOT under empty-string post_type. $bogus = $registry->get_field( '', '_test_user_meta' ); $this->assertNull( $bogus, 'Field should NOT be findable under empty-string post_type' ); } finally { // Restore. $pp->setValue( $registry, $original ); } } }