From 063ea9f7ce826454573d6beef0c3a7c9af303a0e Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 17 Apr 2024 16:10:17 +0100 Subject: [PATCH 1/4] Added tests --- .env.dist.testing | 1 + .env.example | 1 + tests/wpunit/APITest.php | 416 +++++++++++++++++++++++++++++---------- 3 files changed, 309 insertions(+), 109 deletions(-) diff --git a/.env.dist.testing b/.env.dist.testing index c3f4d0d..e1cc30c 100644 --- a/.env.dist.testing +++ b/.env.dist.testing @@ -16,6 +16,7 @@ TEST_TABLE_PREFIX=wp_ TEST_SITE_WP_URL=http://127.0.0.1 TEST_SITE_WP_DOMAIN=127.0.0.1 TEST_SITE_ADMIN_EMAIL=wordpress@convertkit.local +CONVERTKIT_API_BROADCAST_ID="8697158" CONVERTKIT_API_FORM_ID="2765139" CONVERTKIT_API_LEGACY_FORM_ID="470099" CONVERTKIT_API_LANDING_PAGE_ID="2765196" diff --git a/.env.example b/.env.example index 17bcab9..ed515ed 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,7 @@ CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA= CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA= CONVERTKIT_OAUTH_CLIENT_ID= CONVERTKIT_OAUTH_REDIRECT_URI= +CONVERTKIT_API_BROADCAST_ID="8697158" CONVERTKIT_API_FORM_ID="2765139" CONVERTKIT_API_LEGACY_FORM_ID="470099" CONVERTKIT_API_LANDING_PAGE_ID="2765196" diff --git a/tests/wpunit/APITest.php b/tests/wpunit/APITest.php index fbc48ab..b9cda33 100644 --- a/tests/wpunit/APITest.php +++ b/tests/wpunit/APITest.php @@ -31,6 +31,15 @@ class APITest extends \Codeception\TestCase\WPTestCase */ private $errorCode = 'convertkit_api_error'; + /** + * Broadcast IDs to delete on teardown of a test. + * + * @since 2.0.0 + * + * @var array + */ + protected $broadcast_ids = []; + /** * Performs actions before each test. * @@ -68,6 +77,11 @@ public function setUp(): void */ public function tearDown(): void { + // Delete any Broadcasts. + foreach ($this->broadcast_ids as $id) { + $this->api->delete_broadcast($id); + } + // Destroy the classes we tested. unset($this->api); unset($this->api_no_data); @@ -1314,117 +1328,268 @@ public function testUnsubscribeWithInvalidEmail() $this->assertEquals('Not Found: The entity you were trying to find doesn\'t exist', $result->get_error_message()); } - /** - * Test that broadcast_create() and broadcast_delete() works when valid parameters are specified. - * - * We do all tests in a single function, so we don't end up with unnecessary Broadcasts remaining - * on the ConvertKit account when running tests, which might impact - * other tests that expect (or do not expect) specific Broadcasts. - * - * @since 1.3.9 - */ - public function testCreateAndDeleteDraftBroadcast() - { - $this->markTestIncomplete(); - - // Create a broadcast first. - $result = $this->api->broadcast_create( - 'Test Subject', - 'Test Content', - 'Test Broadcast from WordPress Libraries', - ); - - // Confirm the Broadcast saved. - $this->assertArrayHasKey('id', $result); - $this->assertEquals('Test Subject', $result['subject']); - $this->assertEquals('Test Content', $result['content']); - $this->assertEquals('Test Broadcast from WordPress Libraries', $result['description']); - $this->assertEquals(null, $result['published_at']); - $this->assertEquals(null, $result['send_at']); - - // Delete the broadcast. - $this->api->broadcast_delete($result['id']); - } - - /** - * Test that broadcast_create() and broadcast_delete() works when valid published_at and send_at - * parameters are specified. - * - * We do all tests in a single function, so we don't end up with unnecessary Broadcasts remaining - * on the ConvertKit account when running tests, which might impact - * other tests that expect (or do not expect) specific Broadcasts. - * - * @since 1.3.9 - */ - public function testCreateAndDeletePublicBroadcastWithValidDates() - { - $this->markTestIncomplete(); - - // Create DateTime object. - $publishedAt = new \DateTime('now'); - $publishedAt->modify('+7 days'); - $sendAt = new \DateTime('now'); - $sendAt->modify('+14 days'); - - // Create a broadcast first. - $result = $this->api->broadcast_create( - 'Test Subject', - 'Test Content', - 'Test Broadcast from WordPress Libraries', - true, - $publishedAt, - $sendAt - ); - - // Confirm the Broadcast saved. - $this->assertArrayHasKey('id', $result); - $this->assertEquals('Test Subject', $result['subject']); - $this->assertEquals('Test Content', $result['content']); - $this->assertEquals('Test Broadcast from WordPress Libraries', $result['description']); - $this->assertEquals( - $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . '.000Z', - $result['published_at'] - ); - $this->assertEquals( - $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . '.000Z', - $result['send_at'] - ); - - // Delete the broadcast. - $this->api->broadcast_delete($result['id']); - } - - /** - * Test that the `broadcast_delete()` function returns a WP_Error - * when no $broadcast_id parameter is provided. - * - * @since 1.3.9 - */ - public function testDeleteBroadcastWithNoBroadcastID() - { - $this->markTestIncomplete(); - - $result = $this->api->broadcast_delete(''); - $this->assertInstanceOf(WP_Error::class, $result); - $this->assertEquals($result->get_error_code(), $this->errorCode); - $this->assertEquals('broadcast_delete(): the broadcast_id parameter is empty.', $result->get_error_message()); - } /** - * Test that the `broadcast_delete()` function returns a WP_Error - * when an invalid $broadcast_id parameter is provided. - * - * @since 1.3.9 - */ - public function testDeleteBroadcastWithInvalidBroadcastID() - { - $this->markTestIncomplete(); - - $result = $this->api->broadcast_delete(12345); - $this->assertInstanceOf(WP_Error::class, $result); - $this->assertEquals($result->get_error_code(), $this->errorCode); - $this->assertEquals('Not Found: The entity you were trying to find doesn\'t exist', $result->get_error_message()); - } + * Test that get_broadcasts() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastsPagination() + { + $result = $this->api->get_broadcasts( + per_page: 1 + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result['broadcasts']); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result['pagination']['has_previous_page']); + $this->assertTrue($result['pagination']['has_next_page']); + + // Use pagination to fetch next page. + $result = $this->api->get_broadcasts( + per_page: 1, + after_cursor: $result['pagination']['end_cursor'] + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result['broadcasts']); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result['pagination']['has_previous_page']); + $this->assertTrue($result['pagination']['has_next_page']); + + // Use pagination to fetch previous page. + $result = $this->api->get_broadcasts( + per_page: 1, + before_cursor: $result['pagination']['start_cursor'] + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result['broadcasts']); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result['pagination']['has_previous_page']); + $this->assertTrue($result['pagination']['has_next_page']); + } + + /** + * Test that create_broadcast(), update_broadcast() and delete_broadcast() works + * when specifying valid published_at and send_at values. + * + * We do all tests in a single function, so we don't end up with unnecessary Broadcasts remaining + * on the ConvertKit account when running tests, which might impact + * other tests that expect (or do not expect) specific Broadcasts. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateUpdateAndDeleteDraftBroadcast() + { + // Create a broadcast first. + $result = $this->api->create_broadcast( + subject: 'Test Subject', + content: 'Test Content', + description: 'Test Broadcast from WordPress Libraries', + ); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + // Store Broadcast ID. + $broadcastID = $result['broadcast']['id']; + + // Confirm the Broadcast saved. + $this->assertArrayHasKey('broadcast', $result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals('Test Subject', $result['broadcast']['subject']); + $this->assertEquals('Test Content', $result['broadcast']['content']); + $this->assertEquals('Test Broadcast from WordPress Libraries', $result['broadcast']['description']); + $this->assertEquals(null, $result['broadcast']['published_at']); + $this->assertEquals(null, $result['broadcast']['send_at']); + + // Update the existing broadcast. + $result = $this->api->update_broadcast( + id: $broadcastID, + subject: 'New Test Subject', + content: 'New Test Content', + description: 'New Test Broadcast from WordPress Libraries' + ); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + // Confirm the changes saved. + $this->assertArrayHasKey('broadcast', $result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals('New Test Subject', $result['broadcast']['subject']); + $this->assertEquals('New Test Content', $result['broadcast']['content']); + $this->assertEquals('New Test Broadcast from WordPress Libraries', $result['broadcast']['description']); + $this->assertEquals(null, $result['broadcast']['published_at']); + $this->assertEquals(null, $result['broadcast']['send_at']); + + // Delete Broadcast. + $result = $this->api->delete_broadcast($broadcastID); + $this->assertNotInstanceOf(WP_Error::class, $result); + } + + /** + * Test that create_broadcast() works when specifying valid published_at and send_at values. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreatePublicBroadcastWithValidDates() + { + // Create DateTime object. + $publishedAt = new DateTime('now'); + $publishedAt->modify('+7 days'); + $sendAt = new DateTime('now'); + $sendAt->modify('+14 days'); + + // Create broadcast first. + $result = $this->api->create_broadcast( + subject: 'Test Subject', + content: 'Test Content', + description: 'Test Broadcast from WordPress Libraries', + public: true, + published_at: $publishedAt, + send_at: $sendAt + ); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + // Store Broadcast ID. + $broadcastID = $result['broadcast']['id']; + + // Set broadcast_id to ensure broadcast is deleted after test. + $this->broadcast_ids[] = $broadcastID; + + // Confirm the Broadcast saved. + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals('Test Subject', $result['broadcast']['subject']); + $this->assertEquals('Test Content', $result['broadcast']['content']); + $this->assertEquals('Test Broadcast from WordPress Libraries', $result['broadcast']['description']); + $this->assertEquals( + $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . 'Z', + $result['broadcast']['published_at'] + ); + $this->assertEquals( + $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . 'Z', + $result['broadcast']['send_at'] + ); + } + + /** + * Test that get_broadcast() returns the expected data. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetBroadcast() + { + $result = $this->api->get_broadcast($_ENV['CONVERTKIT_API_BROADCAST_ID']); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals($result['broadcast']['id'], $_ENV['CONVERTKIT_API_BROADCAST_ID']); + } + + /** + * Test that get_broadcast() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastWithInvalidBroadcastID() + { + $result = $this->api->get_broadcast(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } + + /** + * Test that get_broadcast_stats() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastStats() + { + $result = $this->api->get_broadcast_stats($_ENV['CONVERTKIT_API_BROADCAST_ID']); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + $this->assertArrayHasKey('broadcast', $result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertArrayHasKey('stats', $result['broadcast']); + $this->assertEquals($result['broadcast']['stats']['recipients'], 1); + $this->assertEquals($result['broadcast']['stats']['open_rate'], 0); + $this->assertEquals($result['broadcast']['stats']['click_rate'], 0); + $this->assertEquals($result['broadcast']['stats']['unsubscribes'], 0); + $this->assertEquals($result['broadcast']['stats']['total_clicks'], 0); + } + + /** + * Test that get_broadcast_stats() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastStatsWithInvalidBroadcastID() + { + $result = $this->api->get_broadcast_stats(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } + + /** + * Test that update_broadcast() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 1.0.0 + * + * @return void + */ + public function testUpdateBroadcastWithInvalidBroadcastID() + { + $result = $this->api->update_broadcast(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } + + /** + * Test that delete_broadcast() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 1.0.0 + * + * @return void + */ + public function testDeleteBroadcastWithInvalidBroadcastID() + { + $result = $this->api->delete_broadcast(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } /** * Test that the `get_custom_fields()` function returns expected data. @@ -2291,4 +2456,37 @@ function( $response ) use ( $httpCode, $httpMessage, $body ) { // phpcs:ignore G } ); } + + /** + * Helper method to assert the given key exists as an array + * in the API response. + * + * @since 2.0.0 + * + * @param array $result API Result. + */ + private function assertDataExists($result, $key) + { + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertArrayHasKey($key, $result); + $this->assertIsArray($result[$key]); + } + + /** + * Helper method to assert pagination object exists in response. + * + * @since 2.0.0 + * + * @param array $result API Result. + */ + private function assertPaginationExists($result) + { + $this->assertArrayHasKey('pagination', $result); + $pagination = $result['pagination']; + $this->assertArrayHasKey('has_previous_page', $pagination); + $this->assertArrayHasKey('has_next_page', $pagination); + $this->assertArrayHasKey('start_cursor', $pagination); + $this->assertArrayHasKey('end_cursor', $pagination); + $this->assertArrayHasKey('per_page', $pagination); + } } From b2832a349061b7ee1e9c73bfed0a303ce41a6d97 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 17 Apr 2024 16:11:16 +0100 Subject: [PATCH 2/4] Coding standards --- tests/wpunit/APITest.php | 607 ++++++++++++++++++++------------------- 1 file changed, 304 insertions(+), 303 deletions(-) diff --git a/tests/wpunit/APITest.php b/tests/wpunit/APITest.php index b9cda33..b7f267e 100644 --- a/tests/wpunit/APITest.php +++ b/tests/wpunit/APITest.php @@ -31,14 +31,14 @@ class APITest extends \Codeception\TestCase\WPTestCase */ private $errorCode = 'convertkit_api_error'; - /** - * Broadcast IDs to delete on teardown of a test. - * - * @since 2.0.0 - * - * @var array - */ - protected $broadcast_ids = []; + /** + * Broadcast IDs to delete on teardown of a test. + * + * @since 2.0.0 + * + * @var array + */ + protected $broadcast_ids = []; /** * Performs actions before each test. @@ -78,9 +78,9 @@ public function setUp(): void public function tearDown(): void { // Delete any Broadcasts. - foreach ($this->broadcast_ids as $id) { - $this->api->delete_broadcast($id); - } + foreach ($this->broadcast_ids as $id) { + $this->api->delete_broadcast($id); + } // Destroy the classes we tested. unset($this->api); @@ -1330,266 +1330,266 @@ public function testUnsubscribeWithInvalidEmail() /** - * Test that get_broadcasts() returns the expected data - * when pagination parameters and per_page limits are specified. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetBroadcastsPagination() - { - $result = $this->api->get_broadcasts( - per_page: 1 - ); - - // Assert broadcasts and pagination exist. - $this->assertDataExists($result, 'broadcasts'); - $this->assertPaginationExists($result); - - // Assert a single broadcast was returned. - $this->assertCount(1, $result['broadcasts']); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result['pagination']['has_previous_page']); - $this->assertTrue($result['pagination']['has_next_page']); - - // Use pagination to fetch next page. - $result = $this->api->get_broadcasts( - per_page: 1, - after_cursor: $result['pagination']['end_cursor'] - ); - - // Assert broadcasts and pagination exist. - $this->assertDataExists($result, 'broadcasts'); - $this->assertPaginationExists($result); - - // Assert a single broadcast was returned. - $this->assertCount(1, $result['broadcasts']); - - // Assert has_previous_page and has_next_page are correct. - $this->assertTrue($result['pagination']['has_previous_page']); - $this->assertTrue($result['pagination']['has_next_page']); - - // Use pagination to fetch previous page. - $result = $this->api->get_broadcasts( - per_page: 1, - before_cursor: $result['pagination']['start_cursor'] - ); - - // Assert broadcasts and pagination exist. - $this->assertDataExists($result, 'broadcasts'); - $this->assertPaginationExists($result); - - // Assert a single broadcast was returned. - $this->assertCount(1, $result['broadcasts']); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result['pagination']['has_previous_page']); - $this->assertTrue($result['pagination']['has_next_page']); - } - - /** - * Test that create_broadcast(), update_broadcast() and delete_broadcast() works - * when specifying valid published_at and send_at values. - * - * We do all tests in a single function, so we don't end up with unnecessary Broadcasts remaining - * on the ConvertKit account when running tests, which might impact - * other tests that expect (or do not expect) specific Broadcasts. - * - * @since 2.0.0 - * - * @return void - */ - public function testCreateUpdateAndDeleteDraftBroadcast() - { - // Create a broadcast first. - $result = $this->api->create_broadcast( - subject: 'Test Subject', - content: 'Test Content', - description: 'Test Broadcast from WordPress Libraries', - ); - $this->assertNotInstanceOf(WP_Error::class, $result); - $this->assertIsArray($result); - - // Store Broadcast ID. - $broadcastID = $result['broadcast']['id']; - - // Confirm the Broadcast saved. - $this->assertArrayHasKey('broadcast', $result); - $this->assertArrayHasKey('id', $result['broadcast']); - $this->assertEquals('Test Subject', $result['broadcast']['subject']); - $this->assertEquals('Test Content', $result['broadcast']['content']); - $this->assertEquals('Test Broadcast from WordPress Libraries', $result['broadcast']['description']); - $this->assertEquals(null, $result['broadcast']['published_at']); - $this->assertEquals(null, $result['broadcast']['send_at']); - - // Update the existing broadcast. - $result = $this->api->update_broadcast( - id: $broadcastID, - subject: 'New Test Subject', - content: 'New Test Content', - description: 'New Test Broadcast from WordPress Libraries' - ); - $this->assertNotInstanceOf(WP_Error::class, $result); - $this->assertIsArray($result); - - // Confirm the changes saved. - $this->assertArrayHasKey('broadcast', $result); - $this->assertArrayHasKey('id', $result['broadcast']); - $this->assertEquals('New Test Subject', $result['broadcast']['subject']); - $this->assertEquals('New Test Content', $result['broadcast']['content']); - $this->assertEquals('New Test Broadcast from WordPress Libraries', $result['broadcast']['description']); - $this->assertEquals(null, $result['broadcast']['published_at']); - $this->assertEquals(null, $result['broadcast']['send_at']); - - // Delete Broadcast. - $result = $this->api->delete_broadcast($broadcastID); - $this->assertNotInstanceOf(WP_Error::class, $result); - } - - /** - * Test that create_broadcast() works when specifying valid published_at and send_at values. - * - * @since 2.0.0 - * - * @return void - */ - public function testCreatePublicBroadcastWithValidDates() - { - // Create DateTime object. - $publishedAt = new DateTime('now'); - $publishedAt->modify('+7 days'); - $sendAt = new DateTime('now'); - $sendAt->modify('+14 days'); - - // Create broadcast first. - $result = $this->api->create_broadcast( - subject: 'Test Subject', - content: 'Test Content', - description: 'Test Broadcast from WordPress Libraries', - public: true, - published_at: $publishedAt, - send_at: $sendAt - ); - $this->assertNotInstanceOf(WP_Error::class, $result); - $this->assertIsArray($result); - - // Store Broadcast ID. - $broadcastID = $result['broadcast']['id']; - - // Set broadcast_id to ensure broadcast is deleted after test. - $this->broadcast_ids[] = $broadcastID; - - // Confirm the Broadcast saved. - $this->assertArrayHasKey('id', $result['broadcast']); - $this->assertEquals('Test Subject', $result['broadcast']['subject']); - $this->assertEquals('Test Content', $result['broadcast']['content']); - $this->assertEquals('Test Broadcast from WordPress Libraries', $result['broadcast']['description']); - $this->assertEquals( - $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . 'Z', - $result['broadcast']['published_at'] - ); - $this->assertEquals( - $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . 'Z', - $result['broadcast']['send_at'] - ); - } - - /** - * Test that get_broadcast() returns the expected data. - * - * @since 1.0.0 - * - * @return void - */ - public function testGetBroadcast() - { - $result = $this->api->get_broadcast($_ENV['CONVERTKIT_API_BROADCAST_ID']); - $this->assertNotInstanceOf(WP_Error::class, $result); - $this->assertIsArray($result); - $this->assertArrayHasKey('id', $result['broadcast']); - $this->assertEquals($result['broadcast']['id'], $_ENV['CONVERTKIT_API_BROADCAST_ID']); - } - - /** - * Test that get_broadcast() throws a ClientException when an invalid - * broadcast ID is specified. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetBroadcastWithInvalidBroadcastID() - { - $result = $this->api->get_broadcast(12345); - $this->assertInstanceOf(WP_Error::class, $result); - } - - /** - * Test that get_broadcast_stats() returns the expected data. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetBroadcastStats() - { - $result = $this->api->get_broadcast_stats($_ENV['CONVERTKIT_API_BROADCAST_ID']); - $this->assertNotInstanceOf(WP_Error::class, $result); - $this->assertIsArray($result); - - $this->assertArrayHasKey('broadcast', $result); - $this->assertArrayHasKey('id', $result['broadcast']); - $this->assertArrayHasKey('stats', $result['broadcast']); - $this->assertEquals($result['broadcast']['stats']['recipients'], 1); - $this->assertEquals($result['broadcast']['stats']['open_rate'], 0); - $this->assertEquals($result['broadcast']['stats']['click_rate'], 0); - $this->assertEquals($result['broadcast']['stats']['unsubscribes'], 0); - $this->assertEquals($result['broadcast']['stats']['total_clicks'], 0); - } - - /** - * Test that get_broadcast_stats() throws a ClientException when an invalid - * broadcast ID is specified. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetBroadcastStatsWithInvalidBroadcastID() - { - $result = $this->api->get_broadcast_stats(12345); - $this->assertInstanceOf(WP_Error::class, $result); - } - - /** - * Test that update_broadcast() throws a ClientException when an invalid - * broadcast ID is specified. - * - * @since 1.0.0 - * - * @return void - */ - public function testUpdateBroadcastWithInvalidBroadcastID() - { - $result = $this->api->update_broadcast(12345); - $this->assertInstanceOf(WP_Error::class, $result); - } - - /** - * Test that delete_broadcast() throws a ClientException when an invalid - * broadcast ID is specified. - * - * @since 1.0.0 - * - * @return void - */ - public function testDeleteBroadcastWithInvalidBroadcastID() - { - $result = $this->api->delete_broadcast(12345); - $this->assertInstanceOf(WP_Error::class, $result); - } + * Test that get_broadcasts() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastsPagination() + { + $result = $this->api->get_broadcasts( + per_page: 1 + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result['broadcasts']); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result['pagination']['has_previous_page']); + $this->assertTrue($result['pagination']['has_next_page']); + + // Use pagination to fetch next page. + $result = $this->api->get_broadcasts( + per_page: 1, + after_cursor: $result['pagination']['end_cursor'] + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result['broadcasts']); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result['pagination']['has_previous_page']); + $this->assertTrue($result['pagination']['has_next_page']); + + // Use pagination to fetch previous page. + $result = $this->api->get_broadcasts( + per_page: 1, + before_cursor: $result['pagination']['start_cursor'] + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result['broadcasts']); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result['pagination']['has_previous_page']); + $this->assertTrue($result['pagination']['has_next_page']); + } + + /** + * Test that create_broadcast(), update_broadcast() and delete_broadcast() works + * when specifying valid published_at and send_at values. + * + * We do all tests in a single function, so we don't end up with unnecessary Broadcasts remaining + * on the ConvertKit account when running tests, which might impact + * other tests that expect (or do not expect) specific Broadcasts. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateUpdateAndDeleteDraftBroadcast() + { + // Create a broadcast first. + $result = $this->api->create_broadcast( + subject: 'Test Subject', + content: 'Test Content', + description: 'Test Broadcast from WordPress Libraries', + ); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + // Store Broadcast ID. + $broadcastID = $result['broadcast']['id']; + + // Confirm the Broadcast saved. + $this->assertArrayHasKey('broadcast', $result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals('Test Subject', $result['broadcast']['subject']); + $this->assertEquals('Test Content', $result['broadcast']['content']); + $this->assertEquals('Test Broadcast from WordPress Libraries', $result['broadcast']['description']); + $this->assertEquals(null, $result['broadcast']['published_at']); + $this->assertEquals(null, $result['broadcast']['send_at']); + + // Update the existing broadcast. + $result = $this->api->update_broadcast( + id: $broadcastID, + subject: 'New Test Subject', + content: 'New Test Content', + description: 'New Test Broadcast from WordPress Libraries' + ); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + // Confirm the changes saved. + $this->assertArrayHasKey('broadcast', $result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals('New Test Subject', $result['broadcast']['subject']); + $this->assertEquals('New Test Content', $result['broadcast']['content']); + $this->assertEquals('New Test Broadcast from WordPress Libraries', $result['broadcast']['description']); + $this->assertEquals(null, $result['broadcast']['published_at']); + $this->assertEquals(null, $result['broadcast']['send_at']); + + // Delete Broadcast. + $result = $this->api->delete_broadcast($broadcastID); + $this->assertNotInstanceOf(WP_Error::class, $result); + } + + /** + * Test that create_broadcast() works when specifying valid published_at and send_at values. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreatePublicBroadcastWithValidDates() + { + // Create DateTime object. + $publishedAt = new DateTime('now'); + $publishedAt->modify('+7 days'); + $sendAt = new DateTime('now'); + $sendAt->modify('+14 days'); + + // Create broadcast first. + $result = $this->api->create_broadcast( + subject: 'Test Subject', + content: 'Test Content', + description: 'Test Broadcast from WordPress Libraries', + public: true, + published_at: $publishedAt, + send_at: $sendAt + ); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + // Store Broadcast ID. + $broadcastID = $result['broadcast']['id']; + + // Set broadcast_id to ensure broadcast is deleted after test. + $this->broadcast_ids[] = $broadcastID; + + // Confirm the Broadcast saved. + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals('Test Subject', $result['broadcast']['subject']); + $this->assertEquals('Test Content', $result['broadcast']['content']); + $this->assertEquals('Test Broadcast from WordPress Libraries', $result['broadcast']['description']); + $this->assertEquals( + $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . 'Z', + $result['broadcast']['published_at'] + ); + $this->assertEquals( + $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . 'Z', + $result['broadcast']['send_at'] + ); + } + + /** + * Test that get_broadcast() returns the expected data. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetBroadcast() + { + $result = $this->api->get_broadcast($_ENV['CONVERTKIT_API_BROADCAST_ID']); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertEquals($result['broadcast']['id'], $_ENV['CONVERTKIT_API_BROADCAST_ID']); + } + + /** + * Test that get_broadcast() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastWithInvalidBroadcastID() + { + $result = $this->api->get_broadcast(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } + + /** + * Test that get_broadcast_stats() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastStats() + { + $result = $this->api->get_broadcast_stats($_ENV['CONVERTKIT_API_BROADCAST_ID']); + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertIsArray($result); + + $this->assertArrayHasKey('broadcast', $result); + $this->assertArrayHasKey('id', $result['broadcast']); + $this->assertArrayHasKey('stats', $result['broadcast']); + $this->assertEquals($result['broadcast']['stats']['recipients'], 1); + $this->assertEquals($result['broadcast']['stats']['open_rate'], 0); + $this->assertEquals($result['broadcast']['stats']['click_rate'], 0); + $this->assertEquals($result['broadcast']['stats']['unsubscribes'], 0); + $this->assertEquals($result['broadcast']['stats']['total_clicks'], 0); + } + + /** + * Test that get_broadcast_stats() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastStatsWithInvalidBroadcastID() + { + $result = $this->api->get_broadcast_stats(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } + + /** + * Test that update_broadcast() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 1.0.0 + * + * @return void + */ + public function testUpdateBroadcastWithInvalidBroadcastID() + { + $result = $this->api->update_broadcast(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } + + /** + * Test that delete_broadcast() throws a ClientException when an invalid + * broadcast ID is specified. + * + * @since 1.0.0 + * + * @return void + */ + public function testDeleteBroadcastWithInvalidBroadcastID() + { + $result = $this->api->delete_broadcast(12345); + $this->assertInstanceOf(WP_Error::class, $result); + } /** * Test that the `get_custom_fields()` function returns expected data. @@ -2457,36 +2457,37 @@ function( $response ) use ( $httpCode, $httpMessage, $body ) { // phpcs:ignore G ); } - /** - * Helper method to assert the given key exists as an array - * in the API response. - * - * @since 2.0.0 - * - * @param array $result API Result. - */ - private function assertDataExists($result, $key) - { - $this->assertNotInstanceOf(WP_Error::class, $result); - $this->assertArrayHasKey($key, $result); - $this->assertIsArray($result[$key]); - } - - /** - * Helper method to assert pagination object exists in response. - * - * @since 2.0.0 - * - * @param array $result API Result. - */ - private function assertPaginationExists($result) - { - $this->assertArrayHasKey('pagination', $result); - $pagination = $result['pagination']; - $this->assertArrayHasKey('has_previous_page', $pagination); - $this->assertArrayHasKey('has_next_page', $pagination); - $this->assertArrayHasKey('start_cursor', $pagination); - $this->assertArrayHasKey('end_cursor', $pagination); - $this->assertArrayHasKey('per_page', $pagination); - } + /** + * Helper method to assert the given key exists as an array + * in the API response. + * + * @since 2.0.0 + * + * @param array $result API Result. + * @param string $key Key. + */ + private function assertDataExists($result, $key) + { + $this->assertNotInstanceOf(WP_Error::class, $result); + $this->assertArrayHasKey($key, $result); + $this->assertIsArray($result[ $key ]); + } + + /** + * Helper method to assert pagination object exists in response. + * + * @since 2.0.0 + * + * @param array $result API Result. + */ + private function assertPaginationExists($result) + { + $this->assertArrayHasKey('pagination', $result); + $pagination = $result['pagination']; + $this->assertArrayHasKey('has_previous_page', $pagination); + $this->assertArrayHasKey('has_next_page', $pagination); + $this->assertArrayHasKey('start_cursor', $pagination); + $this->assertArrayHasKey('end_cursor', $pagination); + $this->assertArrayHasKey('per_page', $pagination); + } } From e0b3760b232feb86e8e287bce1da689469788adb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 17 Apr 2024 16:40:29 +0100 Subject: [PATCH 3/4] Use `http_build_query` instead of `add_query_arg` --- src/class-convertkit-api.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/class-convertkit-api.php b/src/class-convertkit-api.php index 14965ca..f8e7e8b 100644 --- a/src/class-convertkit-api.php +++ b/src/class-convertkit-api.php @@ -1018,8 +1018,12 @@ public function request( $endpoint, $method = 'get', $params = array(), $retry_i // Send request. switch ( strtolower( $method ) ) { case 'get': + // We deliberate don't use add_query_arg(), as this converts double equal signs (typically + // provided by `start_cursor` and `end_cursor`) to a single equal sign, therefore breaking + // pagination. http_build_query() will encode equals signs instead, preserving them + // and ensuring paginated requests work correctly. $result = wp_remote_get( - add_query_arg( $params, $this->get_api_url( $endpoint ) ), + $this->get_api_url( $endpoint ) . '?' . http_build_query( $params ), array( 'headers' => $this->get_request_headers(), 'timeout' => $this->get_timeout(), From 7f877c9968f4681b9ed45a3b8c8fd4a9d00f659f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 17 Apr 2024 16:56:19 +0100 Subject: [PATCH 4/4] Remove named arguments for PHP 7.4 compat. --- tests/wpunit/APITest.php | 41 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/tests/wpunit/APITest.php b/tests/wpunit/APITest.php index b7f267e..089b4e5 100644 --- a/tests/wpunit/APITest.php +++ b/tests/wpunit/APITest.php @@ -1339,9 +1339,8 @@ public function testUnsubscribeWithInvalidEmail() */ public function testGetBroadcastsPagination() { - $result = $this->api->get_broadcasts( - per_page: 1 - ); + // Return one broadcast. + $result = $this->api->get_broadcasts(false, '', '', 1); // Assert broadcasts and pagination exist. $this->assertDataExists($result, 'broadcasts'); @@ -1355,10 +1354,7 @@ public function testGetBroadcastsPagination() $this->assertTrue($result['pagination']['has_next_page']); // Use pagination to fetch next page. - $result = $this->api->get_broadcasts( - per_page: 1, - after_cursor: $result['pagination']['end_cursor'] - ); + $result = $this->api->get_broadcasts(false, $result['pagination']['end_cursor'], '', 1); // Assert broadcasts and pagination exist. $this->assertDataExists($result, 'broadcasts'); @@ -1372,10 +1368,7 @@ public function testGetBroadcastsPagination() $this->assertTrue($result['pagination']['has_next_page']); // Use pagination to fetch previous page. - $result = $this->api->get_broadcasts( - per_page: 1, - before_cursor: $result['pagination']['start_cursor'] - ); + $result = $this->api->get_broadcasts(false, '', $result['pagination']['start_cursor'], 1); // Assert broadcasts and pagination exist. $this->assertDataExists($result, 'broadcasts'); @@ -1405,9 +1398,9 @@ public function testCreateUpdateAndDeleteDraftBroadcast() { // Create a broadcast first. $result = $this->api->create_broadcast( - subject: 'Test Subject', - content: 'Test Content', - description: 'Test Broadcast from WordPress Libraries', + 'Test Subject', + 'Test Content', + 'Test Broadcast from WordPress Libraries', ); $this->assertNotInstanceOf(WP_Error::class, $result); $this->assertIsArray($result); @@ -1426,10 +1419,10 @@ public function testCreateUpdateAndDeleteDraftBroadcast() // Update the existing broadcast. $result = $this->api->update_broadcast( - id: $broadcastID, - subject: 'New Test Subject', - content: 'New Test Content', - description: 'New Test Broadcast from WordPress Libraries' + $broadcastID, + 'New Test Subject', + 'New Test Content', + 'New Test Broadcast from WordPress Libraries' ); $this->assertNotInstanceOf(WP_Error::class, $result); $this->assertIsArray($result); @@ -1465,12 +1458,12 @@ public function testCreatePublicBroadcastWithValidDates() // Create broadcast first. $result = $this->api->create_broadcast( - subject: 'Test Subject', - content: 'Test Content', - description: 'Test Broadcast from WordPress Libraries', - public: true, - published_at: $publishedAt, - send_at: $sendAt + 'Test Subject', + 'Test Content', + 'Test Broadcast from WordPress Libraries', + true, + $publishedAt, + $sendAt ); $this->assertNotInstanceOf(WP_Error::class, $result); $this->assertIsArray($result);