Commit 98240e480e3 for woocommerce

commit 98240e480e3dfb96b9963740f4b74956ebeffdcc
Author: Hannah Tinkler <hannah.tinkler@gmail.com>
Date:   Thu Sep 17 23:29:04 2026 +0300

    Skip push notification work when no push tokens are registered (#68261)

    * Skip push notification work when no push tokens are registered

    A Jetpack connected store with no mobile app signed in still scheduled an
    Action Scheduler safety net job and fired the loopback REST request for
    every order, review and stock change. The processor found no recipients
    and cancelled the job straight away, so merchants saw a stream of
    cancelled actions for sends that never had a recipient.

    The lookup of users that own a push token is now shared with
    get_tokens_for_roles() and memoized per request, so the added check costs
    no extra query on a store that does have tokens.

    * Clear the token caches after a write

    has_tokens() now decides whether a notification is created at all, so a
    memoized owner list that survives a write can drop notifications for the
    rest of the request. No current request both reads and writes, but the
    method is cheap to call and invites new callers who cannot see the trap
    from the call site.

    update() is covered as well as create() and delete(), since it writes
    post_author and can move a token to a different user.

    * Drop the version annotations from the new private methods

    Follows review preference. The two neighbouring private methods in this
    file still carry theirs, so the file is inconsistent until someone
    settles the convention one way or the other.

    * Reduce has_tokens() to a LIMIT 1 query that memoizes only a positive result

    The processor runs in the loopback request, so a cache shared with
    get_tokens_for_roles() never saved a query. A stale true is harmless and a
    stale false would drop a notification, so only true is remembered and no
    invalidation on write is needed.

    * Unwrap the docblocks added for has_tokens()

    * Memoize a negative has_tokens() result until a token is created

    A request that triggers several notifications on a store with no tokens ran the existence query once per notification. Remember the negative result too, and reset it in create() so a token registered later in the same request is still found.

diff --git a/plugins/woocommerce/changelog/dev-skip-push-notification-work-without-tokens b/plugins/woocommerce/changelog/dev-skip-push-notification-work-without-tokens
new file mode 100644
index 00000000000..b706c2e7619
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-skip-push-notification-work-without-tokens
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Skip push notification scheduling and the loopback request when no push tokens are registered
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php b/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php
index 141e99269ab..81a6a7f73df 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php
@@ -32,6 +32,13 @@ class PushTokensDataStore {
 	 */
 	private array $tokens_by_roles_cache = array();

+	/**
+	 * Memoized has_tokens() result. Null until the first lookup, and reset by create() so a stale false cannot drop a notification.
+	 *
+	 * @var bool|null
+	 */
+	private ?bool $has_tokens = null;
+
 	const SUPPORTED_META = array(
 		'origin',
 		'device_uuid',
@@ -81,6 +88,8 @@ class PushTokensDataStore {

 		$push_token->set_id( $id );

+		$this->has_tokens = null;
+
 		return $push_token;
 	}

@@ -355,6 +364,30 @@ class PushTokensDataStore {
 		return null;
 	}

+	/**
+	 * Determines whether any push token exists, ignoring roles and preferences so callers can bail out before the cost of get_tokens_for_roles().
+	 *
+	 * @since 11.2.0
+	 * @return bool True if at least one push token exists.
+	 */
+	public function has_tokens(): bool {
+		if ( null !== $this->has_tokens ) {
+			return $this->has_tokens;
+		}
+
+		global $wpdb;
+
+		// Exactly this SQL to leverage the wp_posts type_status_author index.
+		$this->has_tokens = (bool) $wpdb->get_var(
+			$wpdb->prepare(
+				"SELECT 1 FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'private' LIMIT 1",
+				PushToken::POST_TYPE
+			)
+		);
+
+		return $this->has_tokens;
+	}
+
 	/**
 	 * Returns push tokens belonging to users with the given roles.
 	 *
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Services/PendingNotificationStore.php b/plugins/woocommerce/src/Internal/PushNotifications/Services/PendingNotificationStore.php
index 06879b909ba..a988cf7f7d7 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Services/PendingNotificationStore.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Services/PendingNotificationStore.php
@@ -6,6 +6,7 @@ namespace Automattic\WooCommerce\Internal\PushNotifications\Services;

 defined( 'ABSPATH' ) || exit;

+use Automattic\WooCommerce\Internal\PushNotifications\DataStores\PushTokensDataStore;
 use Automattic\WooCommerce\Internal\PushNotifications\Dispatchers\InternalNotificationDispatcher;
 use Automattic\WooCommerce\Internal\PushNotifications\Notifications\Notification;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\NotificationProcessor;
@@ -36,6 +37,13 @@ class PendingNotificationStore {
 	 */
 	private InternalNotificationDispatcher $dispatcher;

+	/**
+	 * The push tokens data store.
+	 *
+	 * @var PushTokensDataStore
+	 */
+	private PushTokensDataStore $data_store;
+
 	/**
 	 * Pending notifications keyed by identifier.
 	 *
@@ -56,11 +64,13 @@ class PendingNotificationStore {
 	 * @internal
 	 *
 	 * @param InternalNotificationDispatcher $dispatcher The dispatcher to use on shutdown.
+	 * @param PushTokensDataStore            $data_store The push tokens data store.
 	 *
 	 * @since 10.7.0
 	 */
-	final public function init( InternalNotificationDispatcher $dispatcher ): void {
+	final public function init( InternalNotificationDispatcher $dispatcher, PushTokensDataStore $data_store ): void {
 		$this->dispatcher = $dispatcher;
+		$this->data_store = $data_store;
 	}

 	/**
@@ -83,6 +93,8 @@ class PendingNotificationStore {
 	 * request are silently ignored. The shutdown hook is registered on the
 	 * first call.
 	 *
+	 * Notifications are dropped when no push token is registered, so neither the safety net job nor the loopback request is created for a send that has no recipient.
+	 *
 	 * @param Notification $notification The notification to add.
 	 * @return void
 	 *
@@ -93,6 +105,10 @@ class PendingNotificationStore {
 			return;
 		}

+		if ( ! $this->data_store->has_tokens() ) {
+			return;
+		}
+
 		$key = $notification->get_identifier();

 		if ( isset( $this->pending[ $key ] ) ) {
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php
index 4b93f8f5c0e..f759114b6b0 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php
@@ -703,6 +703,77 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
 		$this->assertEquals( array( 'app_version' => '2.0' ), $updated_token->get_metadata() );
 	}

+	/**
+	 * @testdox Should report no tokens when none are registered.
+	 */
+	public function test_has_tokens_returns_false_when_no_tokens_exist(): void {
+		$data_store = new PushTokensDataStore();
+
+		$this->assertFalse( $data_store->has_tokens() );
+	}
+
+	/**
+	 * @testdox Should not query again once a lookup found no tokens.
+	 */
+	public function test_has_tokens_memoizes_a_negative_result(): void {
+		global $wpdb;
+
+		$data_store = new PushTokensDataStore();
+		$data_store->has_tokens();
+
+		$queries_before = $wpdb->num_queries;
+		$data_store->has_tokens();
+
+		$this->assertSame( $queries_before, $wpdb->num_queries );
+	}
+
+	/**
+	 * @testdox Should report tokens once one is registered, regardless of the owner's role.
+	 */
+	public function test_has_tokens_returns_true_when_a_token_exists(): void {
+		$subscriber_id = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+		$data_store    = new PushTokensDataStore();
+
+		$data_store->create(
+			array(
+				'user_id'       => $subscriber_id,
+				'token'         => 'subscriber_token_' . wp_rand(),
+				'platform'      => PushToken::PLATFORM_APPLE,
+				'device_uuid'   => 'subscriber-device-' . wp_rand(),
+				'origin'        => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+				'device_locale' => 'en_US',
+				'metadata'      => array( 'app_version' => '1.0' ),
+			)
+		);
+
+		$this->assertTrue( ( new PushTokensDataStore() )->has_tokens() );
+	}
+
+	/**
+	 * @testdox Should report tokens after one is created, when an earlier lookup found none.
+	 */
+	public function test_has_tokens_reflects_a_token_created_after_an_earlier_lookup(): void {
+		$admin_id   = $this->factory->user->create( array( 'role' => 'administrator' ) );
+		$data_store = new PushTokensDataStore();
+
+		$this->assertFalse( $data_store->has_tokens() );
+
+		$data_store->create(
+			array(
+				'user_id'       => $admin_id,
+				'token'         => 'admin_token_' . wp_rand(),
+				'platform'      => PushToken::PLATFORM_APPLE,
+				'device_uuid'   => 'admin-device-' . wp_rand(),
+				'origin'        => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+				'device_locale' => 'en_US',
+				'metadata'      => array( 'app_version' => '1.0' ),
+			)
+		);
+
+		$this->assertTrue( $data_store->has_tokens() );
+		$this->assertCount( 1, $data_store->get_tokens_for_roles( array( 'administrator' ) ) );
+	}
+
 	/**
 	 * @testdox Should return tokens for users with matching roles.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Helpers/PushNotificationsTestTrait.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Helpers/PushNotificationsTestTrait.php
index 99b887304c2..af548dfabb5 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Helpers/PushNotificationsTestTrait.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Helpers/PushNotificationsTestTrait.php
@@ -5,6 +5,7 @@ declare( strict_types = 1 );
 namespace Automattic\WooCommerce\Tests\Internal\PushNotifications\Helpers;

 use Automattic\Jetpack\Connection\Manager as JetpackConnectionManager;
+use Automattic\WooCommerce\Internal\PushNotifications\DataStores\PushTokensDataStore;
 use Automattic\WooCommerce\Internal\PushNotifications\PushNotifications;
 use Automattic\WooCommerce\Proxies\LegacyProxy;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -15,7 +16,8 @@ use ReflectionClass;
  *
  * Mocks the Jetpack connection state and resets the memoized enablement flag on
  * the container's `PushNotifications` instance — the two things every
- * push-notifications-related controller test needs in setUp.
+ * push-notifications-related controller test needs in setUp. Also builds the
+ * push tokens data store mock that PendingNotificationStore tests need.
  *
  * @package WooCommerce\Tests\PushNotifications
  */
@@ -63,4 +65,17 @@ trait PushNotificationsTestTrait {
 		$property->setAccessible( true );
 		$property->setValue( $push_notifications, null );
 	}
+
+	/**
+	 * Creates a push tokens data store whose has_tokens() returns $has_tokens.
+	 *
+	 * @param bool $has_tokens What has_tokens() should report.
+	 * @return PushTokensDataStore|MockObject
+	 */
+	protected function create_data_store_with_tokens( bool $has_tokens ) {
+		$data_store = $this->createMock( PushTokensDataStore::class );
+		$data_store->method( 'has_tokens' )->willReturn( $has_tokens );
+
+		return $data_store;
+	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/NotificationProcessorTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/NotificationProcessorTest.php
index 94b04ce501e..afaded97a07 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/NotificationProcessorTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/NotificationProcessorTest.php
@@ -18,6 +18,7 @@ use Automattic\WooCommerce\Internal\PushNotifications\Services\NotificationProce
 use Automattic\WooCommerce\Internal\PushNotifications\Services\NotificationRetryHandler;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\PendingNotificationStore;
 use Automattic\WooCommerce\RestApi\UnitTests\LoggerSpyTrait;
+use Automattic\WooCommerce\Tests\Internal\PushNotifications\Helpers\PushNotificationsTestTrait;
 use WC_Helper_Product;
 use WC_Unit_Test_Case;

@@ -27,6 +28,7 @@ use WC_Unit_Test_Case;
 class NotificationProcessorTest extends WC_Unit_Test_Case {

 	use LoggerSpyTrait;
+	use PushNotificationsTestTrait;

 	/**
 	 * The System Under Test.
@@ -715,7 +717,7 @@ class NotificationProcessorTest extends WC_Unit_Test_Case {
 	 */
 	private function schedule_safety_net( Notification $notification ): void {
 		$store = new PendingNotificationStore();
-		$store->init( $this->createMock( InternalNotificationDispatcher::class ) );
+		$store->init( $this->createMock( InternalNotificationDispatcher::class ), $this->create_data_store_with_tokens( true ) );

 		$method = new \ReflectionMethod( PendingNotificationStore::class, 'schedule_safety_net' );
 		$method->setAccessible( true );
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/PendingNotificationStoreTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/PendingNotificationStoreTest.php
index 18f3028e7c9..876e754637f 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/PendingNotificationStoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Services/PendingNotificationStoreTest.php
@@ -8,7 +8,9 @@ use Automattic\WooCommerce\Internal\PushNotifications\Dispatchers\InternalNotifi
 use Automattic\WooCommerce\Internal\PushNotifications\Notifications\NewOrderNotification;
 use Automattic\WooCommerce\Internal\PushNotifications\Notifications\NewReviewNotification;
 use Automattic\WooCommerce\Internal\PushNotifications\Notifications\StockNotification;
+use Automattic\WooCommerce\Internal\PushNotifications\Services\NotificationProcessor;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\PendingNotificationStore;
+use Automattic\WooCommerce\Tests\Internal\PushNotifications\Helpers\PushNotificationsTestTrait;
 use WC_Unit_Test_Case;

 /**
@@ -16,6 +18,8 @@ use WC_Unit_Test_Case;
  */
 class PendingNotificationStoreTest extends WC_Unit_Test_Case {

+	use PushNotificationsTestTrait;
+
 	/**
 	 * An instance of PendingNotificationStore.
 	 *
@@ -32,7 +36,7 @@ class PendingNotificationStoreTest extends WC_Unit_Test_Case {
 		$dispatcher  = $this->createMock( InternalNotificationDispatcher::class );
 		$this->store = new PendingNotificationStore();

-		$this->store->init( $dispatcher );
+		$this->store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
 		$this->store->register();
 	}

@@ -89,13 +93,63 @@ class PendingNotificationStoreTest extends WC_Unit_Test_Case {
 	public function test_add_does_nothing_when_not_registered(): void {
 		$dispatcher = $this->createMock( InternalNotificationDispatcher::class );
 		$store      = new PendingNotificationStore();
-		$store->init( $dispatcher );
+		$store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
+
+		$store->add( $this->create_order_mock( 42 ) );
+
+		$this->assertSame( 0, $store->count() );
+	}
+
+	/**
+	 * @testdox Should not add notifications when the store has no registered push tokens.
+	 */
+	public function test_add_does_nothing_when_no_tokens_registered(): void {
+		$dispatcher = $this->createMock( InternalNotificationDispatcher::class );
+		$store      = new PendingNotificationStore();
+		$store->init( $dispatcher, $this->create_data_store_with_tokens( false ) );
+		$store->register();

 		$store->add( $this->create_order_mock( 42 ) );

 		$this->assertSame( 0, $store->count() );
 	}

+	/**
+	 * @testdox Should not schedule a safety net when the store has no registered push tokens.
+	 */
+	public function test_add_does_not_schedule_safety_net_when_no_tokens_registered(): void {
+		$notification = $this->create_order_mock( 42 );
+
+		$dispatcher = $this->createMock( InternalNotificationDispatcher::class );
+		$store      = new PendingNotificationStore();
+		$store->init( $dispatcher, $this->create_data_store_with_tokens( false ) );
+		$store->register();
+
+		$store->add( $notification );
+
+		$this->assertFalse(
+			as_has_scheduled_action(
+				NotificationProcessor::SAFETY_NET_HOOK,
+				$notification->get_safety_net_args(),
+				NotificationProcessor::ACTION_SCHEDULER_GROUP
+			)
+		);
+	}
+
+	/**
+	 * @testdox Should not register the shutdown hook when the store has no registered push tokens.
+	 */
+	public function test_add_does_not_register_shutdown_hook_when_no_tokens_registered(): void {
+		$dispatcher = $this->createMock( InternalNotificationDispatcher::class );
+		$store      = new PendingNotificationStore();
+		$store->init( $dispatcher, $this->create_data_store_with_tokens( false ) );
+		$store->register();
+
+		$store->add( $this->create_order_mock( 42 ) );
+
+		$this->assertFalse( has_action( 'shutdown', array( $store, 'dispatch_all' ) ) );
+	}
+
 	/**
 	 * @testdox Should register shutdown hook only once regardless of how many notifications are added.
 	 */
@@ -141,7 +195,7 @@ class PendingNotificationStoreTest extends WC_Unit_Test_Case {
 			->method( 'dispatch' );

 		$store = new PendingNotificationStore();
-		$store->init( $dispatcher );
+		$store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
 		$store->register();
 		$store->add( $this->create_order_mock( 1 ) );

@@ -159,7 +213,7 @@ class PendingNotificationStoreTest extends WC_Unit_Test_Case {
 			->method( 'dispatch' );

 		$store = new PendingNotificationStore();
-		$store->init( $dispatcher );
+		$store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
 		$store->register();

 		$store->dispatch_all();
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewOrderNotificationTriggerTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewOrderNotificationTriggerTest.php
index 008e896abc6..69785b7e054 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewOrderNotificationTriggerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewOrderNotificationTriggerTest.php
@@ -7,6 +7,7 @@ namespace Automattic\WooCommerce\Tests\Internal\PushNotifications\Triggers;
 use Automattic\WooCommerce\Internal\PushNotifications\Dispatchers\InternalNotificationDispatcher;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\PendingNotificationStore;
 use Automattic\WooCommerce\Internal\PushNotifications\Triggers\NewOrderNotificationTrigger;
+use Automattic\WooCommerce\Tests\Internal\PushNotifications\Helpers\PushNotificationsTestTrait;
 use WC_Order;
 use WC_Unit_Test_Case;

@@ -14,6 +15,9 @@ use WC_Unit_Test_Case;
  * Tests for the NewOrderNotificationTrigger class.
  */
 class NewOrderNotificationTriggerTest extends WC_Unit_Test_Case {
+
+	use PushNotificationsTestTrait;
+
 	/**
 	 * An instance of NewOrderNotificationTrigger.
 	 *
@@ -37,7 +41,7 @@ class NewOrderNotificationTriggerTest extends WC_Unit_Test_Case {
 		$dispatcher  = $this->createMock( InternalNotificationDispatcher::class );
 		$this->store = new PendingNotificationStore();

-		$this->store->init( $dispatcher );
+		$this->store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
 		$this->store->register();

 		wc_get_container()->replace( PendingNotificationStore::class, $this->store );
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewReviewNotificationTriggerTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewReviewNotificationTriggerTest.php
index 1b77eae5353..0aaf8ef5baa 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewReviewNotificationTriggerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/NewReviewNotificationTriggerTest.php
@@ -7,6 +7,7 @@ namespace Automattic\WooCommerce\Tests\Internal\PushNotifications\Triggers;
 use Automattic\WooCommerce\Internal\PushNotifications\Dispatchers\InternalNotificationDispatcher;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\PendingNotificationStore;
 use Automattic\WooCommerce\Internal\PushNotifications\Triggers\NewReviewNotificationTrigger;
+use Automattic\WooCommerce\Tests\Internal\PushNotifications\Helpers\PushNotificationsTestTrait;
 use WC_Helper_Product;
 use WC_Unit_Test_Case;

@@ -14,6 +15,9 @@ use WC_Unit_Test_Case;
  * Tests for the NewReviewNotificationTrigger class.
  */
 class NewReviewNotificationTriggerTest extends WC_Unit_Test_Case {
+
+	use PushNotificationsTestTrait;
+
 	/**
 	 * An instance of NewReviewNotificationTrigger.
 	 *
@@ -44,7 +48,7 @@ class NewReviewNotificationTriggerTest extends WC_Unit_Test_Case {
 		$dispatcher  = $this->createMock( InternalNotificationDispatcher::class );
 		$this->store = new PendingNotificationStore();

-		$this->store->init( $dispatcher );
+		$this->store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
 		$this->store->register();

 		wc_get_container()->replace( PendingNotificationStore::class, $this->store );
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/StockNotificationTriggerTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/StockNotificationTriggerTest.php
index 765b3e0ade5..0d25c35374e 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/StockNotificationTriggerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Triggers/StockNotificationTriggerTest.php
@@ -8,6 +8,7 @@ use Automattic\WooCommerce\Internal\PushNotifications\Dispatchers\InternalNotifi
 use Automattic\WooCommerce\Internal\PushNotifications\Notifications\StockNotification;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\PendingNotificationStore;
 use Automattic\WooCommerce\Internal\PushNotifications\Triggers\StockNotificationTrigger;
+use Automattic\WooCommerce\Tests\Internal\PushNotifications\Helpers\PushNotificationsTestTrait;
 use WC_Helper_Product;
 use WC_Unit_Test_Case;

@@ -15,6 +16,9 @@ use WC_Unit_Test_Case;
  * Tests for the StockNotificationTrigger class.
  */
 class StockNotificationTriggerTest extends WC_Unit_Test_Case {
+
+	use PushNotificationsTestTrait;
+
 	/**
 	 * An instance of StockNotificationTrigger.
 	 *
@@ -38,7 +42,7 @@ class StockNotificationTriggerTest extends WC_Unit_Test_Case {
 		$dispatcher  = $this->createMock( InternalNotificationDispatcher::class );
 		$this->store = new PendingNotificationStore();

-		$this->store->init( $dispatcher );
+		$this->store->init( $dispatcher, $this->create_data_store_with_tokens( true ) );
 		$this->store->register();

 		wc_get_container()->replace( PendingNotificationStore::class, $this->store );