From 076a20b3d4bbf010aa97257435f92332944bf5e2 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 30 Sep 2025 15:22:10 +0800 Subject: [PATCH 1/9] Tests: Exchange API Key and Secret for OAuth Tokens Exchanges a v3 API Key and Secret for OAuth tokens, as currently OAuth tokens have to be manually generated and updated in Settings > Secret and Variables > Actions every ~ 2 days, due to their expiry. --- .github/workflows/tests.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ea904eb..c8d3d4f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,21 @@ jobs: php-version: ${{ matrix.php-versions }} coverage: none + # Exchange API Keys and Secrets for OAuth Tokens. + - name: Exchange API Key and Secret for OAuth Tokens + id: get-oauth-tokens + run: | + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_API_KEY }}&api_secret=${{ env.CONVERTKIT_API_SECRET }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + access_token=$(echo "$response" | jq -r '.oauth.access_token') + refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') + echo "CONVERTKIT_OAUTH_ACCESS_TOKEN=$access_token" >> $GITHUB_ENV + echo "CONVERTKIT_OAUTH_REFRESH_TOKEN=$refresh_token" >> $GITHUB_ENV + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_API_KEY_NO_DATA }}&api_secret=${{ env.CONVERTKIT_API_SECRET_NO_DATA }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + access_token=$(echo "$response" | jq -r '.oauth.access_token') + refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') + echo "CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=$access_token" >> $GITHUB_ENV + echo "CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=$refresh_token" >> $GITHUB_ENV + # Write any secrets, such as API keys, to the .env.dist.testing file now. # Make sure your committed .env.dist.testing file ends with a newline. # The formatting of the contents to include a blank newline is deliberate. From 51861347a67276f4863e6af074cda6beb634154a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:28:14 +0800 Subject: [PATCH 2/9] Include v3 API Keys for exchange to v4 OAuth Tokens --- .env.example | 5 +++++ .github/workflows/tests.yml | 16 ++++++++-------- SETUP.md | 19 +------------------ tests/ConvertKitAPIKeyTest.php | 20 ++++++++++---------- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/.env.example b/.env.example index c138bea..2628529 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,11 @@ CONVERTKIT_OAUTH_ACCESS_TOKEN= CONVERTKIT_OAUTH_REFRESH_TOKEN= CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA= CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA= +CONVERTKIT_V4_API_KEY= +CONVERTKIT_V3_API_KEY= +CONVERTKIT_V3_API_SECRET= +CONVERTKIT_V3_API_KEY_NO_DATA= +CONVERTKIT_V3_API_SECRET_NO_DATA= CONVERTKIT_OAUTH_CLIENT_ID= CONVERTKIT_OAUTH_CLIENT_SECRET= CONVERTKIT_OAUTH_REDIRECT_URI="https://convertkit-github.local/wp-admin/options-general.php?page=_wp_convertkit_settings" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c8d3d4f..e5990b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] + php-versions: [ '8.0' ] # Steps to install, configure and run tests steps: @@ -42,12 +42,12 @@ jobs: - name: Exchange API Key and Secret for OAuth Tokens id: get-oauth-tokens run: | - response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_API_KEY }}&api_secret=${{ env.CONVERTKIT_API_SECRET }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_V3_API_KEY }}&api_secret=${{ env.CONVERTKIT_V3_API_SECRET }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") access_token=$(echo "$response" | jq -r '.oauth.access_token') refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') echo "CONVERTKIT_OAUTH_ACCESS_TOKEN=$access_token" >> $GITHUB_ENV echo "CONVERTKIT_OAUTH_REFRESH_TOKEN=$refresh_token" >> $GITHUB_ENV - response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_API_KEY_NO_DATA }}&api_secret=${{ env.CONVERTKIT_API_SECRET_NO_DATA }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_V3_API_KEY_NO_DATA }}&api_secret=${{ env.CONVERTKIT_V3_API_SECRET_NO_DATA }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") access_token=$(echo "$response" | jq -r '.oauth.access_token') refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') echo "CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=$access_token" >> $GITHUB_ENV @@ -62,11 +62,11 @@ jobs: path: .env.dist.testing contents: | - CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ secrets.CONVERTKIT_OAUTH_ACCESS_TOKEN }} - CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ secrets.CONVERTKIT_OAUTH_REFRESH_TOKEN }} - CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ secrets.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} - CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ secrets.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} - CONVERTKIT_API_KEY=${{ secrets.CONVERTKIT_API_KEY }} + CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN }} + CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN }} + CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} + CONVERTKIT_V4_API_KEY=${{ secrets.CONVERTKIT_V4_API_KEY }} CONVERTKIT_OAUTH_CLIENT_ID=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }} CONVERTKIT_OAUTH_CLIENT_SECRET=${{ secrets.CONVERTKIT_OAUTH_CLIENT_SECRET }} CONVERTKIT_OAUTH_REDIRECT_URI=${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }} diff --git a/SETUP.md b/SETUP.md index 5a36c4f..f6877bc 100644 --- a/SETUP.md +++ b/SETUP.md @@ -38,24 +38,7 @@ If you're new to this, use [GitHub Desktop](https://desktop.github.com/) or [Tow ### Configure Testing Environment -Copy the `.env.example` file to `.env` in the root of this repository, adding your ConvertKit API keys. - -``` -CONVERTKIT_API_KEY_NO_DATA= -CONVERTKIT_API_SECRET_NO_DATA= -CONVERTKIT_API_KEY= -CONVERTKIT_API_SECRET= -CONVERTKIT_API_BROADCAST_ID="8697158" -CONVERTKIT_API_FORM_ID="2765139" -CONVERTKIT_API_LEGACY_FORM_URL="https://app.convertkit.com/landing_pages/470099" -CONVERTKIT_API_LANDING_PAGE_URL="https://cheerful-architect-3237.ck.page/cc5eb21744" -CONVERTKIT_API_LEGACY_LANDING_PAGE_URL="https://app.convertkit.com/landing_pages/470103" -CONVERTKIT_API_SEQUENCE_ID="1030824" -CONVERTKIT_API_TAG_NAME="wordpress" -CONVERTKIT_API_TAG_ID="2744672" -CONVERTKIT_API_SUBSCRIBER_EMAIL="optin@n7studios.com" -CONVERTKIT_API_SUBSCRIBER_ID="1579118532" -``` +Copy the `.env.example` file to `.env` in the root of this repository, adding your Kit API keys. #### PHPStan diff --git a/tests/ConvertKitAPIKeyTest.php b/tests/ConvertKitAPIKeyTest.php index b7c4536..43dca9e 100644 --- a/tests/ConvertKitAPIKeyTest.php +++ b/tests/ConvertKitAPIKeyTest.php @@ -31,7 +31,7 @@ protected function setUp(): void // Setup API instances. $this->api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'] + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'] ); // Wait a second to avoid hitting a 429 rate limit. @@ -49,7 +49,7 @@ public function testDebugEnabled() { // Setup API with debugging enabled. $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], debug: true ); @@ -75,7 +75,7 @@ public function testDebugEnabledWithCustomLogFile() // Setup API with debugging enabled. $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], debug: true, debugLogFileLocation: $this->logFile ); @@ -101,21 +101,21 @@ public function testDebugCredentialsAndEmailsAreMasked() { // Setup API with debugging enabled. $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], debug: true ); // Create log entries with API Key and Email Address, as if an API method // were to log this sensitive data. - $this->callPrivateMethod($api, 'create_log', ['API Key: ' . $_ENV['CONVERTKIT_API_KEY']]); + $this->callPrivateMethod($api, 'create_log', ['API Key: ' . $_ENV['CONVERTKIT_V4_API_KEY']]); $this->callPrivateMethod($api, 'create_log', ['Email: ' . $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']]); // Confirm that the log includes the masked API Key and Email Address. $this->assertStringContainsString( str_repeat( '*', - (strlen($_ENV['CONVERTKIT_API_KEY']) - 4) - ) . substr($_ENV['CONVERTKIT_API_KEY'], -4), + (strlen($_ENV['CONVERTKIT_V4_API_KEY']) - 4) + ) . substr($_ENV['CONVERTKIT_V4_API_KEY'], -4), $this->getLogFileContents() ); $this->assertStringContainsString( @@ -124,7 +124,7 @@ public function testDebugCredentialsAndEmailsAreMasked() ); // Confirm that the log does not include the unmasked API Key or Email Address. - $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_KEY'], $this->getLogFileContents()); + $this->assertStringNotContainsString($_ENV['CONVERTKIT_V4_API_KEY'], $this->getLogFileContents()); $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], $this->getLogFileContents()); } @@ -158,7 +158,7 @@ public function testRequestHeadersMethod() $this->assertEquals($headers['Accept'], 'application/json'); $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_API_KEY']); + $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_V4_API_KEY']); } /** @@ -181,7 +181,7 @@ public function testRequestHeadersMethodWithType() $this->assertEquals($headers['Accept'], 'text/html'); $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_API_KEY']); + $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_V4_API_KEY']); } /** From fe03fb0ac06f0fc3efc712884a66b202a19f8248 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:41:04 +0800 Subject: [PATCH 3/9] Fix exchange --- .github/workflows/tests.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e5990b6..2dc03de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,12 +42,12 @@ jobs: - name: Exchange API Key and Secret for OAuth Tokens id: get-oauth-tokens run: | - response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_V3_API_KEY }}&api_secret=${{ env.CONVERTKIT_V3_API_SECRET }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ secrets.CONVERTKIT_V3_API_KEY }}&api_secret=${{ secrets.CONVERTKIT_V3_API_SECRET }}&client_id=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") access_token=$(echo "$response" | jq -r '.oauth.access_token') refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') echo "CONVERTKIT_OAUTH_ACCESS_TOKEN=$access_token" >> $GITHUB_ENV echo "CONVERTKIT_OAUTH_REFRESH_TOKEN=$refresh_token" >> $GITHUB_ENV - response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_V3_API_KEY_NO_DATA }}&api_secret=${{ env.CONVERTKIT_V3_API_SECRET_NO_DATA }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ secrets.CONVERTKIT_V3_API_KEY_NO_DATA }}&api_secret=${{ secrets.CONVERTKIT_V3_API_SECRET_NO_DATA }}&client_id=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") access_token=$(echo "$response" | jq -r '.oauth.access_token') refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') echo "CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=$access_token" >> $GITHUB_ENV @@ -86,7 +86,6 @@ jobs: # Run PHPStan for static analysis. - name: Run PHPStan Static Analysis - working-directory: ${{ env.PLUGIN_DIR }} run: php vendor/bin/phpstan analyse --memory-limit=1250M # Run Coding Standards. From 013814cfa69671e3065801f0619882589e92d436 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:49:04 +0800 Subject: [PATCH 4/9] Debug test failures --- tests/ConvertKitAPIKeyTest.php | 630 ------------------------------- tests/ConvertKitAPIOAuthTest.php | 5 + 2 files changed, 5 insertions(+), 630 deletions(-) delete mode 100644 tests/ConvertKitAPIKeyTest.php diff --git a/tests/ConvertKitAPIKeyTest.php b/tests/ConvertKitAPIKeyTest.php deleted file mode 100644 index 43dca9e..0000000 --- a/tests/ConvertKitAPIKeyTest.php +++ /dev/null @@ -1,630 +0,0 @@ -load(); - - // Set location where API class will create/write the log file. - $this->logFile = dirname(dirname(__FILE__)) . '/src/logs/debug.log'; - - // Delete any existing debug log file. - $this->deleteLogFile(); - - // Setup API instances. - $this->api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_V4_API_KEY'] - ); - - // Wait a second to avoid hitting a 429 rate limit. - sleep(1); - } - - /** - * Test that debug logging works when enabled and an API call is made. - * - * @since 2.2.0 - * - * @return void - */ - public function testDebugEnabled() - { - // Setup API with debugging enabled. - $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], - debug: true - ); - - $result = $api->get_account(); - - // Confirm that the log includes expected data. - $this->assertStringContainsString('ck-debug.INFO: GET account', $this->getLogFileContents()); - $this->assertStringContainsString('ck-debug.INFO: Finish request successfully', $this->getLogFileContents()); - } - - /** - * Test that debug logging works when enabled, a custom debug log file and path is specified - * and an API call is made. - * - * @since 2.2.0 - * - * @return void - */ - public function testDebugEnabledWithCustomLogFile() - { - // Define custom log file location. - $this->logFile = dirname(dirname(__FILE__)) . '/src/logs/debug-custom.log'; - - // Setup API with debugging enabled. - $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], - debug: true, - debugLogFileLocation: $this->logFile - ); - $result = $api->get_account(); - - // Confirm log file exists. - $this->assertFileExists($this->logFile); - - // Confirm that the log includes expected data. - $this->assertStringContainsString('ck-debug.INFO: GET account', $this->getLogFileContents()); - $this->assertStringContainsString('ck-debug.INFO: Finish request successfully', $this->getLogFileContents()); - } - - /** - * Test that debug logging works when enabled and an API call is made, with email addresses and credentials - * masked in the log file. - * - * @since 2.2.0 - * - * @return void - */ - public function testDebugCredentialsAndEmailsAreMasked() - { - // Setup API with debugging enabled. - $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], - debug: true - ); - - // Create log entries with API Key and Email Address, as if an API method - // were to log this sensitive data. - $this->callPrivateMethod($api, 'create_log', ['API Key: ' . $_ENV['CONVERTKIT_V4_API_KEY']]); - $this->callPrivateMethod($api, 'create_log', ['Email: ' . $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']]); - - // Confirm that the log includes the masked API Key and Email Address. - $this->assertStringContainsString( - str_repeat( - '*', - (strlen($_ENV['CONVERTKIT_V4_API_KEY']) - 4) - ) . substr($_ENV['CONVERTKIT_V4_API_KEY'], -4), - $this->getLogFileContents() - ); - $this->assertStringContainsString( - 'o****@n********.c**', - $this->getLogFileContents() - ); - - // Confirm that the log does not include the unmasked API Key or Email Address. - $this->assertStringNotContainsString($_ENV['CONVERTKIT_V4_API_KEY'], $this->getLogFileContents()); - $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], $this->getLogFileContents()); - } - - /** - * Test that debug logging is not performed when disabled and an API call is made. - * - * @since 2.2.0 - * - * @return void - */ - public function testDebugDisabled() - { - $result = $this->api->get_account(); - $this->assertEmpty($this->getLogFileContents()); - } - - /** - * Test that calling request_headers() returns the expected array of headers - * - * @since 2.2.0 - * - * @return void - */ - public function testRequestHeadersMethod() - { - $headers = $this->api->get_request_headers(); - $this->assertArrayHasKey('Accept', $headers); - $this->assertArrayHasKey('Content-Type', $headers); - $this->assertArrayHasKey('User-Agent', $headers); - $this->assertArrayHasKey('X-Kit-Api-Key', $headers); - $this->assertEquals($headers['Accept'], 'application/json'); - $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); - $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_V4_API_KEY']); - } - - /** - * Test that calling request_headers() with a different `type` parameter - * returns the expected array of headers - * - * @since 2.2.0 - * - * @return void - */ - public function testRequestHeadersMethodWithType() - { - $headers = $this->api->get_request_headers( - type: 'text/html' - ); - $this->assertArrayHasKey('Accept', $headers); - $this->assertArrayHasKey('Content-Type', $headers); - $this->assertArrayHasKey('User-Agent', $headers); - $this->assertArrayHasKey('X-Kit-Api-Key', $headers); - $this->assertEquals($headers['Accept'], 'text/html'); - $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); - $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_V4_API_KEY']); - } - - /** - * Test that calling request_headers() with the `auth` parameter set to false - * returns the expected array of headers - * - * @since 2.2.0 - * - * @return void - */ - public function testRequestHeadersMethodWithAuthDisabled() - { - $headers = $this->api->get_request_headers( - auth: false - ); - $this->assertArrayHasKey('Accept', $headers); - $this->assertArrayHasKey('Content-Type', $headers); - $this->assertArrayHasKey('User-Agent', $headers); - $this->assertArrayNotHasKey('X-Kit-Api-Key', $headers); - $this->assertEquals($headers['Accept'], 'application/json'); - $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); - $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - } - - /** - * Test that calling request_headers() with a different `type` parameter - * and the `auth` parameter set to false returns the expected array of headers - * - * @since 2.2.0 - * - * @return void - */ - public function testRequestHeadersMethodWithTypeAndAuthDisabled() - { - $headers = $this->api->get_request_headers( - type: 'text/html', - auth: false - ); - $this->assertArrayHasKey('Accept', $headers); - $this->assertArrayHasKey('Content-Type', $headers); - $this->assertArrayHasKey('User-Agent', $headers); - $this->assertArrayNotHasKey('X-Kit-Api-Key', $headers); - $this->assertEquals($headers['Accept'], 'text/html'); - $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); - $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - } - - /** - * Test that create_tags() throws a ClientException when attempting - * to create tags, as this is only supported using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateTags() - { - $this->expectException(ClientException::class); - $result = $this->api->create_tags([ - 'Tag Test ' . mt_rand(), - 'Tag Test ' . mt_rand(), - ]); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreateTags() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateTagsBlank() - { - $this->markTestSkipped('testCreateTags() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreateTags() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateTagsThatExist() - { - $this->markTestSkipped('testCreateTags() above confirms a ClientException is thrown.'); - } - - /** - * Test that tag_subscribers() throws a ClientException when attempting - * to tag subscribers, as this is only supported using OAuth. - * - * @since 2.2.1 - * - * @return void - */ - public function testTagSubscribers() - { - $this->expectException(ClientException::class); - $result = $this->api->tag_subscribers( - [ - [ - 'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'], - 'subscriber_id' => (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] - ], - ] - ); - } - - /** - * Skip this test from ConvertKitAPITest, as testTagSubscribers() above - * confirms a ClientException is thrown. - * - * @since 2.2.1 - * - * @return void - */ - public function testTagSubscribersWithInvalidTagID() - { - $this->markTestSkipped('testTagSubscribers() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testTagSubscribers() above - * confirms a ClientException is thrown. - * - * @since 2.2.1 - * - * @return void - */ - public function testTagSubscribersWithInvalidSubscriberID() - { - $this->markTestSkipped('testTagSubscribers() above confirms a ClientException is thrown.'); - } - - /** - * Test that add_subscribers_to_forms() throws a ClientException when - * attempting to add subscribers to forms, as this is only supported - * using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testAddSubscribersToForms() - { - // Create subscriber. - $emailAddress = $this->generateEmailAddress(); - $subscriber = $this->api->create_subscriber( - email_address: $emailAddress - ); - - // Set subscriber_id to ensure subscriber is unsubscribed after test. - $this->subscriber_ids[] = $subscriber->subscriber->id; - - $this->expectException(ClientException::class); - - // Add subscribers to forms. - $result = $this->api->add_subscribers_to_forms( - forms_subscribers_ids: [ - [ - 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID'], - 'subscriber_id' => $subscriber->subscriber->id, - ], - [ - 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID_2'], - 'subscriber_id' => $subscriber->subscriber->id, - ], - ] - ); - } - - /** - * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testAddSubscribersToFormsWithReferrer() - { - $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testAddSubscribersToFormsWithReferrerUTMParams() - { - $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testAddSubscribersToFormsWithInvalidFormIDs() - { - $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testAddSubscribersToFormsWithInvalidSubscriberIDs() - { - $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); - } - - /** - * Test that create_subscribers() returns a ClientException - * when attempting to create subscribers, as this is only supported - * using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateSubscribers() - { - $this->expectException(ClientException::class); - $subscribers = [ - [ - 'email_address' => str_replace('@kit.com', '-1@kit.com', $this->generateEmailAddress()), - ], - [ - 'email_address' => str_replace('@kit.com', '-2@kit.com', $this->generateEmailAddress()), - ], - ]; - $result = $this->api->create_subscribers($subscribers); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreateSubscribersWithBlankData() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateSubscribersWithBlankData() - { - $this->markTestSkipped('testCreateSubscribers() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreateSubscribersWithBlankData() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateSubscribersWithInvalidEmailAddresses() - { - $this->markTestSkipped('testCreateSubscribers() above confirms a ClientException is thrown.'); - } - - /** - * Test that create_custom_fields() throws a ClientException - * as this is only supported using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreateCustomFields() - { - $this->expectException(ClientException::class); - $labels = [ - 'Custom Field ' . mt_rand(), - 'Custom Field ' . mt_rand(), - ]; - $result = $this->api->create_custom_fields($labels); - } - - /** - * Test that get_purchases() throws a ClientException - * as this is only supported using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testGetPurchases() - { - $this->expectException(ClientException::class); - $result = $this->api->get_purchases(); - } - - /** - * Skip this test from ConvertKitAPITest, as testGetPurchases() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testGetPurchasesWithTotalCount() - { - $this->markTestSkipped('testGetPurchases() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testGetPurchases() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testGetPurchasesPagination() - { - $this->markTestSkipped('testGetPurchases() above confirms a ClientException is thrown.'); - } - - /** - * Test that get_purchases() throws a ClientException - * when a purchase ID is specified, as this is only - * supported using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testGetPurchase() - { - $this->expectException(ClientException::class); - $result = $this->api->get_purchase(12345); - } - - /** - * Skip this test from ConvertKitAPITest, as testGetPurchase() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testGetPurchaseWithInvalidID() - { - $this->markTestSkipped('testGetPurchase() above confirms a ClientException is thrown.'); - } - - /** - * Test that create_purchase() throws a ClientException - * as this is only supported using OAuth. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreatePurchase() - { - $this->expectException(ClientException::class); - $purchase = $this->api->create_purchase( - // Required fields. - email_address: $this->generateEmailAddress(), - transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), - currency: 'usd', - products: [ - [ - 'name' => 'Floppy Disk (512k)', - 'sku' => '7890-ijkl', - 'pid' => 9999, - 'lid' => 7777, - 'quantity' => 2, - 'unit_price' => 5.00, - ], - [ - 'name' => 'Telephone Cord (data)', - 'sku' => 'mnop-1234', - 'pid' => 5555, - 'lid' => 7778, - 'quantity' => 1, - 'unit_price' => 10.00, - ], - ], - // Optional fields. - first_name: 'Tim', - status: 'paid', - subtotal: 20.00, - tax: 2.00, - shipping: 2.00, - discount: 3.00, - total: 21.00, - transaction_time: new DateTime('now'), - ); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreatePurchase() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreatePurchaseWithInvalidEmailAddress() - { - $this->markTestSkipped('testCreatePurchase() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreatePurchase() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreatePurchaseWithBlankTransactionID() - { - $this->markTestSkipped('testCreatePurchase() above confirms a ClientException is thrown.'); - } - - /** - * Skip this test from ConvertKitAPITest, as testCreatePurchase() above - * confirms a ClientException is thrown. - * - * @since 2.2.0 - * - * @return void - */ - public function testCreatePurchaseWithNoProducts() - { - $this->markTestSkipped('testCreatePurchase() above confirms a ClientException is thrown.'); - } -} diff --git a/tests/ConvertKitAPIOAuthTest.php b/tests/ConvertKitAPIOAuthTest.php index 0ac662e..ec120b5 100644 --- a/tests/ConvertKitAPIOAuthTest.php +++ b/tests/ConvertKitAPIOAuthTest.php @@ -20,10 +20,15 @@ class ConvertKitAPIOAuthTest extends ConvertKitAPITest */ protected function setUp(): void { + var_dump($_ENV); + // Load environment credentials from root folder. $dotenv = Dotenv::createImmutable(dirname(dirname(__FILE__))); $dotenv->load(); + var_dump($_ENV); + die(); + // Set location where API class will create/write the log file. $this->logFile = dirname(dirname(__FILE__)) . '/src/logs/debug.log'; From 94986b9545a1b88f75a8cb66992894375ed6f401 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:51:18 +0800 Subject: [PATCH 5/9] Coding standards --- tests/ConvertKitAPIOAuthTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ConvertKitAPIOAuthTest.php b/tests/ConvertKitAPIOAuthTest.php index ec120b5..be3b661 100644 --- a/tests/ConvertKitAPIOAuthTest.php +++ b/tests/ConvertKitAPIOAuthTest.php @@ -21,7 +21,7 @@ class ConvertKitAPIOAuthTest extends ConvertKitAPITest protected function setUp(): void { var_dump($_ENV); - + // Load environment credentials from root folder. $dotenv = Dotenv::createImmutable(dirname(dirname(__FILE__))); $dotenv->load(); From a3a8eb7a79c7f111d82292743e268782ee4e7b59 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:56:25 +0800 Subject: [PATCH 6/9] Fixing tokens --- .github/workflows/tests.yml | 16 ++++++++-------- tests/ConvertKitAPIOAuthTest.php | 2 -- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2dc03de..79bab2a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,13 +45,13 @@ jobs: response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ secrets.CONVERTKIT_V3_API_KEY }}&api_secret=${{ secrets.CONVERTKIT_V3_API_SECRET }}&client_id=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") access_token=$(echo "$response" | jq -r '.oauth.access_token') refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') - echo "CONVERTKIT_OAUTH_ACCESS_TOKEN=$access_token" >> $GITHUB_ENV - echo "CONVERTKIT_OAUTH_REFRESH_TOKEN=$refresh_token" >> $GITHUB_ENV + echo "KIT_OAUTH_ACCESS_TOKEN=$access_token" >> $GITHUB_ENV + echo "KIT_OAUTH_REFRESH_TOKEN=$refresh_token" >> $GITHUB_ENV response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ secrets.CONVERTKIT_V3_API_KEY_NO_DATA }}&api_secret=${{ secrets.CONVERTKIT_V3_API_SECRET_NO_DATA }}&client_id=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") access_token=$(echo "$response" | jq -r '.oauth.access_token') refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') - echo "CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=$access_token" >> $GITHUB_ENV - echo "CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=$refresh_token" >> $GITHUB_ENV + echo "KIT_OAUTH_ACCESS_TOKEN_NO_DATA=$access_token" >> $GITHUB_ENV + echo "KIT_OAUTH_REFRESH_TOKEN_NO_DATA=$refresh_token" >> $GITHUB_ENV # Write any secrets, such as API keys, to the .env.dist.testing file now. # Make sure your committed .env.dist.testing file ends with a newline. @@ -62,10 +62,10 @@ jobs: path: .env.dist.testing contents: | - CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN }} - CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN }} - CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} - CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ env.KIT_OAUTH_ACCESS_TOKEN }} + CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ env.KIT_OAUTH_REFRESH_TOKEN }} + CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ env.KIT_OAUTH_ACCESS_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ env.KIT_OAUTH_REFRESH_TOKEN_NO_DATA }} CONVERTKIT_V4_API_KEY=${{ secrets.CONVERTKIT_V4_API_KEY }} CONVERTKIT_OAUTH_CLIENT_ID=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }} CONVERTKIT_OAUTH_CLIENT_SECRET=${{ secrets.CONVERTKIT_OAUTH_CLIENT_SECRET }} diff --git a/tests/ConvertKitAPIOAuthTest.php b/tests/ConvertKitAPIOAuthTest.php index be3b661..e274df4 100644 --- a/tests/ConvertKitAPIOAuthTest.php +++ b/tests/ConvertKitAPIOAuthTest.php @@ -20,8 +20,6 @@ class ConvertKitAPIOAuthTest extends ConvertKitAPITest */ protected function setUp(): void { - var_dump($_ENV); - // Load environment credentials from root folder. $dotenv = Dotenv::createImmutable(dirname(dirname(__FILE__))); $dotenv->load(); From 513ac33e3047b8e96e6fbada6cf19984b010d71a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:57:24 +0800 Subject: [PATCH 7/9] Remove debugging --- tests/ConvertKitAPIOAuthTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/ConvertKitAPIOAuthTest.php b/tests/ConvertKitAPIOAuthTest.php index e274df4..0ac662e 100644 --- a/tests/ConvertKitAPIOAuthTest.php +++ b/tests/ConvertKitAPIOAuthTest.php @@ -24,9 +24,6 @@ protected function setUp(): void $dotenv = Dotenv::createImmutable(dirname(dirname(__FILE__))); $dotenv->load(); - var_dump($_ENV); - die(); - // Set location where API class will create/write the log file. $this->logFile = dirname(dirname(__FILE__)) . '/src/logs/debug.log'; From 6f725e0334145873a2e33083223025fd26405aaf Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 14:58:56 +0800 Subject: [PATCH 8/9] Reinstate API Key Tests --- tests/ConvertKitAPIKeyTest.php | 630 +++++++++++++++++++++++++++++++++ 1 file changed, 630 insertions(+) create mode 100644 tests/ConvertKitAPIKeyTest.php diff --git a/tests/ConvertKitAPIKeyTest.php b/tests/ConvertKitAPIKeyTest.php new file mode 100644 index 0000000..b7c4536 --- /dev/null +++ b/tests/ConvertKitAPIKeyTest.php @@ -0,0 +1,630 @@ +load(); + + // Set location where API class will create/write the log file. + $this->logFile = dirname(dirname(__FILE__)) . '/src/logs/debug.log'; + + // Delete any existing debug log file. + $this->deleteLogFile(); + + // Setup API instances. + $this->api = new ConvertKit_API( + apiKey: $_ENV['CONVERTKIT_API_KEY'] + ); + + // Wait a second to avoid hitting a 429 rate limit. + sleep(1); + } + + /** + * Test that debug logging works when enabled and an API call is made. + * + * @since 2.2.0 + * + * @return void + */ + public function testDebugEnabled() + { + // Setup API with debugging enabled. + $api = new ConvertKit_API( + apiKey: $_ENV['CONVERTKIT_API_KEY'], + debug: true + ); + + $result = $api->get_account(); + + // Confirm that the log includes expected data. + $this->assertStringContainsString('ck-debug.INFO: GET account', $this->getLogFileContents()); + $this->assertStringContainsString('ck-debug.INFO: Finish request successfully', $this->getLogFileContents()); + } + + /** + * Test that debug logging works when enabled, a custom debug log file and path is specified + * and an API call is made. + * + * @since 2.2.0 + * + * @return void + */ + public function testDebugEnabledWithCustomLogFile() + { + // Define custom log file location. + $this->logFile = dirname(dirname(__FILE__)) . '/src/logs/debug-custom.log'; + + // Setup API with debugging enabled. + $api = new ConvertKit_API( + apiKey: $_ENV['CONVERTKIT_API_KEY'], + debug: true, + debugLogFileLocation: $this->logFile + ); + $result = $api->get_account(); + + // Confirm log file exists. + $this->assertFileExists($this->logFile); + + // Confirm that the log includes expected data. + $this->assertStringContainsString('ck-debug.INFO: GET account', $this->getLogFileContents()); + $this->assertStringContainsString('ck-debug.INFO: Finish request successfully', $this->getLogFileContents()); + } + + /** + * Test that debug logging works when enabled and an API call is made, with email addresses and credentials + * masked in the log file. + * + * @since 2.2.0 + * + * @return void + */ + public function testDebugCredentialsAndEmailsAreMasked() + { + // Setup API with debugging enabled. + $api = new ConvertKit_API( + apiKey: $_ENV['CONVERTKIT_API_KEY'], + debug: true + ); + + // Create log entries with API Key and Email Address, as if an API method + // were to log this sensitive data. + $this->callPrivateMethod($api, 'create_log', ['API Key: ' . $_ENV['CONVERTKIT_API_KEY']]); + $this->callPrivateMethod($api, 'create_log', ['Email: ' . $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']]); + + // Confirm that the log includes the masked API Key and Email Address. + $this->assertStringContainsString( + str_repeat( + '*', + (strlen($_ENV['CONVERTKIT_API_KEY']) - 4) + ) . substr($_ENV['CONVERTKIT_API_KEY'], -4), + $this->getLogFileContents() + ); + $this->assertStringContainsString( + 'o****@n********.c**', + $this->getLogFileContents() + ); + + // Confirm that the log does not include the unmasked API Key or Email Address. + $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_KEY'], $this->getLogFileContents()); + $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], $this->getLogFileContents()); + } + + /** + * Test that debug logging is not performed when disabled and an API call is made. + * + * @since 2.2.0 + * + * @return void + */ + public function testDebugDisabled() + { + $result = $this->api->get_account(); + $this->assertEmpty($this->getLogFileContents()); + } + + /** + * Test that calling request_headers() returns the expected array of headers + * + * @since 2.2.0 + * + * @return void + */ + public function testRequestHeadersMethod() + { + $headers = $this->api->get_request_headers(); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayHasKey('X-Kit-Api-Key', $headers); + $this->assertEquals($headers['Accept'], 'application/json'); + $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_API_KEY']); + } + + /** + * Test that calling request_headers() with a different `type` parameter + * returns the expected array of headers + * + * @since 2.2.0 + * + * @return void + */ + public function testRequestHeadersMethodWithType() + { + $headers = $this->api->get_request_headers( + type: 'text/html' + ); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayHasKey('X-Kit-Api-Key', $headers); + $this->assertEquals($headers['Accept'], 'text/html'); + $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_API_KEY']); + } + + /** + * Test that calling request_headers() with the `auth` parameter set to false + * returns the expected array of headers + * + * @since 2.2.0 + * + * @return void + */ + public function testRequestHeadersMethodWithAuthDisabled() + { + $headers = $this->api->get_request_headers( + auth: false + ); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayNotHasKey('X-Kit-Api-Key', $headers); + $this->assertEquals($headers['Accept'], 'application/json'); + $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + } + + /** + * Test that calling request_headers() with a different `type` parameter + * and the `auth` parameter set to false returns the expected array of headers + * + * @since 2.2.0 + * + * @return void + */ + public function testRequestHeadersMethodWithTypeAndAuthDisabled() + { + $headers = $this->api->get_request_headers( + type: 'text/html', + auth: false + ); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayNotHasKey('X-Kit-Api-Key', $headers); + $this->assertEquals($headers['Accept'], 'text/html'); + $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + } + + /** + * Test that create_tags() throws a ClientException when attempting + * to create tags, as this is only supported using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateTags() + { + $this->expectException(ClientException::class); + $result = $this->api->create_tags([ + 'Tag Test ' . mt_rand(), + 'Tag Test ' . mt_rand(), + ]); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreateTags() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateTagsBlank() + { + $this->markTestSkipped('testCreateTags() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreateTags() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateTagsThatExist() + { + $this->markTestSkipped('testCreateTags() above confirms a ClientException is thrown.'); + } + + /** + * Test that tag_subscribers() throws a ClientException when attempting + * to tag subscribers, as this is only supported using OAuth. + * + * @since 2.2.1 + * + * @return void + */ + public function testTagSubscribers() + { + $this->expectException(ClientException::class); + $result = $this->api->tag_subscribers( + [ + [ + 'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'], + 'subscriber_id' => (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + ], + ] + ); + } + + /** + * Skip this test from ConvertKitAPITest, as testTagSubscribers() above + * confirms a ClientException is thrown. + * + * @since 2.2.1 + * + * @return void + */ + public function testTagSubscribersWithInvalidTagID() + { + $this->markTestSkipped('testTagSubscribers() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testTagSubscribers() above + * confirms a ClientException is thrown. + * + * @since 2.2.1 + * + * @return void + */ + public function testTagSubscribersWithInvalidSubscriberID() + { + $this->markTestSkipped('testTagSubscribers() above confirms a ClientException is thrown.'); + } + + /** + * Test that add_subscribers_to_forms() throws a ClientException when + * attempting to add subscribers to forms, as this is only supported + * using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testAddSubscribersToForms() + { + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $subscriber = $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $subscriber->subscriber->id; + + $this->expectException(ClientException::class); + + // Add subscribers to forms. + $result = $this->api->add_subscribers_to_forms( + forms_subscribers_ids: [ + [ + 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID'], + 'subscriber_id' => $subscriber->subscriber->id, + ], + [ + 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID_2'], + 'subscriber_id' => $subscriber->subscriber->id, + ], + ] + ); + } + + /** + * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testAddSubscribersToFormsWithReferrer() + { + $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testAddSubscribersToFormsWithReferrerUTMParams() + { + $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testAddSubscribersToFormsWithInvalidFormIDs() + { + $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testAddSubscribersToForms() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testAddSubscribersToFormsWithInvalidSubscriberIDs() + { + $this->markTestSkipped('testAddSubscribersToForms() above confirms a ClientException is thrown.'); + } + + /** + * Test that create_subscribers() returns a ClientException + * when attempting to create subscribers, as this is only supported + * using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateSubscribers() + { + $this->expectException(ClientException::class); + $subscribers = [ + [ + 'email_address' => str_replace('@kit.com', '-1@kit.com', $this->generateEmailAddress()), + ], + [ + 'email_address' => str_replace('@kit.com', '-2@kit.com', $this->generateEmailAddress()), + ], + ]; + $result = $this->api->create_subscribers($subscribers); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreateSubscribersWithBlankData() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateSubscribersWithBlankData() + { + $this->markTestSkipped('testCreateSubscribers() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreateSubscribersWithBlankData() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateSubscribersWithInvalidEmailAddresses() + { + $this->markTestSkipped('testCreateSubscribers() above confirms a ClientException is thrown.'); + } + + /** + * Test that create_custom_fields() throws a ClientException + * as this is only supported using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreateCustomFields() + { + $this->expectException(ClientException::class); + $labels = [ + 'Custom Field ' . mt_rand(), + 'Custom Field ' . mt_rand(), + ]; + $result = $this->api->create_custom_fields($labels); + } + + /** + * Test that get_purchases() throws a ClientException + * as this is only supported using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testGetPurchases() + { + $this->expectException(ClientException::class); + $result = $this->api->get_purchases(); + } + + /** + * Skip this test from ConvertKitAPITest, as testGetPurchases() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testGetPurchasesWithTotalCount() + { + $this->markTestSkipped('testGetPurchases() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testGetPurchases() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testGetPurchasesPagination() + { + $this->markTestSkipped('testGetPurchases() above confirms a ClientException is thrown.'); + } + + /** + * Test that get_purchases() throws a ClientException + * when a purchase ID is specified, as this is only + * supported using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testGetPurchase() + { + $this->expectException(ClientException::class); + $result = $this->api->get_purchase(12345); + } + + /** + * Skip this test from ConvertKitAPITest, as testGetPurchase() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testGetPurchaseWithInvalidID() + { + $this->markTestSkipped('testGetPurchase() above confirms a ClientException is thrown.'); + } + + /** + * Test that create_purchase() throws a ClientException + * as this is only supported using OAuth. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreatePurchase() + { + $this->expectException(ClientException::class); + $purchase = $this->api->create_purchase( + // Required fields. + email_address: $this->generateEmailAddress(), + transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), + currency: 'usd', + products: [ + [ + 'name' => 'Floppy Disk (512k)', + 'sku' => '7890-ijkl', + 'pid' => 9999, + 'lid' => 7777, + 'quantity' => 2, + 'unit_price' => 5.00, + ], + [ + 'name' => 'Telephone Cord (data)', + 'sku' => 'mnop-1234', + 'pid' => 5555, + 'lid' => 7778, + 'quantity' => 1, + 'unit_price' => 10.00, + ], + ], + // Optional fields. + first_name: 'Tim', + status: 'paid', + subtotal: 20.00, + tax: 2.00, + shipping: 2.00, + discount: 3.00, + total: 21.00, + transaction_time: new DateTime('now'), + ); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreatePurchase() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreatePurchaseWithInvalidEmailAddress() + { + $this->markTestSkipped('testCreatePurchase() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreatePurchase() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreatePurchaseWithBlankTransactionID() + { + $this->markTestSkipped('testCreatePurchase() above confirms a ClientException is thrown.'); + } + + /** + * Skip this test from ConvertKitAPITest, as testCreatePurchase() above + * confirms a ClientException is thrown. + * + * @since 2.2.0 + * + * @return void + */ + public function testCreatePurchaseWithNoProducts() + { + $this->markTestSkipped('testCreatePurchase() above confirms a ClientException is thrown.'); + } +} From 4d1efd38f5d742a093c1b0c150379b324a28ea51 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 16 Oct 2025 15:10:34 +0800 Subject: [PATCH 9/9] Test all PHP versions; fix API Key env --- .github/workflows/tests.yml | 2 +- tests/ConvertKitAPIKeyTest.php | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 79bab2a..65813ed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: [ '8.0' ] + php-versions: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] # Steps to install, configure and run tests steps: diff --git a/tests/ConvertKitAPIKeyTest.php b/tests/ConvertKitAPIKeyTest.php index b7c4536..43dca9e 100644 --- a/tests/ConvertKitAPIKeyTest.php +++ b/tests/ConvertKitAPIKeyTest.php @@ -31,7 +31,7 @@ protected function setUp(): void // Setup API instances. $this->api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'] + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'] ); // Wait a second to avoid hitting a 429 rate limit. @@ -49,7 +49,7 @@ public function testDebugEnabled() { // Setup API with debugging enabled. $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], debug: true ); @@ -75,7 +75,7 @@ public function testDebugEnabledWithCustomLogFile() // Setup API with debugging enabled. $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], debug: true, debugLogFileLocation: $this->logFile ); @@ -101,21 +101,21 @@ public function testDebugCredentialsAndEmailsAreMasked() { // Setup API with debugging enabled. $api = new ConvertKit_API( - apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiKey: $_ENV['CONVERTKIT_V4_API_KEY'], debug: true ); // Create log entries with API Key and Email Address, as if an API method // were to log this sensitive data. - $this->callPrivateMethod($api, 'create_log', ['API Key: ' . $_ENV['CONVERTKIT_API_KEY']]); + $this->callPrivateMethod($api, 'create_log', ['API Key: ' . $_ENV['CONVERTKIT_V4_API_KEY']]); $this->callPrivateMethod($api, 'create_log', ['Email: ' . $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']]); // Confirm that the log includes the masked API Key and Email Address. $this->assertStringContainsString( str_repeat( '*', - (strlen($_ENV['CONVERTKIT_API_KEY']) - 4) - ) . substr($_ENV['CONVERTKIT_API_KEY'], -4), + (strlen($_ENV['CONVERTKIT_V4_API_KEY']) - 4) + ) . substr($_ENV['CONVERTKIT_V4_API_KEY'], -4), $this->getLogFileContents() ); $this->assertStringContainsString( @@ -124,7 +124,7 @@ public function testDebugCredentialsAndEmailsAreMasked() ); // Confirm that the log does not include the unmasked API Key or Email Address. - $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_KEY'], $this->getLogFileContents()); + $this->assertStringNotContainsString($_ENV['CONVERTKIT_V4_API_KEY'], $this->getLogFileContents()); $this->assertStringNotContainsString($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], $this->getLogFileContents()); } @@ -158,7 +158,7 @@ public function testRequestHeadersMethod() $this->assertEquals($headers['Accept'], 'application/json'); $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_API_KEY']); + $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_V4_API_KEY']); } /** @@ -181,7 +181,7 @@ public function testRequestHeadersMethodWithType() $this->assertEquals($headers['Accept'], 'text/html'); $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); - $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_API_KEY']); + $this->assertEquals($headers['X-Kit-Api-Key'], $_ENV['CONVERTKIT_V4_API_KEY']); } /**