Commit c082aba805b for woocommerce

commit c082aba805bf26807507e81250f90241bfa7c50e
Author: Faisal Ahammad <faisalahammad24@gmail.com>
Date:   Wed Aug 5 17:22:37 2026 +0600

    Tests: Add end-to-end routing test for order reviews AJAX submission (#64408)

    * test: add coverage for OrderReviews\SubmissionHandler AJAX endpoint

    Adds unit tests driving the existing woocommerce_submit_order_reviews
    AJAX action. Trunk already registers the handler via
    OrderReviews\SubmissionHandler; the original PR added a duplicate
    WC_AJAX::submit_order_reviews() that shadowed the registered one with
    a different payload contract.

    Refs #64310.

    * fix: address 7 review issues in order reviews test coverage

    - Fix add_product() returns int not object (line 59)
    - Enable customer_review_request feature flag in set_up()
    - Clean up feature flag in tear_down()
    - Remove data.code assertions (handler sets no code key)
    - Change changelog type from add to dev for test-only work

    Addresses PR #64408 review feedback.

    * fix: resolve 12 PHPUnit test failures and lint warnings

    Wire SubmissionHandler AJAX hooks in setUp() (feature flag disabled
    at WP bootstrap, so maybe_init_order_reviews() skips init()).

    Replace bogus data.code assertions with data.message checks
    (handler never sets 'code' in JSON response body).

    Fix 4 PHPCS alignment warnings (auto-fixed by phpcbf).

    Errors fixed:
    - All 12 WC_AJAX_Submit_Order_Reviews_Test failures: handler not registered
    - data.code check: tests nothing (handler uses data.message, not data.code)
    - PHPCS alignment: 4 spacing warnings in test file

    Refs #64408

    * test: replace legacy submit order reviews AJAX test with routing test

    SubmissionHandlerRoutingTest replaces class-wc-ajax-submit-order-reviews-test.php,
    proving the registered nopriv admin-ajax action routes guest requests through
    SubmissionHandler::handle() and inserts a review end to end.

    * fix(tests): address Ferdev routing test feedback

    - setUp() re-runs SubmissionHandler::init() so the AJAX actions
      re-register after tearDown() removes them.
    - dispatch() captures the JSON envelope via output buffering, matching
      the pattern SubmissionHandlerTest uses for wp_send_json output.

    Tests: 2/2 pass. Lint clean. CodeRabbit clean.

    * fix(tests): import WPAjaxDieContinueException per Ferdev review

    The unqualified class reference in the catch block resolves to the test
    namespace, not the global WP test-suite exception. Adding the import
    matches the sibling SubmissionHandlerTest convention.

    Addresses PR #64408 review feedback.

    * test(order-reviews): clean output buffer on non-WP exception escape

    Move ob_get_clean into a finally block so the test process does not leak a
    stray output buffer when do_action raises an exception other than
    WPAjaxDieContinueException.

    Addresses PR #64408 review feedback from Ferdev (thread #3647175364).

    * test(order-reviews): cover resolver path and wp_ajax_ hook

    - setUp uses WC()->maybe_init_order_reviews() to exercise production wiring,
      then handler->init() for repeatability across tests
    - test_action_hooks_are_registered covers both wp_ajax_ and wp_ajax_nopriv_
      hooks per issue #64310 scope

    Refs #64408

    * test(order-reviews): reset container cache per test, drop redundant init()

    Adds wc_get_container()->reset_all_resolved() at the top of tearDown so
    the resolved-instance cache does not stream across tests. The cached
    SubmissionHandler instance skips auto-init() on the next resolve, which
    would silently regress test_action_hooks_are_registered.

    Removes the explicit $handler->init() call from setUp so the resolver
    path (WC()->maybe_init_order_reviews) is the sole source of the
    wp_ajax_* hook wiring. If the resolver ever stops resolving
    the handler, this test will now fail instead of staying green via the
    manual fallback.

    Drops the two remove_action('wp_ajax_*') calls in tearDown that were
    only compensating for the cached-instance problem.

    Addresses PR feedback from Ferdev.

diff --git a/plugins/woocommerce/changelog/64310-add-ajax-submit-order-reviews b/plugins/woocommerce/changelog/64310-add-ajax-submit-order-reviews
new file mode 100644
index 00000000000..c900b33948e
--- /dev/null
+++ b/plugins/woocommerce/changelog/64310-add-ajax-submit-order-reviews
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Add test coverage for the existing `woocommerce_submit_order_reviews` AJAX endpoint handled by `OrderReviews\SubmissionHandler`.
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerRoutingTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerRoutingTest.php
new file mode 100644
index 00000000000..158c348fd08
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderReviews/SubmissionHandlerRoutingTest.php
@@ -0,0 +1,144 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\OrderReviews;
+
+use Automattic\WooCommerce\Enums\OrderStatus;
+use Automattic\WooCommerce\Internal\OrderReviews\ItemEligibility;
+use Automattic\WooCommerce\Internal\OrderReviews\SubmissionHandler;
+use WC_Helper_Product;
+use WC_Unit_Test_Case;
+use WPAjaxDieContinueException;
+
+/**
+ * End-to-end wiring test for the submit_order_reviews AJAX action.
+ *
+ * Complements SubmissionHandlerTest (which calls handle() directly) by proving
+ * the registered admin-ajax action actually routes to the handler for guests.
+ */
+class SubmissionHandlerRoutingTest extends WC_Unit_Test_Case {
+
+	/**
+	 * Set up test fixtures.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		update_option( 'woocommerce_feature_customer_review_request_enabled', 'yes' );
+		// Drive the production registration path: the flag was off at bootstrap,
+		// so re-run the init-hooked resolver now that it's on. tearDown() drops the
+		// container's resolved-instance cache, so each test re-resolves a fresh
+		// SubmissionHandler whose auto-init() registers both wp_ajax_* hooks.
+		WC()->maybe_init_order_reviews();
+		update_option( 'comment_moderation', '0' );
+		wp_set_current_user( 0 );
+	}
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		// Drop the container's resolved-instance cache so the next setUp()
+		// re-resolves a fresh SubmissionHandler. Without this, the cached instance
+		// would skip its auto-init() on the next resolve and the wp_ajax_* hooks
+		// would never register, regressing test_action_hooks_are_registered.
+		wc_get_container()->reset_all_resolved();
+		delete_option( 'woocommerce_feature_customer_review_request_enabled' );
+		delete_option( 'comment_moderation' );
+		$_POST = array();
+		ItemEligibility::reset_cache();
+		remove_all_filters( 'wp_die_ajax_handler' );
+		remove_all_filters( 'wp_doing_ajax' );
+		remove_all_filters( 'wp_send_json_handler' );
+		parent::tearDown();
+	}
+
+	/**
+	 * @testdox Both the authenticated and nopriv AJAX actions are wired through the production init path.
+	 */
+	public function test_action_hooks_are_registered(): void {
+		$handler = wc_get_container()->get( SubmissionHandler::class );
+
+		$this->assertNotFalse(
+			has_action( 'wp_ajax_' . SubmissionHandler::ACTION, array( $handler, 'handle' ) ),
+			'Authenticated submit_order_reviews action should be wired when the feature is enabled.'
+		);
+		$this->assertNotFalse(
+			has_action( 'wp_ajax_nopriv_' . SubmissionHandler::ACTION, array( $handler, 'handle' ) ),
+			'Guest submit_order_reviews action should be wired when the feature is enabled.'
+		);
+	}
+
+	/**
+	 * @testdox A guest request routes through admin-ajax to the handler and inserts a review.
+	 */
+	public function test_guest_submission_routes_end_to_end(): void {
+		$order = wc_create_order( array( 'status' => OrderStatus::COMPLETED ) );
+		$order->set_billing_first_name( 'John' );
+		$order->set_billing_email( 'john@example.com' );
+		$product = WC_Helper_Product::create_simple_product();
+		$order->add_product( $product, 1 );
+		$order->save();
+
+		$item_id = 0;
+		foreach ( $order->get_items() as $item ) {
+			$item_id = $item->get_id();
+		}
+
+		$_POST['_wcnonce'] = wp_create_nonce( SubmissionHandler::ACTION );
+		$_POST['order_id'] = $order->get_id();
+		$_POST['key']      = $order->get_order_key();
+		$_POST['reviews']  = array(
+			array(
+				'order_item_id' => $item_id,
+				'product_id'    => $product->get_id(),
+				'rating'        => 5,
+				'text'          => 'Great product!',
+			),
+		);
+
+		$response = $this->dispatch();
+
+		$this->assertTrue( $response['success'] );
+		$this->assertSame( 'ok', $response['data']['results'][0]['status'] );
+
+		$comment = get_comment( $response['data']['results'][0]['comment_id'] );
+		$this->assertSame( 'review', $comment->comment_type );
+		$this->assertSame( $product->get_id(), (int) $comment->comment_post_ID );
+	}
+
+	/**
+	 * Fire the registered nopriv action and capture the JSON envelope it emits.
+	 *
+	 * @return array{success:bool,data:mixed}
+	 */
+	private function dispatch(): array {
+		// Same capture pattern SubmissionHandlerTest uses: wp_send_json writes
+		// to the output buffer via wp_die, so an ob_start/ob_get_clean pair
+		// collects the rendered JSON. wp_send_json itself fires no filter.
+		add_filter( 'wp_die_ajax_handler', static fn() => static fn() => null );
+		add_filter( 'wp_doing_ajax', '__return_true' );
+
+		ob_start();
+		try {
+			do_action( 'wp_ajax_nopriv_' . SubmissionHandler::ACTION ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+		} catch ( WPAjaxDieContinueException $e ) {
+			// Expected: wp_send_json_* always calls wp_die().
+			unset( $e );
+		} finally {
+			// Clean the buffer even if a non-WPAjax exception escaped, so the test
+			// process does not leak a stray buffer to subsequent tests.
+			$body = (string) ob_get_clean();
+		}
+
+		$decoded  = json_decode( $body, true );
+		$response = array(
+			'success' => false,
+			'data'    => null,
+		);
+		if ( is_array( $decoded ) ) {
+			$response['success'] = ! empty( $decoded['success'] );
+			$response['data']    = $decoded['data'] ?? null;
+		}
+		return $response;
+	}
+}