From e4daa5c8eb2bf2d5ff80254d05c20f994c92ddc6 Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 27 Mar 2026 09:00:41 -0600 Subject: [PATCH 1/3] chore(todo): add GH issue refs for t453-466, mark t459-461 complete --- TODO.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/TODO.md b/TODO.md index 15ca3d1e8..914a3d53a 100644 --- a/TODO.md +++ b/TODO.md @@ -13,23 +13,23 @@ Overall coverage: **35%** (20,720 / 59,212 statements). 90 files at 0% coverage. ### Priority 1 — Business-Critical Code (low coverage, high risk) -- [ ] t453 test(checkout): write unit tests for Checkout class (inc/checkout/class-checkout.php — 7.9% coverage, 960 uncovered stmts) #testing #auto-dispatch ~6h -- [ ] t454 test(cart): improve Cart test coverage (inc/checkout/class-cart.php — 61.1% coverage, 382 uncovered stmts) #testing #auto-dispatch ~4h +- [ ] t453 test(checkout): write unit tests for Checkout class (inc/checkout/class-checkout.php — 7.9% coverage, 960 uncovered stmts) #testing #auto-dispatch ~6h ref:GH#555 +- [ ] t454 test(cart): improve Cart test coverage (inc/checkout/class-cart.php — 61.1% coverage, 382 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#556 - [ ] t455 test(stripe): write unit tests for Base_Stripe_Gateway (inc/gateways/class-base-stripe-gateway.php — 28.6% coverage, 1093 uncovered stmts) #testing ~8h - [ ] t456 test(stripe): improve Stripe_Gateway test coverage (inc/gateways/class-stripe-gateway.php — 55% coverage, 200 uncovered stmts) #testing ~4h - [ ] t457 test(paypal): write unit tests for PayPal_Gateway (inc/gateways/class-paypal-gateway.php — 1.1% coverage, 783 uncovered stmts) #testing ~6h - [ ] t458 test(paypal): improve PayPal_REST_Gateway test coverage (inc/gateways/class-paypal-rest-gateway.php — 29.8% coverage, 683 uncovered stmts) #testing ~5h -- [ ] t459 test(paypal): write tests for PayPal_OAuth_Handler (inc/gateways/class-paypal-oauth-handler.php — 15% coverage, 238 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#549 -- [ ] t460 test(gateway): improve Gateway_Manager test coverage (inc/managers/class-gateway-manager.php — 34.7% coverage, 177 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#550 -- [ ] t461 test(checkout-pages): improve Checkout_Pages test coverage (inc/checkout/class-checkout-pages.php — 33.7% coverage, 203 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#551 +- [x] t459 test(paypal): write tests for PayPal_OAuth_Handler (inc/gateways/class-paypal-oauth-handler.php — 15% coverage, 238 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#549 pr:#554 completed:2026-03-27 +- [x] t460 test(gateway): improve Gateway_Manager test coverage (inc/managers/class-gateway-manager.php — 34.7% coverage, 177 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#550 pr:#552 completed:2026-03-27 +- [x] t461 test(checkout-pages): improve Checkout_Pages test coverage (inc/checkout/class-checkout-pages.php — 33.7% coverage, 203 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#551 pr:#553 completed:2026-03-27 ### Priority 2 — Core Domain Logic (moderate coverage gaps) -- [ ] t462 test(site-manager): improve Site_Manager test coverage (inc/managers/class-site-manager.php — 23.5% coverage, 433 uncovered stmts) #testing #auto-dispatch ~4h -- [ ] t463 test(domain-manager): improve DNS_Record_Manager test coverage (inc/managers/class-dns-record-manager.php — 14.4% coverage, 393 uncovered stmts) #testing #auto-dispatch ~4h -- [ ] t464 test(event-manager): improve Event_Manager test coverage (inc/managers/class-event-manager.php — 33% coverage, 240 uncovered stmts) #testing #auto-dispatch ~3h -- [ ] t465 test(form-manager): improve Form_Manager test coverage (inc/managers/class-form-manager.php — 9.7% coverage, 251 uncovered stmts) #testing #auto-dispatch ~3h -- [ ] t466 test(notes-manager): improve Notes_Manager test coverage (inc/managers/class-notes-manager.php — 9.9% coverage, 283 uncovered stmts) #testing #auto-dispatch ~3h +- [ ] t462 test(site-manager): improve Site_Manager test coverage (inc/managers/class-site-manager.php — 23.5% coverage, 433 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#557 +- [ ] t463 test(domain-manager): improve DNS_Record_Manager test coverage (inc/managers/class-dns-record-manager.php — 14.4% coverage, 393 uncovered stmts) #testing #auto-dispatch ~4h ref:GH#558 +- [ ] t464 test(event-manager): improve Event_Manager test coverage (inc/managers/class-event-manager.php — 33% coverage, 240 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#559 +- [ ] t465 test(form-manager): improve Form_Manager test coverage (inc/managers/class-form-manager.php — 9.7% coverage, 251 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#560 +- [ ] t466 test(notes-manager): improve Notes_Manager test coverage (inc/managers/class-notes-manager.php — 9.9% coverage, 283 uncovered stmts) #testing #auto-dispatch ~3h ref:GH#561 - [ ] t467 test(sso): improve SSO test coverage (inc/sso/class-sso.php — 36.7% coverage, 210 uncovered stmts) #testing #auto-dispatch ~4h - [ ] t468 test(domain-mapping): write tests for Domain_Mapping (inc/class-domain-mapping.php — 13.8% coverage, 168 uncovered stmts) #testing #auto-dispatch ~3h - [ ] t469 test(membership-functions): improve membership function tests (inc/functions/membership.php — 28.3% coverage, 152 uncovered stmts) #testing #auto-dispatch ~2h From f6acd04d3137f0acfb50fa6027972a0ac1c01f0a Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 27 Mar 2026 09:02:20 -0600 Subject: [PATCH 2/3] chore(todo): add ref:GH#562 for t495 test exit bug --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 914a3d53a..ec0ac1d58 100644 --- a/TODO.md +++ b/TODO.md @@ -73,4 +73,4 @@ Overall coverage: **35%** (20,720 / 59,212 statements). 90 files at 0% coverage. ### Fix: Test suite exits early at 56% -- [ ] t495 fix(tests): Form_Manager_Test::test_handle_model_delete_form_requires_confirmation calls exit() killing test runner at test 2533/4411 #bug #auto-dispatch ~1h \ No newline at end of file +- [ ] t495 fix(tests): Form_Manager_Test::test_handle_model_delete_form_requires_confirmation calls exit() killing test runner at test 2533/4411 #bug #auto-dispatch ~1h ref:GH#562 \ No newline at end of file From ab6ef748de275b331543fe974c47ca3734189426 Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 27 Mar 2026 13:16:58 -0600 Subject: [PATCH 3/3] test(form-manager): add security_checks and error-path coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add test_security_checks_dies_for_non_ajax_request: covers the wp_die(0) path in security_checks() when HTTP_X_REQUESTED_WITH is absent (non-AJAX request). Uses wp_die_handler filter to intercept WPDieException instead of process termination. - Add test_security_checks_passes_for_valid_ajax_request: covers the pass-through path when AJAX context + valid form + capable user. - Add test_default_bulk_action_handler_sends_error_when_process_fails: covers the wp_send_json_error() path in default_bulk_action_handler() when process_bulk_action() returns WP_Error (nonexistent model). Coverage: 56.25% → 62.50% methods, 79.06% → 81.23% lines (≥60% target met) Tests: 40 → 43, Assertions: 89 → 94 Closes #559 --- .../WP_Ultimo/Managers/Form_Manager_Test.php | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/tests/WP_Ultimo/Managers/Form_Manager_Test.php b/tests/WP_Ultimo/Managers/Form_Manager_Test.php index 48f70b7a8..05e438cad 100644 --- a/tests/WP_Ultimo/Managers/Form_Manager_Test.php +++ b/tests/WP_Ultimo/Managers/Form_Manager_Test.php @@ -1073,4 +1073,123 @@ public function test_init_registers_hooks(): void { $this->assertNotFalse(has_action('wu_ajax_wu_form_handler', [$manager, 'handle_form'])); $this->assertNotFalse(has_action('wu_register_forms', [$manager, 'register_action_forms'])); } + + // ========================================================================= + // security_checks + // ========================================================================= + + /** + * Test security_checks calls wp_die(0) when request is not an AJAX request. + * + * security_checks() checks $_SERVER['HTTP_X_REQUESTED_WITH'] for the value + * 'xmlhttprequest'. When absent (or wrong), it calls wp_die(0). + * + * wp_die() in non-AJAX context uses wp_die_handler (not wp_die_ajax_handler). + * We install a wp_die_handler filter that throws WPDieException so PHPUnit + * can catch it instead of the process terminating. + */ + public function test_security_checks_dies_for_non_ajax_request(): void { + + $manager = $this->get_manager_instance(); + + // Ensure HTTP_X_REQUESTED_WITH is absent (non-AJAX request). + unset($_SERVER['HTTP_X_REQUESTED_WITH']); + + $die_handler = function () { + return function ( $message ) { + throw new \WPDieException( (string) $message ); + }; + }; + + add_filter('wp_die_handler', $die_handler, 1); + + $exception_caught = false; + + try { + $manager->security_checks(); + } catch (\WPDieException $e) { + $exception_caught = true; + } + + remove_filter('wp_die_handler', $die_handler, 1); + + $this->assertTrue($exception_caught, 'security_checks() should call wp_die() for non-AJAX requests'); + } + + /** + * Test security_checks passes through when request is AJAX and form exists with capability. + * + * When HTTP_X_REQUESTED_WITH is 'XMLHttpRequest', the form is registered, + * and the current user has the required capability, security_checks() returns + * without calling wp_die() or display_form_unavailable(). + */ + public function test_security_checks_passes_for_valid_ajax_request(): void { + + $manager = $this->get_manager_instance(); + + // Register a form with 'read' capability (all users have this). + $manager->register_form( + 'security_test_form_xyz', + [ + 'capability' => 'read', + 'render' => '__return_empty_string', + 'handler' => '__return_false', + ] + ); + + // Simulate AJAX request. + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $_REQUEST['form'] = 'security_test_form_xyz'; + + // Grant the current user 'read' capability (WP_UnitTestCase sets up a user). + wp_set_current_user($this->factory()->user->create(['role' => 'subscriber'])); + + $exception_caught = false; + + try { + $manager->security_checks(); + } catch (\Exception $e) { + $exception_caught = true; + } + + unset($_SERVER['HTTP_X_REQUESTED_WITH'], $_REQUEST['form']); + + $this->assertFalse($exception_caught, 'security_checks() should not die for a valid AJAX request with capable user'); + } + + // ========================================================================= + // default_bulk_action_handler — error path + // ========================================================================= + + /** + * Test default_bulk_action_handler sends json_error when process_bulk_action fails. + * + * process_bulk_action() returns WP_Error when the model function doesn't exist + * (e.g. model='nonexistent_model_xyz'). default_bulk_action_handler() should + * then call wp_send_json_error() with that WP_Error. + */ + public function test_default_bulk_action_handler_sends_error_when_process_fails(): void { + + $manager = $this->get_manager_instance(); + + // Use a model that has no corresponding wu_get_* function. + $_REQUEST['bulk_action'] = 'delete'; + $_REQUEST['model'] = 'nonexistent_model_xyz'; + $_REQUEST['ids'] = '1,2,3'; + + $result = $this->run_in_ajax_context( + function () use ( $manager ) { + $manager->default_bulk_action_handler('delete', 'nonexistent_model_xyz', ['1', '2', '3']); + } + ); + + unset($_REQUEST['bulk_action'], $_REQUEST['model'], $_REQUEST['ids']); + + $this->assertTrue($result['exception'], 'Should terminate via wp_die()'); + + $response = json_decode($result['output'], true); + + $this->assertIsArray($response); + $this->assertFalse($response['success'], 'Should return error when process_bulk_action fails'); + } }