From a9a365e0258a6331ecbc5da616d9ad34f3e5d0b3 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 17:29:45 +0800 Subject: [PATCH 01/15] Remove and Revoke Tokens on Uninstall --- uninstall.php | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 uninstall.php diff --git a/uninstall.php b/uninstall.php new file mode 100644 index 000000000..df9a2a74a --- /dev/null +++ b/uninstall.php @@ -0,0 +1,75 @@ + Delete. + * + * @package ConvertKit + * @author ConvertKit + */ + +// If uninstall.php is not called by WordPress, die. +if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { + die; +} + +// Only WordPress and PHP methods can be used. Plugin classes and methods +// are not reliably available due to the Plugin being deactivated and going +// through deletion now. + +// Get settings. +$settings = get_option( '_wp_convertkit_settings' ); + +// Bail if no settings exist. +if ( ! $settings ) { + return; +} + +// Revoke Access Token. +if ( array_key_exists( 'access_token', $settings ) && ! empty( $settings['access_token'] ) ) { + wp_remote_post( + 'https://api.kit.com/v4/oauth/revoke', + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'client_id' => 'HXZlOCj-K5r0ufuWCtyoyo3f688VmMAYSsKg1eGvw0Y', + 'token' => $settings['access_token'], + ) + ), + 'timeout' => 5, + ) + ); +} + +// Revoke Refresh Token. +if ( array_key_exists( 'refresh_token', $settings ) && ! empty( $settings['refresh_token'] ) ) { + wp_remote_post( + 'https://api.kit.com/v4/oauth/revoke', + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'client_id' => 'HXZlOCj-K5r0ufuWCtyoyo3f688VmMAYSsKg1eGvw0Y', + 'token' => $settings['refresh_token'], + ) + ), + 'timeout' => 5, + ) + ); +} + +// Remove credentials from settings. +$settings['access_token'] = ''; +$settings['refresh_token'] = ''; +$settings['token_expires'] = ''; +$settings['api_key'] = ''; +$settings['api_secret'] = ''; + +// Save settings. +update_option( '_wp_convertkit_settings', $settings ); From 0b162a9c238916c6c8354a26c47b5bb6063bfa14 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 17:30:11 +0800 Subject: [PATCH 02/15] Coding standards --- uninstall.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uninstall.php b/uninstall.php index df9a2a74a..ac47f35cd 100644 --- a/uninstall.php +++ b/uninstall.php @@ -65,11 +65,11 @@ } // Remove credentials from settings. -$settings['access_token'] = ''; +$settings['access_token'] = ''; $settings['refresh_token'] = ''; $settings['token_expires'] = ''; -$settings['api_key'] = ''; -$settings['api_secret'] = ''; +$settings['api_key'] = ''; +$settings['api_secret'] = ''; // Save settings. update_option( '_wp_convertkit_settings', $settings ); From 5759b0043ccab0baa61ee2f29b376d65d40726b1 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 18:43:05 +0800 Subject: [PATCH 03/15] Added test --- .../other/ActivateDeactivatePluginCest.php | 67 +++++++++++++++++++ tests/Support/Helper/KitPlugin.php | 13 ++++ tests/Support/Helper/ThirdPartyPlugin.php | 32 +++++++++ 3 files changed, 112 insertions(+) diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index 88dfd4e37..26a70c5fc 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -60,4 +60,71 @@ public function testPluginActivationAndDeactivationWithOtherPlugins(EndToEndTest $I->deactivateThirdPartyPlugin($I, 'convertkit-for-woocommerce'); $I->deactivateKitPlugin($I); } + + /** + * Test that the Plugin's access and refresh tokens are revoked, and all v4 and v4 + * API credentials are removed from the Plugin's settings when the Plugin is deleted. + * + * @since 3.2.4 + * + * @param EndToEndTester $I Tester. + */ + public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) + { + // Activate this Plugin. + $I->activateKitPlugin($I, false); + + // Initialize the API without an access token or refresh token. + $api = new \ConvertKit_API_V4( + $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + $_ENV['KIT_OAUTH_REDIRECT_URI'] + ); + + // Generate an access token by API key and secret. + $result = $api->get_access_token_by_api_key_and_secret( + $_ENV['CONVERTKIT_API_KEY'], + $_ENV['CONVERTKIT_API_SECRET'], + wp_generate_password( 10, false ) // Random tenant name to produce a token for this request only. + ); + + // Store the tokens and API keys in the Plugin's settings. + $I->setupKitPlugin( + $I, + [ + 'access_token' => $result['oauth']['access_token'], + 'refresh_token' => $result['oauth']['refresh_token'], + 'token_expires' => $result['oauth']['expires_at'], + 'api_key' => $_ENV['CONVERTKIT_API_KEY'], + 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], + ] + ); + + // Deactivate the Plugin. + $I->deactivateKitPlugin($I); + + // Delete the Plugin. + $I->deleteKitPlugin($I); + + // Confirm the credentials have been removed from the Plugin's settings. + $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings'); + $I->assertEmpty($settings['access_token']); + $I->assertEmpty($settings['refresh_token']); + $I->assertEmpty($settings['token_expires']); + $I->assertEmpty($settings['api_key']); + $I->assertEmpty($settings['api_secret']); + + // Initialize the API with the (now revoked) access token and refresh token. + $api = new \ConvertKit_API_V4( + $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + $_ENV['KIT_OAUTH_REDIRECT_URI'], + $result['oauth']['access_token'], + $result['oauth']['refresh_token'] + ); + + // Confirm attempting to use the revoked access token no longer works. + $this->assertInstanceOf( 'WP_Error', $api->get_account() ); + + // Confirm attempting to use the revoked refresh token no longer works. + $this->assertInstanceOf( 'WP_Error', $api->refresh_token() ); + } } diff --git a/tests/Support/Helper/KitPlugin.php b/tests/Support/Helper/KitPlugin.php index 05ce2056f..593a0bf09 100644 --- a/tests/Support/Helper/KitPlugin.php +++ b/tests/Support/Helper/KitPlugin.php @@ -38,6 +38,19 @@ public function deactivateKitPlugin($I) $I->deactivateThirdPartyPlugin($I, 'convertkit'); } + /** + * Helper method to delete the Kit Plugin, checking + * it deleted and no errors were output. + * + * @since 3.2.4 + * + * @param EndToEndTester $I EndToEndTester. + */ + public function deleteKitPlugin($I) + { + $I->deleteThirdPartyPlugin($I, 'convertkit'); + } + /** * Helper method to programmatically setup the Plugin's settings, as if the * user configured the Plugin at `Settings > Kit`. diff --git a/tests/Support/Helper/ThirdPartyPlugin.php b/tests/Support/Helper/ThirdPartyPlugin.php index a0ba082ac..6399f8c89 100644 --- a/tests/Support/Helper/ThirdPartyPlugin.php +++ b/tests/Support/Helper/ThirdPartyPlugin.php @@ -121,6 +121,38 @@ public function deactivateThirdPartyPlugin($I, $name) } } + /** + * Helper method to delete a third party Plugin, checking + * it deleted and no errors were output. + * + * @since 3.2.4 + * + * @param EndToEndTester $I EndToEnd Tester. + * @param string $name Plugin Slug. + */ + public function deleteThirdPartyPlugin($I, $name) + { + // Login as the Administrator, if we're not already logged in. + if ( ! $this->amLoggedInAsAdmin($I) ) { + $this->doLoginAsAdmin($I); + } + + // Go to the Plugins screen in the WordPress Administration interface. + $I->amOnPluginsPage(); + + // Wait for the Plugins page to load. + $I->waitForElementVisible('body.plugins-php'); + + // Delete the Plugin. + $I->click('a#delete-' . $name); + + // Click the confirmation dialog. + $I->acceptPopup(); + + // Wait for the Plugin to be marked as deleted. + $I->waitForElementNotVisible('table.plugins tr.deleted[data-slug=' . $name . ']'); + } + /** * Helper method to check if the Administrator is logged in. * From 53f0a46c8eb8c0fc92908feb0bed1a894e794795 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 18:43:10 +0800 Subject: [PATCH 04/15] Isolate test --- .github/workflows/tests.yml | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 53c9ae07e..68e63d116 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,28 +59,11 @@ jobs: fail-fast: false matrix: wp-versions: [ 'latest' ] #[ '6.1.1', 'latest' ] - php-versions: [ '8.1', '8.2', '8.3', '8.4' ] #[ '7.4', '8.0', '8.1' ] + php-versions: [ '8.1' ] #[ '7.4', '8.0', '8.1' ] # Folder names within the 'tests' folder to run tests in parallel. test-groups: [ - 'EndToEnd/broadcasts/blocks-shortcodes', - 'EndToEnd/broadcasts/import-export', - 'EndToEnd/forms/blocks-shortcodes', - 'EndToEnd/forms/general', - 'EndToEnd/forms/post-types', - 'EndToEnd/general/other', - 'EndToEnd/general/plugin-screens', - 'EndToEnd/integrations/divi-builder', - 'EndToEnd/integrations/divi-theme', - 'EndToEnd/integrations/other', - 'EndToEnd/integrations/wlm', - 'EndToEnd/integrations/woocommerce', - 'EndToEnd/landing-pages', - 'EndToEnd/products', - 'EndToEnd/restrict-content/general', - 'EndToEnd/restrict-content/post-types', - 'EndToEnd/tags', - 'Integration' + 'EndToEnd/general/other/ActivateDeactivatePluginCest:testPluginDeletionRevokesAndRemovesTokens' ] # Steps to install, configure and run tests From 94bb25fee9dc951d581415bb2c7640d15dfef2ca Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:05:10 +0800 Subject: [PATCH 05/15] Modified test to use wp_remote_* methods --- .../other/ActivateDeactivatePluginCest.php | 73 +++++++++++++------ 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index 26a70c5fc..a59d6f5d0 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -74,26 +74,34 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) // Activate this Plugin. $I->activateKitPlugin($I, false); - // Initialize the API without an access token or refresh token. - $api = new \ConvertKit_API_V4( - $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], - $_ENV['KIT_OAUTH_REDIRECT_URI'] - ); - - // Generate an access token by API key and secret. - $result = $api->get_access_token_by_api_key_and_secret( - $_ENV['CONVERTKIT_API_KEY'], - $_ENV['CONVERTKIT_API_SECRET'], - wp_generate_password( 10, false ) // Random tenant name to produce a token for this request only. + // Generate an access token and refresh token by API key and secret. + // We don't use the tokens from the environment, as revoking those + // would result in later tests failing. + $result = wp_remote_post( + 'https://api.kit.com/wordpress/accounts/oauth_access_token', + [ + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'api_key' => $_ENV['CONVERTKIT_API_KEY'], + 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], + 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + 'tenant_name' => wp_generate_password( 10, false ), // Random tenant name to produce a token for this request only. + ] + ), + ] ); + $tokens = json_decode(wp_remote_retrieve_body($result), true)['oauth']; // Store the tokens and API keys in the Plugin's settings. $I->setupKitPlugin( $I, [ - 'access_token' => $result['oauth']['access_token'], - 'refresh_token' => $result['oauth']['refresh_token'], - 'token_expires' => $result['oauth']['expires_at'], + 'access_token' => $tokens['access_token'], + 'refresh_token' => $tokens['refresh_token'], + 'token_expires' => $tokens['expires_at'], 'api_key' => $_ENV['CONVERTKIT_API_KEY'], 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], ] @@ -113,18 +121,35 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) $I->assertEmpty($settings['api_key']); $I->assertEmpty($settings['api_secret']); - // Initialize the API with the (now revoked) access token and refresh token. - $api = new \ConvertKit_API_V4( - $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], - $_ENV['KIT_OAUTH_REDIRECT_URI'], - $result['oauth']['access_token'], - $result['oauth']['refresh_token'] - ); - // Confirm attempting to use the revoked access token no longer works. - $this->assertInstanceOf( 'WP_Error', $api->get_account() ); + $result = wp_remote_get( + 'https://api.kit.com/v4/account', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $tokens['access_token'], + ], + ] + ); + $data = json_decode(wp_remote_retrieve_body($result), true); + $I->assertArrayHasKey( 'errors', $data ); + $I->assertEquals( 'The access token is invalid', $data['errors'][0] ); // Confirm attempting to use the revoked refresh token no longer works. - $this->assertInstanceOf( 'WP_Error', $api->refresh_token() ); + $result = wp_remote_post( + 'https://api.kit.com/v4/oauth/token', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $tokens['access_token'], + ], + 'body' => [ + 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + 'grant_type' => 'refresh_token', + 'refresh_token' => $tokens['refresh_token'], + ], + ] + ); + $data = json_decode(wp_remote_retrieve_body($result), true); + $I->assertArrayHasKey( 'error', $data ); + $I->assertEquals( 'invalid_grant', $data['error'] ); } } From 8693a8cc0baabf3aaadff2f7d649693cf532f2ff Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:11:21 +0800 Subject: [PATCH 06/15] Use activateKitPlugin without `false` param --- tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index a59d6f5d0..bfb13ff71 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -72,7 +72,7 @@ public function testPluginActivationAndDeactivationWithOtherPlugins(EndToEndTest public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) { // Activate this Plugin. - $I->activateKitPlugin($I, false); + $I->activateKitPlugin($I); // Generate an access token and refresh token by API key and secret. // We don't use the tokens from the environment, as revoking those From 1af7a034143aed1d6681e59df5ab122cf7676923 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:18:22 +0800 Subject: [PATCH 07/15] Tests: Run all ActivateDeactivatePluginCest tests --- .github/workflows/tests.yml | 2 +- tests/Support/Helper/ThirdPartyPlugin.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 68e63d116..47cad5775 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,7 +63,7 @@ jobs: # Folder names within the 'tests' folder to run tests in parallel. test-groups: [ - 'EndToEnd/general/other/ActivateDeactivatePluginCest:testPluginDeletionRevokesAndRemovesTokens' + 'EndToEnd/general/other/ActivateDeactivatePluginCest' ] # Steps to install, configure and run tests diff --git a/tests/Support/Helper/ThirdPartyPlugin.php b/tests/Support/Helper/ThirdPartyPlugin.php index 6399f8c89..eb1d29141 100644 --- a/tests/Support/Helper/ThirdPartyPlugin.php +++ b/tests/Support/Helper/ThirdPartyPlugin.php @@ -144,6 +144,7 @@ public function deleteThirdPartyPlugin($I, $name) $I->waitForElementVisible('body.plugins-php'); // Delete the Plugin. + $I->waitForElementVisible('a#delete-' . $name); $I->click('a#delete-' . $name); // Click the confirmation dialog. From da0de2b51aa9fc2bf9472d5569caaf2424cf3a1a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:30:50 +0800 Subject: [PATCH 08/15] =?UTF-8?q?Tests:=20Don=E2=80=99t=20enable=20DISALLO?= =?UTF-8?q?W=5FFILE=5FMODS=20on=20general/other=20suite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 47cad5775..7fd0e0b06 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -170,7 +170,10 @@ jobs: # DISALLOW_FILE_MODS = true is required to disable the block directory's "Available to Install" suggestions, which trips up # tests that search and wait for block results. + # We don't enable DISALLOW_FILE_MODS for the ActivateDeactivatePluginCest test, as tests will perform a Plugin deletion + # which requires DISALLOW_FILE_MODS to not be true. - name: Enable DISALLOW_FILE_MODS + if: ${{ matrix.test-groups != 'EndToEnd/general/other/ActivateDeactivatePluginCest' }} working-directory: ${{ env.ROOT_DIR }} run: | wp-cli config set DISALLOW_FILE_MODS true --raw From 2a67649f2df0eb4594e03cdd287abfabe22f00f5 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:39:47 +0800 Subject: [PATCH 09/15] Tests: Cleanup previous test --- tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index bfb13ff71..ddf10c38b 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -59,6 +59,7 @@ public function testPluginActivationAndDeactivationWithOtherPlugins(EndToEndTest // Deactivate Plugins. $I->deactivateThirdPartyPlugin($I, 'convertkit-for-woocommerce'); $I->deactivateKitPlugin($I); + $I->resetKitPlugin($I); } /** From 724e1f96c79d504e1b247ec763b3a33c1045e0ae Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:51:34 +0800 Subject: [PATCH 10/15] Tests: Add wait() before checking settings --- tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index ddf10c38b..5e57725dc 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -115,6 +115,7 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) $I->deleteKitPlugin($I); // Confirm the credentials have been removed from the Plugin's settings. + $I->wait(2); $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings'); $I->assertEmpty($settings['access_token']); $I->assertEmpty($settings['refresh_token']); From 7e0cdf5d78baf86faa6c4ac30f1d6c67da1bd418 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 19:57:57 +0800 Subject: [PATCH 11/15] Fix expected error message on test --- tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index 5e57725dc..9c3b38bdb 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -115,7 +115,7 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) $I->deleteKitPlugin($I); // Confirm the credentials have been removed from the Plugin's settings. - $I->wait(2); + $I->wait(3); $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings'); $I->assertEmpty($settings['access_token']); $I->assertEmpty($settings['refresh_token']); @@ -134,7 +134,7 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) ); $data = json_decode(wp_remote_retrieve_body($result), true); $I->assertArrayHasKey( 'errors', $data ); - $I->assertEquals( 'The access token is invalid', $data['errors'][0] ); + $I->assertEquals( 'The access token was revoked', $data['errors'][0] ); // Confirm attempting to use the revoked refresh token no longer works. $result = wp_remote_post( From 51f48db42a201985b9aed54a9aab545acac605a4 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 20:21:54 +0800 Subject: [PATCH 12/15] Reinstate all tests --- .github/workflows/tests.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7fd0e0b06..a7bc1f9f5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,7 +63,24 @@ jobs: # Folder names within the 'tests' folder to run tests in parallel. test-groups: [ - 'EndToEnd/general/other/ActivateDeactivatePluginCest' + 'EndToEnd/broadcasts/blocks-shortcodes', + 'EndToEnd/broadcasts/import-export', + 'EndToEnd/forms/blocks-shortcodes', + 'EndToEnd/forms/general', + 'EndToEnd/forms/post-types', + 'EndToEnd/general/other', + 'EndToEnd/general/plugin-screens', + 'EndToEnd/integrations/divi-builder', + 'EndToEnd/integrations/divi-theme', + 'EndToEnd/integrations/other', + 'EndToEnd/integrations/wlm', + 'EndToEnd/integrations/woocommerce', + 'EndToEnd/landing-pages', + 'EndToEnd/products', + 'EndToEnd/restrict-content/general', + 'EndToEnd/restrict-content/post-types', + 'EndToEnd/tags', + 'Integration' ] # Steps to install, configure and run tests @@ -173,7 +190,7 @@ jobs: # We don't enable DISALLOW_FILE_MODS for the ActivateDeactivatePluginCest test, as tests will perform a Plugin deletion # which requires DISALLOW_FILE_MODS to not be true. - name: Enable DISALLOW_FILE_MODS - if: ${{ matrix.test-groups != 'EndToEnd/general/other/ActivateDeactivatePluginCest' }} + if: ${{ matrix.test-groups != 'EndToEnd/general/other' }} working-directory: ${{ env.ROOT_DIR }} run: | wp-cli config set DISALLOW_FILE_MODS true --raw From 1c2b7c05ee2539cd5c0e3a94144360aa018a0462 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 9 Apr 2026 12:27:11 +0800 Subject: [PATCH 13/15] Tests: Reinstate all PHP versions --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a7bc1f9f5..0c90cbeb3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,7 +59,7 @@ jobs: fail-fast: false matrix: wp-versions: [ 'latest' ] #[ '6.1.1', 'latest' ] - php-versions: [ '8.1' ] #[ '7.4', '8.0', '8.1' ] + php-versions: [ '8.1', '8.2', '8.3', '8.4' ] #[ '7.4', '8.0', '8.1' ] # Folder names within the 'tests' folder to run tests in parallel. test-groups: [ From 98e01bbf00d17d460196c1741c2787854e4363f6 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 9 Apr 2026 12:28:49 +0800 Subject: [PATCH 14/15] Run Uninstall Tests in isolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test results in Plugin deletion. If other tests exist in the same test-group, those would subsequently fail as there’s no Plugin to test. --- .github/workflows/tests.yml | 1 + .../other/ActivateDeactivatePluginCest.php | 93 --------------- .../general/uninstall/UninstallCest.php | 106 ++++++++++++++++++ 3 files changed, 107 insertions(+), 93 deletions(-) create mode 100644 tests/EndToEnd/general/uninstall/UninstallCest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0c90cbeb3..d09c735c7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,6 +69,7 @@ jobs: 'EndToEnd/forms/general', 'EndToEnd/forms/post-types', 'EndToEnd/general/other', + 'EndToEnd/general/uninstall', 'EndToEnd/general/plugin-screens', 'EndToEnd/integrations/divi-builder', 'EndToEnd/integrations/divi-theme', diff --git a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php index 9c3b38bdb..82760c62a 100644 --- a/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php +++ b/tests/EndToEnd/general/other/ActivateDeactivatePluginCest.php @@ -61,97 +61,4 @@ public function testPluginActivationAndDeactivationWithOtherPlugins(EndToEndTest $I->deactivateKitPlugin($I); $I->resetKitPlugin($I); } - - /** - * Test that the Plugin's access and refresh tokens are revoked, and all v4 and v4 - * API credentials are removed from the Plugin's settings when the Plugin is deleted. - * - * @since 3.2.4 - * - * @param EndToEndTester $I Tester. - */ - public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) - { - // Activate this Plugin. - $I->activateKitPlugin($I); - - // Generate an access token and refresh token by API key and secret. - // We don't use the tokens from the environment, as revoking those - // would result in later tests failing. - $result = wp_remote_post( - 'https://api.kit.com/wordpress/accounts/oauth_access_token', - [ - 'headers' => [ - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'api_key' => $_ENV['CONVERTKIT_API_KEY'], - 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], - 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], - 'tenant_name' => wp_generate_password( 10, false ), // Random tenant name to produce a token for this request only. - ] - ), - ] - ); - $tokens = json_decode(wp_remote_retrieve_body($result), true)['oauth']; - - // Store the tokens and API keys in the Plugin's settings. - $I->setupKitPlugin( - $I, - [ - 'access_token' => $tokens['access_token'], - 'refresh_token' => $tokens['refresh_token'], - 'token_expires' => $tokens['expires_at'], - 'api_key' => $_ENV['CONVERTKIT_API_KEY'], - 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], - ] - ); - - // Deactivate the Plugin. - $I->deactivateKitPlugin($I); - - // Delete the Plugin. - $I->deleteKitPlugin($I); - - // Confirm the credentials have been removed from the Plugin's settings. - $I->wait(3); - $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings'); - $I->assertEmpty($settings['access_token']); - $I->assertEmpty($settings['refresh_token']); - $I->assertEmpty($settings['token_expires']); - $I->assertEmpty($settings['api_key']); - $I->assertEmpty($settings['api_secret']); - - // Confirm attempting to use the revoked access token no longer works. - $result = wp_remote_get( - 'https://api.kit.com/v4/account', - [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $tokens['access_token'], - ], - ] - ); - $data = json_decode(wp_remote_retrieve_body($result), true); - $I->assertArrayHasKey( 'errors', $data ); - $I->assertEquals( 'The access token was revoked', $data['errors'][0] ); - - // Confirm attempting to use the revoked refresh token no longer works. - $result = wp_remote_post( - 'https://api.kit.com/v4/oauth/token', - [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $tokens['access_token'], - ], - 'body' => [ - 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], - 'grant_type' => 'refresh_token', - 'refresh_token' => $tokens['refresh_token'], - ], - ] - ); - $data = json_decode(wp_remote_retrieve_body($result), true); - $I->assertArrayHasKey( 'error', $data ); - $I->assertEquals( 'invalid_grant', $data['error'] ); - } } diff --git a/tests/EndToEnd/general/uninstall/UninstallCest.php b/tests/EndToEnd/general/uninstall/UninstallCest.php new file mode 100644 index 000000000..2ae1cc343 --- /dev/null +++ b/tests/EndToEnd/general/uninstall/UninstallCest.php @@ -0,0 +1,106 @@ +activateKitPlugin($I); + + // Generate an access token and refresh token by API key and secret. + // We don't use the tokens from the environment, as revoking those + // would result in later tests failing. + $result = wp_remote_post( + 'https://api.kit.com/wordpress/accounts/oauth_access_token', + [ + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'api_key' => $_ENV['CONVERTKIT_API_KEY'], + 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], + 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + 'tenant_name' => wp_generate_password( 10, false ), // Random tenant name to produce a token for this request only. + ] + ), + ] + ); + $tokens = json_decode(wp_remote_retrieve_body($result), true)['oauth']; + + // Store the tokens and API keys in the Plugin's settings. + $I->setupKitPlugin( + $I, + [ + 'access_token' => $tokens['access_token'], + 'refresh_token' => $tokens['refresh_token'], + 'token_expires' => $tokens['expires_at'], + 'api_key' => $_ENV['CONVERTKIT_API_KEY'], + 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], + ] + ); + + // Deactivate the Plugin. + $I->deactivateKitPlugin($I); + + // Delete the Plugin. + $I->deleteKitPlugin($I); + + // Confirm the credentials have been removed from the Plugin's settings. + $I->wait(3); + $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings'); + $I->assertEmpty($settings['access_token']); + $I->assertEmpty($settings['refresh_token']); + $I->assertEmpty($settings['token_expires']); + $I->assertEmpty($settings['api_key']); + $I->assertEmpty($settings['api_secret']); + + // Confirm attempting to use the revoked access token no longer works. + $result = wp_remote_get( + 'https://api.kit.com/v4/account', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $tokens['access_token'], + ], + ] + ); + $data = json_decode(wp_remote_retrieve_body($result), true); + $I->assertArrayHasKey( 'errors', $data ); + $I->assertEquals( 'The access token was revoked', $data['errors'][0] ); + + // Confirm attempting to use the revoked refresh token no longer works. + $result = wp_remote_post( + 'https://api.kit.com/v4/oauth/token', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $tokens['access_token'], + ], + 'body' => [ + 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + 'grant_type' => 'refresh_token', + 'refresh_token' => $tokens['refresh_token'], + ], + ] + ); + $data = json_decode(wp_remote_retrieve_body($result), true); + $I->assertArrayHasKey( 'error', $data ); + $I->assertEquals( 'invalid_grant', $data['error'] ); + } +} From 941e458d27b0e9ade36d76f2ad878b4429889427 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 9 Apr 2026 12:42:43 +0800 Subject: [PATCH 15/15] Tests: Permit file changes when running the general/uninstall test suite --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d09c735c7..64faeab4f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,10 +188,10 @@ jobs: # DISALLOW_FILE_MODS = true is required to disable the block directory's "Available to Install" suggestions, which trips up # tests that search and wait for block results. - # We don't enable DISALLOW_FILE_MODS for the ActivateDeactivatePluginCest test, as tests will perform a Plugin deletion + # We don't enable DISALLOW_FILE_MODS for the UninstallCest test, as tests will perform a Plugin deletion # which requires DISALLOW_FILE_MODS to not be true. - name: Enable DISALLOW_FILE_MODS - if: ${{ matrix.test-groups != 'EndToEnd/general/other' }} + if: ${{ matrix.test-groups != 'EndToEnd/general/uninstall' }} working-directory: ${{ env.ROOT_DIR }} run: | wp-cli config set DISALLOW_FILE_MODS true --raw