Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Dev - Renames previous Order Helper class methods to use the `_id` suffix
* Dev - Expands the Stripe Order Helper class to handle customer ID, card ID, UPE payment type, and UPE redirect status metas
* Fix - Remove persistent reconnection notices
* Add - Allow cache prefetch window to be adjusted via the wc_stripe_database_cache_prefetch_window filter

= 10.0.0 - 2025-10-14 =
* Update - Removes frontend code related to Payment Request Buttons in the checkout page
Expand Down
63 changes: 54 additions & 9 deletions includes/class-wc-stripe-database-cache-prefetch.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static function get_instance(): WC_Stripe_Database_Cache_Prefetch {
* @return bool True if the cache key can be prefetched, false otherwise.
*/
public function should_prefetch_cache_key( string $key ): bool {
return isset( self::PREFETCH_CONFIG[ $key ] ) && self::PREFETCH_CONFIG[ $key ] > 0;
return $this->get_prefetch_window( $key ) > 0;
}

/**
Expand All @@ -70,12 +70,11 @@ public function should_prefetch_cache_key( string $key ): bool {
* @param int $expiry_time The expiry time of the cache entry.
*/
public function maybe_queue_prefetch( string $key, int $expiry_time ): void {
if ( ! $this->should_prefetch_cache_key( $key ) ) {
$prefetch_window = $this->get_prefetch_window( $key );
if ( 0 === $prefetch_window ) {
return;
}

$prefetch_window = self::PREFETCH_CONFIG[ $key ];

// If now plus the prefetch window is before the expiry time, do not trigger a prefetch.
if ( ( time() + $prefetch_window ) < $expiry_time ) {
return;
Expand Down Expand Up @@ -107,14 +106,48 @@ public function maybe_queue_prefetch( string $key, int $expiry_time ): void {
}
}

/**
* Get the prefetch window for a given cache key.
*
* @param string $key The unprefixed cache key to get the prefetch window for.
* @return int The prefetch window for the cache key. 0 indicates that prefetching is disabled for the key.
*/
private function get_prefetch_window( string $cache_key ): int {
if ( ! isset( self::PREFETCH_CONFIG[ $cache_key ] ) ) {
return 0;
}

$initial_prefetch_window = self::PREFETCH_CONFIG[ $cache_key ];

/**
* Filters the cache prefetch window for a given cache key. Return 0 or less to disable prefetching for the key.
*
* @param int $prefetch_window The prefetch window for the cache key.
* @param string $cache_key The unprefixed cache key.
*/
$prefetch_window = apply_filters( 'wc_stripe_database_cache_prefetch_window', $initial_prefetch_window, $cache_key );

// If the filter returns a non-integer, use the initial prefetch window.
if ( ! is_int( $prefetch_window ) ) {
return $initial_prefetch_window;
}

if ( $prefetch_window <= 0 ) {
return 0;
}

return $prefetch_window;
}

/**
* Check if a prefetch is already queued up.
*
* @param string $key The unprefixed cache key to check.
* @return bool True if a prefetch is queued up, false otherwise.
*/
private function is_prefetch_queued( string $key ): bool {
if ( ! isset( self::PREFETCH_CONFIG[ $key ] ) ) {
$prefetch_window = $this->get_prefetch_window( $key );
if ( 0 === $prefetch_window ) {
return false;
}

Expand All @@ -126,8 +159,7 @@ private function is_prefetch_queued( string $key ): bool {
return false;
}

$now = time();
$prefetch_window = self::PREFETCH_CONFIG[ $key ];
$now = time();

if ( $prefetch_option >= ( $now - $prefetch_window ) ) {
// If the prefetch entry expires in the future, or falls within the prefetch window for the key, we should consider the item live and queued.
Expand Down Expand Up @@ -161,13 +193,14 @@ public function handle_prefetch_action( $key ): void {
'Invalid cache prefetch key',
[
'cache_key' => $key,
'reason' => 'invalid_key',
'reason' => 'invalid_cache_key',
]
);
return;
}

if ( ! $this->should_prefetch_cache_key( $key ) ) {
// We don't use should_prefetch_cache_key(), as that calls get_prefetch_window(), which we're checking below.
if ( ! isset( self::PREFETCH_CONFIG[ $key ] ) ) {
WC_Stripe_Logger::warning(
'Invalid cache prefetch key',
[
Expand All @@ -178,6 +211,18 @@ public function handle_prefetch_action( $key ): void {
return;
}

$prefetch_window = $this->get_prefetch_window( $key );
if ( 0 === $prefetch_window ) {
WC_Stripe_Logger::warning(
'Cache prefetch key was disabled',
[
'cache_key' => $key,
'reason' => 'cache_key_disabled',
]
);
return;
}

$this->prefetch_cache_key( $key );

// Regardless of whether the prefetch was successful or not, we should remove the prefetch tracking option.
Expand Down
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
* Dev - Renames previous Order Helper class methods to use the `_id` suffix
* Dev - Expands the Stripe Order Helper class to handle customer ID, card ID, UPE payment type, and UPE redirect status metas
* Fix - Remove persistent reconnection notices
* Add - Allow cache prefetch window to be adjusted via the wc_stripe_database_cache_prefetch_window filter

[See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt).
116 changes: 103 additions & 13 deletions tests/phpunit/WC_Stripe_Database_Cache_Prefetch_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,45 @@ class WC_Stripe_Database_Cache_Prefetch_Test extends \WP_UnitTestCase {
*/
public function provide_handle_prefetch_action_test_cases(): array {
return [
'pmc_key_exists_and_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, true ],
'invalid_key_should_not_prefetch' => [ 'invalid_test_key', false ],
'pmc_key_exists_and_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, null, true ],
'pmc_key_exists_and_should_prefetch_with_20_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 20, true ],
'pmc_key_exists_and_should_not_prefetch_with_0_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 0, false ],
'pmc_key_exists_and_should_not_prefetch_with_negative_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, -3, false ],
'pmc_key_exists_and_should_prefetch_with_invalid_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 'invalid', true ],
'invalid_key_should_not_prefetch' => [ 'invalid_test_key', null, false ],
'invalid_key_should_not_prefetch_with_filter' => [ 'invalid_test_key', 10, false ],
'invalid_key_should_not_prefetch_with_0_filter' => [ 'invalid_test_key', 0, false ],
'invalid_key_should_not_prefetch_with_negative_filter' => [ 'invalid_test_key', -3, false ],
'invalid_key_should_not_prefetch_with_invalid_filter' => [ 'invalid_test_key', 'invalid', false ],
];
}

/**
* Test {@see \WC_Stripe_Database_Cache_Prefetch::handle_prefetch_action()}.
*
* @param string $key The key to prefetch.
* @param bool $should_prefetch Whether we expect the key to be prefetched.
* @param string $key The key to prefetch.
* @param mixed $prefetch_window_filter_value The value to filter the prefetch window with. Null is no filter value set; other values will be returned as-is.
* @param bool $should_prefetch Whether we expect the key to be prefetched.
*
* @dataProvider provide_handle_prefetch_action_test_cases
*/
public function test_handle_prefetch_action( string $key, bool $should_prefetch ): void {
public function test_handle_prefetch_action( string $key, $prefetch_window_filter_value, bool $should_prefetch ): void {
$mock_instance = $this->getMockBuilder( 'WC_Stripe_Database_Cache_Prefetch' )
->disableOriginalConstructor()
->onlyMethods( [ 'prefetch_cache_key' ] )
->getMock();

$filter_callback = null;
if ( null !== $prefetch_window_filter_value ) {
$filter_callback = function ( $prefetch_window, $cache_key ) use ( $key, $prefetch_window_filter_value ) {
if ( $cache_key === $key ) {
return $prefetch_window_filter_value;
}
return $prefetch_window;
};
add_filter( 'wc_stripe_database_cache_prefetch_window', $filter_callback, 10, 2 );
}

$expected_prefetch_count = $should_prefetch ? $this->once() : $this->never();

$mock_instance->expects( $expected_prefetch_count )
Expand All @@ -45,6 +65,10 @@ public function test_handle_prefetch_action( string $key, bool $should_prefetch
->willReturn( true );

$mock_instance->handle_prefetch_action( $key );

if ( null !== $filter_callback ) {
remove_filter( 'wc_stripe_database_cache_prefetch_window', $filter_callback, 10 );
}
}

/**
Expand All @@ -54,13 +78,18 @@ public function test_handle_prefetch_action( string $key, bool $should_prefetch
*/
public function provide_maybe_queue_prefetch_test_cases(): array {
return [
'invalid_key_should_not_prefetch' => [ 'invalid_test_key', 5, false ],
'pmc_key_expires_in_60_seconds_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 60, false ],
'pmc_key_expires_in_5_seconds_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true ],
'pmc_key_expires_in_5_seconds_with_option_set_2s_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, 2 ],
'pmc_key_expires_in_5_seconds_with_option_set_-2s_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, -2 ],
'pmc_key_expires_in_5_seconds_with_option_set_-11s_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true, -11 ],
'pmc_key_expires_in_5_seconds_with_invalid_option_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true, 'invalid' ],
'invalid_key_should_not_prefetch' => [ 'invalid_test_key', 5, false ],
'pmc_key_expires_in_60_seconds_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 60, false ],
'pmc_key_expires_in_5_seconds_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true ],
'pmc_key_expires_in_5_seconds_with_option_set_2s_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, 2 ],
'pmc_key_expires_in_5_seconds_with_option_set_-2s_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, -2 ],
'pmc_key_expires_in_5_seconds_with_option_set_-11s_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true, -11 ],
'pmc_key_expires_in_5_seconds_with_invalid_option_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true, 'invalid' ],
'pmc_key_expires_in_5_seconds_with_20_filter_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true, null, 20 ],
'pmc_key_expires_in_5_seconds_with_20_filter_option_-11_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, -11, 20 ],
'pmc_key_expires_in_5_seconds_with_0_filter_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, null, 0 ],
'pmc_key_expires_in_5_seconds_with_negative_filter_should_not_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, false, null, -3 ],
'pmc_key_expires_in_5_seconds_with_invalid_filter_should_prefetch' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, 5, true, null, 'invalid' ],
];
}

Expand All @@ -74,7 +103,7 @@ public function provide_maybe_queue_prefetch_test_cases(): array {
*
* @dataProvider provide_maybe_queue_prefetch_test_cases
*/
public function test_maybe_queue_prefetch( string $key, int $expiry_time_adjustment, bool $should_enqueue_action, $option_adjusted_time = null ): void {
public function test_maybe_queue_prefetch( string $key, int $expiry_time_adjustment, bool $should_enqueue_action, $option_adjusted_time = null, $prefetch_window_filter_value = null ): void {
$instance = \WC_Stripe_Database_Cache_Prefetch::get_instance();

$mock_class = $this->getMockBuilder( \stdClass::class )
Expand All @@ -88,6 +117,17 @@ public function test_maybe_queue_prefetch( string $key, int $expiry_time_adjustm

add_filter( 'pre_as_enqueue_async_action', [ $mock_class, 'test_stub_callback' ], 10, 4 );

$filter_callback = null;
if ( null !== $prefetch_window_filter_value ) {
$filter_callback = function ( $prefetch_window, $cache_key ) use ( $key, $prefetch_window_filter_value ) {
if ( $cache_key === $key ) {
return $prefetch_window_filter_value;
}
return $prefetch_window;
};
add_filter( 'wc_stripe_database_cache_prefetch_window', $filter_callback, 10, 2 );
}

$test_args = [ $key, $expiry_time_adjustment, $should_enqueue_action, $option_adjusted_time ];

$option_name = 'wcstripe_prefetch_' . $key;
Expand All @@ -112,6 +152,9 @@ public function test_maybe_queue_prefetch( string $key, int $expiry_time_adjustm
$end_time = time();

remove_filter( 'pre_as_enqueue_async_action', [ $mock_class, 'test_stub_callback' ], 10 );
if ( null !== $filter_callback ) {
remove_filter( 'wc_stripe_database_cache_prefetch_window', $filter_callback, 10 );
}

$option_value = get_option( $option_name, false );

Expand All @@ -127,4 +170,51 @@ public function test_maybe_queue_prefetch( string $key, int $expiry_time_adjustm
$this->assertEquals( $initial_option_value, $option_value );
}
}

/**
* Provide test cases for {@see test_should_prefetch_cache_key()}.
*
* @return array
*/
public function provide_test_should_prefetch_cache_key_test_cases(): array {
return [
'invalid_key_should_not_prefetch' => [ 'invalid_test_key', false, null ],
'pmc_key_should_prefetch_with_no_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, true, null ],
'pmc_key_should_not_prefetch_with_0_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, false, 0 ],
'pmc_key_should_prefetch_with_20_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, true, 20 ],
'pmc_key_should_not_prefetch_with_negative_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, false, -5 ],
'pmc_key_should_prefetch_with_invalid_filter' => [ \WC_Stripe_Payment_Method_Configurations::CONFIGURATION_CACHE_KEY, true, 'invalid' ],
];
}

/**
* Test {@see \WC_Stripe_Database_Cache_Prefetch::should_prefetch_cache_key()}.
*
* @param string $cache_key The key to prefetch.
* @param bool $expected_result The expected result.
* @param mixed $filter_return_value The value to return from the prefetch window filter. Null is no filter value returned; other values will be returned as-is.
*
* @dataProvider provide_test_should_prefetch_cache_key_test_cases
*/
public function test_should_prefetch_cache_key( string $cache_key, bool $expected_result, $filter_return_value = null ): void {
$instance = \WC_Stripe_Database_Cache_Prefetch::get_instance();

$filter_callback = null;
if ( null !== $filter_return_value ) {
$filter_callback = function ( $prefetch_window, $key ) use ( $cache_key, $filter_return_value ) {
if ( $cache_key === $key ) {
return $filter_return_value;
}
return $prefetch_window;
};
add_filter( 'wc_stripe_database_cache_prefetch_window', $filter_callback, 10, 2 );
}

$result = $instance->should_prefetch_cache_key( $cache_key );
if ( null !== $filter_callback ) {
remove_filter( 'wc_stripe_database_cache_prefetch_window', $filter_callback, 10 );
}

$this->assertEquals( $expected_result, $result );
}
}
Loading