From ad98a68a067475e543d2712ba00529471df91201 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 8 Sep 2026 13:59:51 +0800 Subject: [PATCH 1/2] Tests: Unsubscribe: Wait for subscriber --- src/ConvertKit_API_Traits.php | 24 +++++++++---- tests/TestsTrait.php | 65 +++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php index 471c1b8..10a5f8f 100644 --- a/src/ConvertKit_API_Traits.php +++ b/src/ConvertKit_API_Traits.php @@ -1725,6 +1725,7 @@ public function filter_subscribers( * Return false if subscriber not found. * * @param string $email_address Email Address. + * @param string $status Subscriber status (active|bounced|cancelled|complained|inactive|all). * * @throws \InvalidArgumentException If the email address is not a valid email format. * @@ -1732,11 +1733,14 @@ public function filter_subscribers( * * @return false|integer */ - public function get_subscriber_id(string $email_address) + public function get_subscriber_id(string $email_address, string $status = 'active') { $subscribers = $this->get( 'subscribers', - ['email_address' => $email_address] + [ + 'email_address' => $email_address, + 'status' => $status, + ] ); if (!$subscribers instanceof \stdClass) { @@ -1820,17 +1824,25 @@ public function update_subscriber( * * @param string $email_address Email Address. * + * @throws \InvalidArgumentException If no subscriber exists with the given email address. + * * @see https://developers.kit.com/api-reference/subscribers/unsubscribe-subscriber * * @return mixed|object */ public function unsubscribe_by_email(string $email_address) { + $subscriber_id = $this->get_subscriber_id($email_address); + + // Bail if no subscriber found, otherwise a malformed endpoint is requested. + if (!is_int($subscriber_id)) { + throw new \InvalidArgumentException( + sprintf('No subscriber found with the email address %s', $email_address) + ); + } + return $this->post( - sprintf( - 'subscribers/%s/unsubscribe', - $this->get_subscriber_id($email_address) - ) + sprintf('subscribers/%s/unsubscribe', $subscriber_id) ); } diff --git a/tests/TestsTrait.php b/tests/TestsTrait.php index 0f14ad0..227932e 100644 --- a/tests/TestsTrait.php +++ b/tests/TestsTrait.php @@ -4925,8 +4925,8 @@ public function testUnsubscribeByEmail() email_address: $emailAddress ); - // Wait a moment to ensure subscriber is created. - sleep(3); + // Wait until the subscriber can be found by their email address. + $this->waitForSubscriber($emailAddress); // Unsubscribe. $this->assertNull($this->api->unsubscribe_by_email($emailAddress)); @@ -6944,6 +6944,67 @@ public function generateEmailAddress($domain = 'kit.com') return 'php-sdk-' . date('Y-m-d-H-i-s') . '-php-' . PHP_VERSION_ID . '@' . $domain; } + /** + * Repeatedly invokes the given callback until it returns a truthy value, or the + * maximum number of attempts is reached. + * + * Use this to wrap API checks that can be flaky due to eventual consistency at Kit's + * end. List endpoints typically reflect a write within ~30 seconds, and can take up + * to 5 minutes, so reading back immediately after a write is not reliable. + * + * @since 2.7.0 + * + * @see https://developers.kit.com/api-reference/eventual-consistency + * + * @param callable $callback Callback to invoke. Should return the value to use, or + * false / null when the check has not yet succeeded. + * @param integer $attempts Maximum number of attempts. + * @param integer $delay Seconds to wait between attempts. + * @return mixed Value returned by the callback, or false if all attempts are exhausted. + */ + public function retryUntil(callable $callback, $attempts = 20, $delay = 5) + { + for ($i = 0; $i < $attempts; $i++) { + $result = $callback(); + + if ($result) { + return $result; + } + + // Don't sleep after the final attempt. + if ($i < ($attempts - 1)) { + sleep($delay); + } + } + + return false; + } + + /** + * Waits for the given email address to be queryable by get_subscriber_id(), returning + * the Subscriber ID, and failing the test if it never becomes queryable. + * + * @since 2.7.0 + * + * @param string $emailAddress Email Address. + * @return integer Subscriber ID. + */ + public function waitForSubscriber($emailAddress) + { + $subscriberID = $this->retryUntil( + function () use ($emailAddress) { + return $this->api->get_subscriber_id($emailAddress); + } + ); + + $this->assertNotFalse( + $subscriberID, + sprintf('Subscriber %s was not returned by the API in time.', $emailAddress) + ); + + return $subscriberID; + } + /** * Checks if string is html. * From bc14a1b0f376ea61c1ca35300f912ddf4c055b54 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 8 Sep 2026 14:40:36 +0800 Subject: [PATCH 2/2] Revert trait changes --- src/ConvertKit_API_Traits.php | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php index 10a5f8f..471c1b8 100644 --- a/src/ConvertKit_API_Traits.php +++ b/src/ConvertKit_API_Traits.php @@ -1725,7 +1725,6 @@ public function filter_subscribers( * Return false if subscriber not found. * * @param string $email_address Email Address. - * @param string $status Subscriber status (active|bounced|cancelled|complained|inactive|all). * * @throws \InvalidArgumentException If the email address is not a valid email format. * @@ -1733,14 +1732,11 @@ public function filter_subscribers( * * @return false|integer */ - public function get_subscriber_id(string $email_address, string $status = 'active') + public function get_subscriber_id(string $email_address) { $subscribers = $this->get( 'subscribers', - [ - 'email_address' => $email_address, - 'status' => $status, - ] + ['email_address' => $email_address] ); if (!$subscribers instanceof \stdClass) { @@ -1824,25 +1820,17 @@ public function update_subscriber( * * @param string $email_address Email Address. * - * @throws \InvalidArgumentException If no subscriber exists with the given email address. - * * @see https://developers.kit.com/api-reference/subscribers/unsubscribe-subscriber * * @return mixed|object */ public function unsubscribe_by_email(string $email_address) { - $subscriber_id = $this->get_subscriber_id($email_address); - - // Bail if no subscriber found, otherwise a malformed endpoint is requested. - if (!is_int($subscriber_id)) { - throw new \InvalidArgumentException( - sprintf('No subscriber found with the email address %s', $email_address) - ); - } - return $this->post( - sprintf('subscribers/%s/unsubscribe', $subscriber_id) + sprintf( + 'subscribers/%s/unsubscribe', + $this->get_subscriber_id($email_address) + ) ); }