Commit 0d74cf00d27 for woocommerce

commit 0d74cf00d27e096536e904f6d46d75621f1bed61
Author: Raluca Stan <ralucastn@gmail.com>
Date:   Thu May 21 13:42:49 2026 +0300

    Fix Back in Stock Notifications tests silently halting PHPUnit (#65207)

    * Trap wp_safe_redirect in EmailActionControllerTests to fix silent PHPUnit halt

    PR #64329 added `exit;` after `wp_safe_redirect()` in
    EmailActionController::process_verification_action() and
    process_unsubscribe_action(). The new EmailActionControllerTests calls
    validate_and_maybe_process_request() directly with valid keys, which
    now reaches `wp_safe_redirect + exit;` and silently terminates PHPUnit
    mid-run with status 0 — CI stays green while the rest of the suite
    never executes.

    Install the same `wp_redirect` filter trap already used by the sister
    NotificationManagementServiceTests so the controller's `exit;` is never
    reached during tests. The four happy-path tests (verify success,
    verified email dispatch, repeated-verify short-circuit, unsubscribe)
    wrap their SUT calls in try/catch and assert the intercept message.
    Production code is unchanged — wp_safe_redirect + exit is the correct
    WP pattern.

    Bug introduced in PR #64329.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Monorepo: cleanup.

    ---------

    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
    Co-authored-by: Vladimir Reznichenko <kalessil@gmail.com>

diff --git a/plugins/woocommerce/changelog/65207-claude-pensive-nightingale-ad626c b/plugins/woocommerce/changelog/65207-claude-pensive-nightingale-ad626c
new file mode 100644
index 00000000000..4fa13f2a729
--- /dev/null
+++ b/plugins/woocommerce/changelog/65207-claude-pensive-nightingale-ad626c
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Trap `wp_safe_redirect()` in `EmailActionControllerTests` so PHPUnit no longer silently halts mid-suite on the Back in Stock Notifications redirect tests.
\ No newline at end of file
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index b005bd27fe0..4f007b91831 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -47265,18 +47265,6 @@ parameters:
 			count: 1
 			path: src/Admin/Features/OnboardingTasks/Tasks/Products.php

-		-
-			message: '#^Cannot access property \$base on WP_Screen\|null\.$#'
-			identifier: property.nonObject
-			count: 1
-			path: src/Admin/Features/OnboardingTasks/Tasks/Products.php
-
-		-
-			message: '#^Cannot access property \$post_type on WP_Screen\|null\.$#'
-			identifier: property.nonObject
-			count: 2
-			path: src/Admin/Features/OnboardingTasks/Tasks/Products.php
-
 		-
 			message: '#^Method Automattic\\WooCommerce\\Admin\\Features\\OnboardingTasks\\Tasks\\Products\:\:maybe_set_has_product_transient\(\) has no return type specified\.$#'
 			identifier: missingType.return
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
index 950d3ecc800..42498aed3c7 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
@@ -35,11 +35,36 @@ class EmailActionControllerTests extends \WC_Unit_Test_Case {
 	 */
 	public function setUp(): void {
 		parent::setUp();
+
+		// Intercept redirects so headers aren't emitted, and throw so the trailing `exit;`
+		// in production code never runs during the test.
+		add_filter( 'wp_redirect', array( $this, 'intercept_redirect' ) );
+
 		$this->email_manager = $this->createMock( EmailManager::class );
 		$this->sut           = new EmailActionController();
 		$this->sut->init( $this->email_manager );
 	}

+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		remove_filter( 'wp_redirect', array( $this, 'intercept_redirect' ) );
+		parent::tearDown();
+	}
+
+	/**
+	 * `wp_redirect` filter callback that throws so the SUT's trailing `exit;`
+	 * never executes and the test can still assert state after the method.
+	 *
+	 * @param string $location Redirect target.
+	 * @return never
+	 * @throws \RuntimeException Always.
+	 */
+	public function intercept_redirect( string $location ): void {
+		throw new \RuntimeException( 'wp_redirect intercepted: ' . esc_url_raw( $location ) );
+	}
+
 	/**
 	 * Persist a notification with a single action-key meta entry.
 	 *
@@ -68,7 +93,12 @@ class EmailActionControllerTests extends \WC_Unit_Test_Case {
 			time() . ':' . wp_fast_hash( 'test' )
 		);

-		$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
+		try {
+			$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
+			$this->fail( 'Expected redirect to be intercepted via exception.' );
+		} catch ( \RuntimeException $e ) {
+			$this->assertStringContainsString( 'wp_redirect intercepted', $e->getMessage() );
+		}

 		$this->assertEquals( NotificationStatus::ACTIVE, Factory::get_notification( $id )->get_status() );
 	}
@@ -94,7 +124,12 @@ class EmailActionControllerTests extends \WC_Unit_Test_Case {
 				)
 			);

-		$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
+		try {
+			$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
+			$this->fail( 'Expected redirect to be intercepted via exception.' );
+		} catch ( \RuntimeException $e ) {
+			$this->assertStringContainsString( 'wp_redirect intercepted', $e->getMessage() );
+		}
 	}

 	/**
@@ -131,8 +166,14 @@ class EmailActionControllerTests extends \WC_Unit_Test_Case {
 			->expects( $this->once() )
 			->method( 'send_verified_email' );

-		// First hit transitions PENDING -> ACTIVE and dispatches the verified email.
-		$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
+		// First hit transitions PENDING -> ACTIVE and dispatches the verified email. The
+		// trailing redirect throws via the intercept filter so the SUT's `exit;` is skipped.
+		try {
+			$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
+			$this->fail( 'Expected redirect to be intercepted via exception.' );
+		} catch ( \RuntimeException $e ) {
+			$this->assertStringContainsString( 'wp_redirect intercepted', $e->getMessage() );
+		}

 		// Second hit (double-click, email prefetch, bot) must short-circuit without re-dispatch.
 		$this->sut->validate_and_maybe_process_request( $id, 'test', 'verify' );
@@ -148,7 +189,12 @@ class EmailActionControllerTests extends \WC_Unit_Test_Case {
 			wp_fast_hash( 'test' )
 		);

-		$this->sut->validate_and_maybe_process_request( $id, 'test', 'unsubscribe' );
+		try {
+			$this->sut->validate_and_maybe_process_request( $id, 'test', 'unsubscribe' );
+			$this->fail( 'Expected redirect to be intercepted via exception.' );
+		} catch ( \RuntimeException $e ) {
+			$this->assertStringContainsString( 'wp_redirect intercepted', $e->getMessage() );
+		}

 		$updated = Factory::get_notification( $id );
 		$this->assertEquals( NotificationStatus::CANCELLED, $updated->get_status() );