From 39767115d3674c412e44a42144536c77243c157a Mon Sep 17 00:00:00 2001 From: shazzad Date: Fri, 27 Feb 2026 00:36:55 +0600 Subject: [PATCH 1/2] Cache updates API response to reduce redundant HTTP calls Add short-lived site transient cache (10 min default) to Client::updates() to avoid hitting the remote API on every set_site_transient call. Cache is cleared automatically in clear_updates_transient() and refresh_updates_transient(). Cache period is configurable via argument, pass 0 to bypass. Closes #11 Co-Authored-By: Claude Opus 4.6 --- src/Client.php | 23 ++++- src/Integration.php | 14 +++ src/Updater.php | 4 +- tests/ClientApiRequestTest.php | 145 ++++++++++++++++++++++++++- tests/IntegrationTransientTest.php | 76 ++++++++++++++ tests/UpdaterPreSetTransientTest.php | 16 +++ tests/bootstrap.php | 4 + 7 files changed, 277 insertions(+), 5 deletions(-) diff --git a/src/Client.php b/src/Client.php index 8bf745f..bf840a1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -79,11 +79,24 @@ public function check_license( $license = '' ) { /** * Fetch available updates from the remote API. * + * Uses a short-lived site transient cache to avoid redundant HTTP calls + * when WordPress sets the update_plugins transient multiple times. + * * @since 1.1 * + * @param int $cache_period Cache duration in seconds. Pass 0 to skip caching. * @return array|WP_Error Response data or WP_Error on failure. */ - public function updates() { + public function updates( $cache_period = 600 ) { + if ( $cache_period > 0 ) { + $cache_key = $this->integration->get_updates_cache_key(); + $cached = get_site_transient( $cache_key ); + + if ( false !== $cached ) { + return $cached; + } + } + $args = []; if ( $this->integration->license_enabled ) { $license = $this->integration->get_license_code(); @@ -92,7 +105,13 @@ public function updates() { } } - return $this->request( 'updates', $args ); + $response = $this->request( 'updates', $args ); + + if ( $cache_period > 0 && ! is_wp_error( $response ) ) { + set_site_transient( $cache_key, $response, $cache_period ); + } + + return $response; } /** diff --git a/src/Integration.php b/src/Integration.php index d05f462..04b4dc5 100644 --- a/src/Integration.php +++ b/src/Integration.php @@ -244,6 +244,17 @@ public function get_license_data_key() { return "{$this->license_name}_data"; } + /** + * Retrieves the transient key for caching updates API responses. + * + * @since 1.3 + * + * @return string + */ + public function get_updates_cache_key() { + return "{$this->license_name}_updates_cache"; + } + /** * Get license status. @@ -353,6 +364,7 @@ public function is_license_active() { * @return void */ public function refresh_updates_transient() { + delete_site_transient( $this->get_updates_cache_key() ); set_site_transient( 'update_plugins', get_site_transient( 'update_plugins' ) ); } @@ -363,6 +375,8 @@ public function refresh_updates_transient() { * @return void */ public function clear_updates_transient() { + delete_site_transient( $this->get_updates_cache_key() ); + $transient = get_site_transient( 'update_plugins' ); // Initialize transient if it doesn't exist diff --git a/src/Updater.php b/src/Updater.php index c22136f..b4b0987 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -224,9 +224,9 @@ public function upgrader_process_complete( $upgrader, $args ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin = get_plugin_data( WP_PLUGIN_DIR . '/' . $this->integration->product_file ); - $this->integration->clear_updates_transient(); - $this->integration->product_version = $plugin['Version']; + + $this->integration->clear_updates_transient(); $this->integration->client->ping(); } } diff --git a/tests/ClientApiRequestTest.php b/tests/ClientApiRequestTest.php index 3452053..5110be8 100644 --- a/tests/ClientApiRequestTest.php +++ b/tests/ClientApiRequestTest.php @@ -7,7 +7,7 @@ class ClientApiRequestTest extends TestCase { /** - * Stub the common WP functions used by Client::request(). + * Stub the common WP functions used by Client::request() and updates() cache. */ private function stub_api_dependencies(): void { Functions\when( 'esc_url' )->returnArg(); @@ -17,6 +17,21 @@ private function stub_api_dependencies(): void { Functions\when( 'add_query_arg' )->alias( function ( $args, $url ) { return $url . '?' . http_build_query( $args ); } ); + Functions\when( 'get_site_transient' )->justReturn( false ); + Functions\when( 'set_site_transient' )->justReturn( true ); + } + + /** + * Stub only the HTTP-layer WP functions (no cache stubs). + */ + private function stub_http_dependencies(): void { + Functions\when( 'esc_url' )->returnArg(); + Functions\when( 'site_url' )->justReturn( 'https://example.com' ); + Functions\when( 'get_locale' )->justReturn( 'en_US' ); + Functions\when( 'get_bloginfo' )->justReturn( '6.4' ); + Functions\when( 'add_query_arg' )->alias( function ( $args, $url ) { + return $url . '?' . http_build_query( $args ); + } ); } /** @test */ @@ -239,4 +254,132 @@ public function details_returns_plugin_details() { $this->assertIsArray( $result ); $this->assertArrayHasKey( 'details', $result ); } + + /** @test */ + public function updates_returns_cached_response_without_api_call() { + $integration = $this->create_integration(); + + $cached_response = $this->load_fixture( 'updates-available.json' ); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $integration->get_updates_cache_key() ) + ->andReturn( $cached_response ); + + Functions\expect( 'wp_remote_request' )->never(); + + $result = $integration->client->updates(); + + $this->assertIsArray( $result ); + $this->assertSame( '1.3.0', $result['updates']['new_version'] ); + } + + /** @test */ + public function updates_caches_successful_api_response() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $fixture = $this->load_fixture_raw( 'updates-available.json' ); + $cache_key = $integration->get_updates_cache_key(); + + $stored = null; + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $cache_key ) + ->andReturn( false ); + + Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); + Functions\expect( 'wp_remote_retrieve_response_code' )->once()->andReturn( 200 ); + Functions\expect( 'wp_remote_retrieve_body' )->once()->andReturn( $fixture ); + + Functions\expect( 'set_site_transient' ) + ->once() + ->with( + $cache_key, + \Mockery::on( function ( $value ) use ( &$stored ) { + $stored = $value; + return is_array( $value ) && ! empty( $value['updates'] ); + } ), + 600 + ) + ->andReturn( true ); + + $result = $integration->client->updates(); + + $this->assertIsArray( $result ); + $this->assertNotNull( $stored ); + $this->assertArrayHasKey( 'updates', $stored ); + } + + /** @test */ + public function updates_does_not_cache_wp_error_response() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $cache_key = $integration->get_updates_cache_key(); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $cache_key ) + ->andReturn( false ); + + Functions\expect( 'wp_remote_request' ) + ->once() + ->andReturn( new WP_Error( 'http_error', 'Timeout' ) ); + + Functions\expect( 'set_site_transient' )->never(); + + $result = $integration->client->updates(); + + $this->assertInstanceOf( WP_Error::class, $result ); + } + + /** @test */ + public function updates_skips_cache_when_period_is_zero() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $fixture = $this->load_fixture_raw( 'updates-available.json' ); + + Functions\expect( 'get_site_transient' )->never(); + Functions\expect( 'set_site_transient' )->never(); + + Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); + Functions\expect( 'wp_remote_retrieve_response_code' )->once()->andReturn( 200 ); + Functions\expect( 'wp_remote_retrieve_body' )->once()->andReturn( $fixture ); + + $result = $integration->client->updates( 0 ); + + $this->assertIsArray( $result ); + $this->assertSame( '1.3.0', $result['updates']['new_version'] ); + } + + /** @test */ + public function updates_uses_custom_cache_period() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $fixture = $this->load_fixture_raw( 'updates-available.json' ); + $cache_key = $integration->get_updates_cache_key(); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $cache_key ) + ->andReturn( false ); + + Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); + Functions\expect( 'wp_remote_retrieve_response_code' )->once()->andReturn( 200 ); + Functions\expect( 'wp_remote_retrieve_body' )->once()->andReturn( $fixture ); + + Functions\expect( 'set_site_transient' ) + ->once() + ->with( $cache_key, \Mockery::type( 'array' ), 300 ) + ->andReturn( true ); + + $result = $integration->client->updates( 300 ); + + $this->assertIsArray( $result ); + $this->assertSame( '1.3.0', $result['updates']['new_version'] ); + } } diff --git a/tests/IntegrationTransientTest.php b/tests/IntegrationTransientTest.php index e9bb2b4..a229559 100644 --- a/tests/IntegrationTransientTest.php +++ b/tests/IntegrationTransientTest.php @@ -23,6 +23,10 @@ public function moves_plugin_from_response_to_no_update() { $saved = null; + Functions\expect( 'delete_site_transient' ) + ->once() + ->with( $integration->get_updates_cache_key() ); + Functions\expect( 'get_site_transient' ) ->once() ->with( 'update_plugins' ) @@ -53,6 +57,10 @@ public function creates_no_update_entry_when_plugin_not_in_response() { $saved = null; + Functions\expect( 'delete_site_transient' ) + ->once() + ->with( $integration->get_updates_cache_key() ); + Functions\expect( 'get_site_transient' ) ->once() ->with( 'update_plugins' ) @@ -80,6 +88,10 @@ public function initializes_transient_when_false() { $saved = null; + Functions\expect( 'delete_site_transient' ) + ->once() + ->with( $integration->get_updates_cache_key() ); + Functions\expect( 'get_site_transient' ) ->once() ->with( 'update_plugins' ) @@ -115,6 +127,10 @@ public function initializes_no_update_array_when_missing() { ->with( 'update_plugins' ) ->andReturn( $transient ); + Functions\expect( 'delete_site_transient' ) + ->once() + ->with( $integration->get_updates_cache_key() ); + Functions\expect( 'set_site_transient' ) ->once() ->with( 'update_plugins', Mockery::on( function ( $t ) use ( &$saved ) { @@ -127,4 +143,64 @@ public function initializes_no_update_array_when_missing() { $this->assertIsArray( $saved->no_update ); $this->assertArrayHasKey( 'my-plugin/my-plugin.php', $saved->no_update ); } + + /** @test */ + public function clear_updates_transient_deletes_cache() { + $integration = $this->create_integration(); + + $transient = new \stdClass(); + $transient->response = []; + $transient->no_update = []; + $transient->checked = []; + + $deleted_key = null; + + Functions\expect( 'delete_site_transient' ) + ->once() + ->with( Mockery::on( function ( $key ) use ( &$deleted_key ) { + $deleted_key = $key; + return true; + } ) ); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( 'update_plugins' ) + ->andReturn( $transient ); + + Functions\expect( 'set_site_transient' ) + ->once(); + + $integration->clear_updates_transient(); + + $this->assertSame( $integration->get_updates_cache_key(), $deleted_key ); + } + + /** @test */ + public function refresh_updates_transient_deletes_cache() { + $integration = $this->create_integration(); + + $transient = new \stdClass(); + + $deleted_key = null; + + Functions\expect( 'delete_site_transient' ) + ->once() + ->with( Mockery::on( function ( $key ) use ( &$deleted_key ) { + $deleted_key = $key; + return true; + } ) ); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( 'update_plugins' ) + ->andReturn( $transient ); + + Functions\expect( 'set_site_transient' ) + ->once() + ->with( 'update_plugins', $transient ); + + $integration->refresh_updates_transient(); + + $this->assertSame( $integration->get_updates_cache_key(), $deleted_key ); + } } diff --git a/tests/UpdaterPreSetTransientTest.php b/tests/UpdaterPreSetTransientTest.php index ecad74a..e76abe0 100644 --- a/tests/UpdaterPreSetTransientTest.php +++ b/tests/UpdaterPreSetTransientTest.php @@ -23,6 +23,9 @@ private function stub_api_dependencies(): void { /** * Helper: create an Updater with its Integration and stub an API response. * + * Stubs cache as a miss (get_site_transient returns false) so the API call + * proceeds, and accepts the set_site_transient call to store the cache. + * * @param string|null $fixture_name Fixture file name or null for WP_Error. * @param int $status_code HTTP status code. * @return Updater @@ -31,6 +34,9 @@ private function create_updater_with_api_response( ?string $fixture_name, int $s $integration = $this->create_integration(); $this->stub_api_dependencies(); + Functions\when( 'get_site_transient' )->justReturn( false ); + Functions\when( 'set_site_transient' )->justReturn( true ); + if ( null === $fixture_name ) { Functions\expect( 'wp_remote_request' ) ->once() @@ -102,6 +108,9 @@ public function adds_to_no_update_when_version_is_same() { $integration->product_version = '1.3.0'; // Same as fixture. $this->stub_api_dependencies(); + Functions\when( 'get_site_transient' )->justReturn( false ); + Functions\when( 'set_site_transient' )->justReturn( true ); + $fixture = $this->load_fixture_raw( 'updates-available.json' ); Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); @@ -122,6 +131,9 @@ public function adds_to_no_update_when_version_is_lower() { $integration->product_version = '2.0.0'; // Higher than fixture's 1.3.0. $this->stub_api_dependencies(); + Functions\when( 'get_site_transient' )->justReturn( false ); + Functions\when( 'set_site_transient' )->justReturn( true ); + $fixture = $this->load_fixture_raw( 'updates-available.json' ); Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); @@ -142,6 +154,9 @@ public function moves_existing_response_to_no_update_when_not_newer() { $integration->product_version = '1.3.0'; // Same as fixture. $this->stub_api_dependencies(); + Functions\when( 'get_site_transient' )->justReturn( false ); + Functions\when( 'set_site_transient' )->justReturn( true ); + $fixture = $this->load_fixture_raw( 'updates-available.json' ); Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); @@ -181,4 +196,5 @@ public function does_nothing_when_updates_key_missing() { $this->assertEmpty( $result->response ); $this->assertEmpty( $result->no_update ); } + } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 793e638..1a390fb 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -15,6 +15,10 @@ define( 'WP_PLUGIN_DIR', ABSPATH . 'wp-content/plugins' ); } +if ( ! defined( 'MINUTE_IN_SECONDS' ) ) { + define( 'MINUTE_IN_SECONDS', 60 ); +} + // Minimal WP_Error stub so source files can reference the class. if ( ! class_exists( 'WP_Error' ) ) { class WP_Error { From 2fe790e538abe78c7ba10fbde67c81c2f62bbb21 Mon Sep 17 00:00:00 2001 From: shazzad Date: Fri, 27 Feb 2026 01:00:53 +0600 Subject: [PATCH 2/2] Cache details API response to reduce redundant HTTP calls Add the same short-lived transient cache (10 min default) to Client::details() that was added to updates(). The details endpoint is called from both plugins_api and the license admin page. Both caches are cleared together in clear_updates_transient() and refresh_updates_transient(). Co-Authored-By: Claude Opus 4.6 --- src/Client.php | 23 ++++++- src/Integration.php | 13 ++++ tests/ClientApiRequestTest.php | 100 +++++++++++++++++++++++++++++ tests/IntegrationTransientTest.php | 44 ++++++------- 4 files changed, 153 insertions(+), 27 deletions(-) diff --git a/src/Client.php b/src/Client.php index bf840a1..4ca2ee8 100644 --- a/src/Client.php +++ b/src/Client.php @@ -117,11 +117,24 @@ public function updates( $cache_period = 600 ) { /** * Fetch plugin details from the remote API. * + * Uses a short-lived site transient cache to avoid redundant HTTP calls + * from plugins_api and the license admin page. + * * @since 1.1 * + * @param int $cache_period Cache duration in seconds. Pass 0 to skip caching. * @return array|WP_Error Response data or WP_Error on failure. */ - public function details() { + public function details( $cache_period = 600 ) { + if ( $cache_period > 0 ) { + $cache_key = $this->integration->get_details_cache_key(); + $cached = get_site_transient( $cache_key ); + + if ( false !== $cached ) { + return $cached; + } + } + $args = []; if ( $this->integration->license_enabled ) { $license = $this->integration->get_license_code(); @@ -130,7 +143,13 @@ public function details() { } } - return $this->request( 'details', $args ); + $response = $this->request( 'details', $args ); + + if ( $cache_period > 0 && ! is_wp_error( $response ) ) { + set_site_transient( $cache_key, $response, $cache_period ); + } + + return $response; } /** diff --git a/src/Integration.php b/src/Integration.php index 04b4dc5..2e0248b 100644 --- a/src/Integration.php +++ b/src/Integration.php @@ -255,6 +255,17 @@ public function get_updates_cache_key() { return "{$this->license_name}_updates_cache"; } + /** + * Retrieves the transient key for caching details API responses. + * + * @since 1.3 + * + * @return string + */ + public function get_details_cache_key() { + return "{$this->license_name}_details_cache"; + } + /** * Get license status. @@ -365,6 +376,7 @@ public function is_license_active() { */ public function refresh_updates_transient() { delete_site_transient( $this->get_updates_cache_key() ); + delete_site_transient( $this->get_details_cache_key() ); set_site_transient( 'update_plugins', get_site_transient( 'update_plugins' ) ); } @@ -376,6 +388,7 @@ public function refresh_updates_transient() { */ public function clear_updates_transient() { delete_site_transient( $this->get_updates_cache_key() ); + delete_site_transient( $this->get_details_cache_key() ); $transient = get_site_transient( 'update_plugins' ); diff --git a/tests/ClientApiRequestTest.php b/tests/ClientApiRequestTest.php index 5110be8..db9a531 100644 --- a/tests/ClientApiRequestTest.php +++ b/tests/ClientApiRequestTest.php @@ -382,4 +382,104 @@ public function updates_uses_custom_cache_period() { $this->assertIsArray( $result ); $this->assertSame( '1.3.0', $result['updates']['new_version'] ); } + + /** @test */ + public function details_returns_cached_response_without_api_call() { + $integration = $this->create_integration(); + + $cached_response = $this->load_fixture( 'details-success.json' ); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $integration->get_details_cache_key() ) + ->andReturn( $cached_response ); + + Functions\expect( 'wp_remote_request' )->never(); + + $result = $integration->client->details(); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'details', $result ); + } + + /** @test */ + public function details_caches_successful_api_response() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $fixture = $this->load_fixture_raw( 'details-success.json' ); + $cache_key = $integration->get_details_cache_key(); + + $stored = null; + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $cache_key ) + ->andReturn( false ); + + Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); + Functions\expect( 'wp_remote_retrieve_response_code' )->once()->andReturn( 200 ); + Functions\expect( 'wp_remote_retrieve_body' )->once()->andReturn( $fixture ); + + Functions\expect( 'set_site_transient' ) + ->once() + ->with( + $cache_key, + \Mockery::on( function ( $value ) use ( &$stored ) { + $stored = $value; + return is_array( $value ) && ! empty( $value['details'] ); + } ), + 600 + ) + ->andReturn( true ); + + $result = $integration->client->details(); + + $this->assertIsArray( $result ); + $this->assertNotNull( $stored ); + $this->assertArrayHasKey( 'details', $stored ); + } + + /** @test */ + public function details_does_not_cache_wp_error_response() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $cache_key = $integration->get_details_cache_key(); + + Functions\expect( 'get_site_transient' ) + ->once() + ->with( $cache_key ) + ->andReturn( false ); + + Functions\expect( 'wp_remote_request' ) + ->once() + ->andReturn( new WP_Error( 'http_error', 'Timeout' ) ); + + Functions\expect( 'set_site_transient' )->never(); + + $result = $integration->client->details(); + + $this->assertInstanceOf( WP_Error::class, $result ); + } + + /** @test */ + public function details_skips_cache_when_period_is_zero() { + $integration = $this->create_integration(); + $this->stub_http_dependencies(); + + $fixture = $this->load_fixture_raw( 'details-success.json' ); + + Functions\expect( 'get_site_transient' )->never(); + Functions\expect( 'set_site_transient' )->never(); + + Functions\expect( 'wp_remote_request' )->once()->andReturn( [] ); + Functions\expect( 'wp_remote_retrieve_response_code' )->once()->andReturn( 200 ); + Functions\expect( 'wp_remote_retrieve_body' )->once()->andReturn( $fixture ); + + $result = $integration->client->details( 0 ); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'details', $result ); + } } diff --git a/tests/IntegrationTransientTest.php b/tests/IntegrationTransientTest.php index a229559..8e7a008 100644 --- a/tests/IntegrationTransientTest.php +++ b/tests/IntegrationTransientTest.php @@ -23,9 +23,7 @@ public function moves_plugin_from_response_to_no_update() { $saved = null; - Functions\expect( 'delete_site_transient' ) - ->once() - ->with( $integration->get_updates_cache_key() ); + Functions\when( 'delete_site_transient' )->justReturn( true ); Functions\expect( 'get_site_transient' ) ->once() @@ -57,9 +55,7 @@ public function creates_no_update_entry_when_plugin_not_in_response() { $saved = null; - Functions\expect( 'delete_site_transient' ) - ->once() - ->with( $integration->get_updates_cache_key() ); + Functions\when( 'delete_site_transient' )->justReturn( true ); Functions\expect( 'get_site_transient' ) ->once() @@ -88,9 +84,7 @@ public function initializes_transient_when_false() { $saved = null; - Functions\expect( 'delete_site_transient' ) - ->once() - ->with( $integration->get_updates_cache_key() ); + Functions\when( 'delete_site_transient' )->justReturn( true ); Functions\expect( 'get_site_transient' ) ->once() @@ -122,15 +116,13 @@ public function initializes_no_update_array_when_missing() { $saved = null; + Functions\when( 'delete_site_transient' )->justReturn( true ); + Functions\expect( 'get_site_transient' ) ->once() ->with( 'update_plugins' ) ->andReturn( $transient ); - Functions\expect( 'delete_site_transient' ) - ->once() - ->with( $integration->get_updates_cache_key() ); - Functions\expect( 'set_site_transient' ) ->once() ->with( 'update_plugins', Mockery::on( function ( $t ) use ( &$saved ) { @@ -145,7 +137,7 @@ public function initializes_no_update_array_when_missing() { } /** @test */ - public function clear_updates_transient_deletes_cache() { + public function clear_updates_transient_deletes_caches() { $integration = $this->create_integration(); $transient = new \stdClass(); @@ -153,12 +145,12 @@ public function clear_updates_transient_deletes_cache() { $transient->no_update = []; $transient->checked = []; - $deleted_key = null; + $deleted_keys = []; Functions\expect( 'delete_site_transient' ) - ->once() - ->with( Mockery::on( function ( $key ) use ( &$deleted_key ) { - $deleted_key = $key; + ->twice() + ->with( Mockery::on( function ( $key ) use ( &$deleted_keys ) { + $deleted_keys[] = $key; return true; } ) ); @@ -172,21 +164,22 @@ public function clear_updates_transient_deletes_cache() { $integration->clear_updates_transient(); - $this->assertSame( $integration->get_updates_cache_key(), $deleted_key ); + $this->assertContains( $integration->get_updates_cache_key(), $deleted_keys ); + $this->assertContains( $integration->get_details_cache_key(), $deleted_keys ); } /** @test */ - public function refresh_updates_transient_deletes_cache() { + public function refresh_updates_transient_deletes_caches() { $integration = $this->create_integration(); $transient = new \stdClass(); - $deleted_key = null; + $deleted_keys = []; Functions\expect( 'delete_site_transient' ) - ->once() - ->with( Mockery::on( function ( $key ) use ( &$deleted_key ) { - $deleted_key = $key; + ->twice() + ->with( Mockery::on( function ( $key ) use ( &$deleted_keys ) { + $deleted_keys[] = $key; return true; } ) ); @@ -201,6 +194,7 @@ public function refresh_updates_transient_deletes_cache() { $integration->refresh_updates_transient(); - $this->assertSame( $integration->get_updates_cache_key(), $deleted_key ); + $this->assertContains( $integration->get_updates_cache_key(), $deleted_keys ); + $this->assertContains( $integration->get_details_cache_key(), $deleted_keys ); } }