diff --git a/.env.dist.testing b/.env.dist.testing
index d4e141b..8d7fb74 100644
--- a/.env.dist.testing
+++ b/.env.dist.testing
@@ -35,7 +35,9 @@ TEST_SITE_HTTP_USER_AGENT_MOBILE=HeadlessChromeMobile
CONVERTKIT_API_BROADCAST_ID="8697158"
CONVERTKIT_API_CUSTOM_FIELD_ID="258240"
CONVERTKIT_API_FORM_ID="2765139"
+CONVERTKIT_API_FORM_ID_2="2780977"
CONVERTKIT_API_LEGACY_FORM_ID="470099"
+CONVERTKIT_API_LEGACY_FORM_URL="https://app.convertkit.com/landing_pages/470099"
CONVERTKIT_API_LANDING_PAGE_ID="2765196"
CONVERTKIT_API_LANDING_PAGE_URL="https://cheerful-architect-3237.kit.com/99f1db6843"
CONVERTKIT_API_LANDING_PAGE_CHARACTER_ENCODING_ID="2849151"
@@ -45,10 +47,13 @@ CONVERTKIT_API_LEGACY_LANDING_PAGE_URL="https://app.kit.com/landing_pages/470103
CONVERTKIT_API_POST_ID="3175837"
CONVERTKIT_API_PRODUCT_ID="36377"
CONVERTKIT_API_SEQUENCE_ID="1030824"
+CONVERTKIT_API_SEQUENCE_EMAIL_ID="4533458"
CONVERTKIT_API_TAG_NAME="wordpress"
CONVERTKIT_API_TAG_ID="2744672"
CONVERTKIT_API_TAG_NAME_2="gravityforms-tag-1"
CONVERTKIT_API_TAG_ID_2="2907192"
CONVERTKIT_API_SUBSCRIBER_EMAIL="optin@n7studios.com"
CONVERTKIT_API_SUBSCRIBER_ID="1579118532"
+CONVERTKIT_API_EMAIL_TEMPLATE_ID="5215567"
+CONVERTKIT_API_SNIPPET_ID="136038"
CONVERTKIT_API_RECOMMENDATIONS_JS="https://cheerful-architect-3237.kit.com/WnaDZ370gtgOq750dwOl-recommendations.js"
diff --git a/.env.example b/.env.example
index faf05a5..e8bd896 100644
--- a/.env.example
+++ b/.env.example
@@ -43,7 +43,9 @@ CONVERTKIT_OAUTH_REDIRECT_URI=
CONVERTKIT_API_BROADCAST_ID="8697158"
CONVERTKIT_API_CUSTOM_FIELD_ID="258240"
CONVERTKIT_API_FORM_ID="2765139"
+CONVERTKIT_API_FORM_ID_2="2780977"
CONVERTKIT_API_LEGACY_FORM_ID="470099"
+CONVERTKIT_API_LEGACY_FORM_URL="https://app.convertkit.com/landing_pages/470099"
CONVERTKIT_API_LANDING_PAGE_ID="2765196"
CONVERTKIT_API_LANDING_PAGE_URL="https://cheerful-architect-3237.kit.com/99f1db6843"
CONVERTKIT_API_LANDING_PAGE_CHARACTER_ENCODING_ID="2849151"
@@ -53,12 +55,15 @@ CONVERTKIT_API_LEGACY_LANDING_PAGE_URL="https://app.kit.com/landing_pages/470103
CONVERTKIT_API_POST_ID="3175837"
CONVERTKIT_API_PRODUCT_ID="36377"
CONVERTKIT_API_SEQUENCE_ID="1030824"
+CONVERTKIT_API_SEQUENCE_EMAIL_ID="4533458"
CONVERTKIT_API_TAG_NAME="wordpress"
CONVERTKIT_API_TAG_ID="2744672"
CONVERTKIT_API_TAG_NAME_2="gravityforms-tag-1"
CONVERTKIT_API_TAG_ID_2="2907192"
CONVERTKIT_API_SUBSCRIBER_EMAIL="optin@n7studios.com"
CONVERTKIT_API_SUBSCRIBER_ID="1579118532"
+CONVERTKIT_API_EMAIL_TEMPLATE_ID="5215567"
+CONVERTKIT_API_SNIPPET_ID="136038"
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID=
CONVERTKIT_API_SUBSCRIBER_TOKEN=
CONVERTKIT_API_RECOMMENDATIONS_JS="https://cheerful-architect-3237.kit.com/WnaDZ370gtgOq750dwOl-recommendations.js"
diff --git a/phpcs.tests.xml b/phpcs.tests.xml
index 881d54b..5763df5 100644
--- a/phpcs.tests.xml
+++ b/phpcs.tests.xml
@@ -5,6 +5,13 @@
tests
+
+ tests/Integration/TestsTrait.php
+
@@ -37,6 +44,9 @@
+
+
+
diff --git a/src/class-convertkit-api-traits.php b/src/class-convertkit-api-traits.php
index 1244373..7d564be 100644
--- a/src/class-convertkit-api-traits.php
+++ b/src/class-convertkit-api-traits.php
@@ -156,11 +156,12 @@ public function get_growth_stats(?\DateTime $starting = null, ?\DateTime $ending
/**
* List forms.
*
- * @param string $status Form status (active|archived|trashed|all).
- * @param boolean $include_total_count To include the total count of records in the response, use true.
- * @param string $after_cursor Return results after the given pagination cursor.
- * @param string $before_cursor Return results before the given pagination cursor.
- * @param integer $per_page Number of results to return.
+ * @param string $status Form status (active|archived|trashed|all).
+ * @param array $include Additional fields to include: subscriber_count.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
*
* @see https://developers.kit.com/api-reference/forms/list-forms
*
@@ -168,18 +169,26 @@ public function get_growth_stats(?\DateTime $starting = null, ?\DateTime $ending
*/
public function get_forms(
string $status = 'active',
+ array $include = [],
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
+ // Build parameters.
+ $options = [
+ 'type' => 'embed',
+ 'status' => $status,
+ ];
+
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
+
return $this->get(
'forms',
$this->build_total_count_and_pagination_params(
- [
- 'type' => 'embed',
- 'status' => $status,
- ],
+ $options,
$include_total_count,
$after_cursor,
$before_cursor,
@@ -330,6 +339,7 @@ public function add_subscriber_to_legacy_form(int $form_id, int $subscriber_id)
* @param \DateTime|null $created_before Filter subscribers who have been created before this date.
* @param \DateTime|null $added_after Filter subscribers who have been added to the form after this date.
* @param \DateTime|null $added_before Filter subscribers who have been added to the form before this date.
+ * @param boolean $slim When true, omits expensive optional fields from the response.
* @param boolean $include_total_count To include the total count of records in the response, use true.
* @param string $after_cursor Return results after the given pagination cursor.
* @param string $before_cursor Return results before the given pagination cursor.
@@ -346,13 +356,14 @@ public function get_form_subscriptions(
?\DateTime $created_before = null,
?\DateTime $added_after = null,
?\DateTime $added_before = null,
+ bool $slim = false,
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
// Build parameters.
- $options = [];
+ $options = ['slim' => $slim];
if (!empty($subscriber_state)) {
$options['status'] = $subscriber_state;
@@ -386,25 +397,34 @@ public function get_form_subscriptions(
/**
* List sequences
*
- * @param boolean $include_total_count To include the total count of records in the response, use true.
- * @param string $after_cursor Return results after the given pagination cursor.
- * @param string $before_cursor Return results before the given pagination cursor.
- * @param integer $per_page Number of results to return.
+ * @param array $include Additional fields to include: stats.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
*
* @see https://developers.kit.com/api-reference/sequences/list-sequences
*
* @return false|mixed
*/
public function get_sequences(
+ array $include = [],
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
+ // Build parameters.
+ $options = [];
+
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
+
return $this->get(
'sequences',
$this->build_total_count_and_pagination_params(
- [],
+ $options,
$include_total_count,
$after_cursor,
$before_cursor,
@@ -413,6 +433,166 @@ public function get_sequences(
);
}
+ /**
+ * Create a sequence
+ *
+ * @param string $name The name of the sequence.
+ * @param string $email_address The sending email address to use. Uses the account's sending email address if not provided.
+ * @param integer $email_template_id Id of the email template to use.
+ * @param array $send_days The days of the week to send the sequence on. Must be one of: `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, `saturday`, `sunday`.
+ * @param integer $send_hour The hour of the day to send the sequence at. Must be an integer between 0 and 23.
+ * @param string $time_zone The timezone to use for the sequence. Must be a valid IANA timezone string.
+ * @param boolean $active Use `true` to activate the sequence, `false` to deactivate it.
+ * @param boolean $repeat When `true`, subscribers can restart the sequence multiple times.
+ * @param boolean $hold When `true`, subscribers added via Visual Automations stay in the sequence after receiving the last email.
+ * @param array> $exclude_subscriber_sources The subscriber sources to exclude from the sequence. Uses the account's default exclude subscriber sources if not provided.
+ *
+ * @see https://developers.kit.com/api-reference/sequences/create-a-sequence
+ *
+ * @return mixed|object
+ */
+ public function create_sequence(
+ string $name,
+ string $email_address = '',
+ int $email_template_id = 0,
+ array $send_days = [],
+ int $send_hour = 0,
+ string $time_zone = '',
+ bool $active = true,
+ bool $repeat = false,
+ bool $hold = false,
+ array $exclude_subscriber_sources = []
+ ) {
+ $options = [
+ 'name' => $name,
+ 'email_address' => $email_address,
+ 'email_template_id' => $email_template_id,
+ 'send_hour' => $send_hour,
+ 'time_zone' => $time_zone,
+ 'active' => $active,
+ 'repeat' => $repeat,
+ 'hold' => $hold,
+ ];
+ if (count($send_days)) {
+ $options['send_days'] = $send_days;
+ }
+ if (count($exclude_subscriber_sources)) {
+ $options['exclude_subscriber_sources'] = $exclude_subscriber_sources;
+ }
+
+ // Iterate through options, removing blank entries.
+ foreach ($options as $key => $value) {
+ if (is_string($value) && strlen($value) === 0) {
+ unset($options[$key]);
+ }
+ }
+
+ // Send request.
+ return $this->post(
+ 'sequences',
+ $options
+ );
+ }
+
+ /**
+ * Get a sequence.
+ *
+ * @param integer $id Sequence ID.
+ * @param array $include Additional fields to include: stats.
+ *
+ * @see https://developers.kit.com/api-reference/sequences/get-a-sequence
+ *
+ * @return mixed|object
+ */
+ public function get_sequence(
+ int $id,
+ array $include = []
+ ) {
+ // Build parameters.
+ $options = [];
+
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
+
+ return $this->get(sprintf('sequences/%s', $id), $options);
+ }
+
+ /**
+ * Updates a sequence
+ *
+ * @param integer $sequence_id Sequence ID.
+ * @param string $name The name of the sequence.
+ * @param string $email_address The sending email address to use. Uses the account's sending email address if not provided.
+ * @param integer $email_template_id Id of the email template to use.
+ * @param array $send_days The days of the week to send the sequence on. Must be one of: `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, `saturday`, `sunday`.
+ * @param integer $send_hour The hour of the day to send the sequence at. Must be an integer between 0 and 23.
+ * @param string $time_zone The timezone to use for the sequence. Must be a valid IANA timezone string.
+ * @param boolean $active Use `true` to activate the sequence, `false` to deactivate it.
+ * @param boolean $repeat When `true`, subscribers can restart the sequence multiple times.
+ * @param boolean $hold When `true`, subscribers added via Visual Automations stay in the sequence after receiving the last email.
+ * @param array> $exclude_subscriber_sources The subscriber sources to exclude from the sequence. Uses the account's default exclude subscriber sources if not provided.
+ *
+ * @see https://developers.kit.com/api-reference/sequences/create-a-sequence
+ *
+ * @return mixed|object
+ */
+ public function update_sequence(
+ int $sequence_id,
+ string $name = '',
+ string $email_address = '',
+ int $email_template_id = 0,
+ array $send_days = [],
+ int $send_hour = 0,
+ string $time_zone = '',
+ bool $active = true,
+ bool $repeat = false,
+ bool $hold = false,
+ array $exclude_subscriber_sources = []
+ ) {
+ $options = [
+ 'name' => $name,
+ 'email_address' => $email_address,
+ 'email_template_id' => $email_template_id,
+ 'send_days' => $send_days,
+ 'send_hour' => $send_hour,
+ 'time_zone' => $time_zone,
+ 'active' => $active,
+ 'repeat' => $repeat,
+ 'hold' => $hold,
+ ];
+ if (count($exclude_subscriber_sources)) {
+ $options['exclude_subscriber_sources'] = $exclude_subscriber_sources;
+ }
+
+ // Iterate through options, removing blank entries.
+ foreach ($options as $key => $value) {
+ if (is_string($value) && strlen($value) === 0) {
+ unset($options[$key]);
+ }
+ }
+
+ // Send request.
+ return $this->put(
+ sprintf('sequences/%s', $sequence_id),
+ $options
+ );
+ }
+
+ /**
+ * Deletes a sequence.
+ *
+ * @param integer $id Sequence ID.
+ *
+ * @see https://developers.kit.com/api-reference/sequences/delete-a-sequence
+ *
+ * @return mixed|object
+ */
+ public function delete_sequence(int $id)
+ {
+ return $this->delete(sprintf('sequences/%s', $id));
+ }
+
/**
* Adds subscriber to sequence by email address
*
@@ -510,13 +690,371 @@ public function get_sequence_subscriptions(
);
}
+ /**
+ * List sequence emails
+ *
+ * @param integer $sequence_id Sequence ID.
+ * @param array $include Additional fields to include: stats.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
+ *
+ * @see https://developers.kit.com/api-reference/sequence-emails/list-sequence-emails
+ *
+ * @return false|mixed
+ */
+ public function get_sequence_emails(
+ int $sequence_id,
+ array $include = [],
+ bool $include_total_count = false,
+ string $after_cursor = '',
+ string $before_cursor = '',
+ int $per_page = 100
+ ) {
+ // Build parameters.
+ $options = [];
+
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
+
+ return $this->get(
+ sprintf('sequences/%s/emails', $sequence_id),
+ $this->build_total_count_and_pagination_params(
+ $options,
+ $include_total_count,
+ $after_cursor,
+ $before_cursor,
+ $per_page
+ )
+ );
+ }
+
+ /**
+ * Create a sequence email
+ *
+ * @param integer $sequence_id Sequence ID.
+ * @param string $subject Subject line of the email.
+ * @param integer $delay_value Number of days or hours to wait before sending this email after the previous one.
+ * @param string $delay_unit Unit for the send delay. Use `days` for schedule-aware delivery, `hours` for a fixed hourly delay.
+ * @param string|null $preview_text Preview text shown in email clients before the email is opened.
+ * @param string|null $content HTML body content of the email.
+ * @param integer|null $email_template_id ID of the email template to use for layout and styling.
+ * @param boolean $published Whether the email is active and will be sent to subscribers.
+ * @param array|null $send_days Days of the week this email may be sent. Defaults to all 7 days (inherits the sequence schedule). Pass a subset to restrict delivery, or null to reset to all days.
+ * @param integer|null $position Zero-based position of the email in the sequence. Assigned automatically after the last email if omitted.
+ *
+ * @see https://developers.kit.com/api-reference/sequence-emails/create-a-sequence-email
+ *
+ * @return mixed|object
+ */
+ public function create_sequence_email(
+ int $sequence_id,
+ string $subject,
+ int $delay_value,
+ string $delay_unit,
+ ?string $preview_text = null,
+ ?string $content = null,
+ ?int $email_template_id = null,
+ bool $published = false,
+ ?array $send_days = null,
+ ?int $position = null
+ ) {
+ $options = [
+ 'subject' => $subject,
+ 'delay_value' => $delay_value,
+ 'delay_unit' => $delay_unit,
+ 'published' => $published,
+ 'send_days' => $send_days,
+ ];
+
+ if (!empty($preview_text)) {
+ $options['preview_text'] = $preview_text;
+ }
+ if (!empty($content)) {
+ $options['content'] = $content;
+ }
+ if (!empty($email_template_id)) {
+ $options['email_template_id'] = $email_template_id;
+ }
+ if (!empty($position)) {
+ $options['position'] = $position;
+ }
+
+ // Send request.
+ return $this->post(
+ sprintf('sequences/%s/emails', $sequence_id),
+ $options
+ );
+ }
+
+ /**
+ * Get a sequence email.
+ *
+ * @param integer $sequence_id Sequence ID.
+ * @param integer $email_id Email ID.
+ * @param array $include Additional fields to include: stats.
+ *
+ * @see https://developers.kit.com/api-reference/sequence-emails/get-a-sequence-email
+ *
+ * @return mixed|object
+ */
+ public function get_sequence_email(
+ int $sequence_id,
+ int $email_id,
+ array $include = []
+ ) {
+ // Build parameters.
+ $options = [];
+
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
+
+ return $this->get(sprintf('sequences/%s/emails/%s', $sequence_id, $email_id), $options);
+ }
+
+ /**
+ * Updates a sequence
+ *
+ * @param integer $sequence_id Sequence ID.
+ * @param integer $email_id Sequence Email ID.
+ * @param string|null $subject Subject line of the email.
+ * @param integer|null $delay_value Number of days or hours to wait before sending this email after the previous one.
+ * @param string|null $delay_unit Unit for the send delay. Use `days` for schedule-aware delivery, `hours` for a fixed hourly delay.
+ * @param string|null $preview_text Preview text shown in email clients before the email is opened.
+ * @param string|null $content HTML body content of the email.
+ * @param integer|null $email_template_id ID of the email template to use for layout and styling.
+ * @param boolean|null $published Whether the email is active and will be sent to subscribers.
+ * @param array|null $send_days Days of the week this email may be sent. Defaults to all 7 days (inherits the sequence schedule). Pass a subset to restrict delivery, or null to reset to all days.
+ * @param integer|null $position Zero-based position of the email in the sequence. Assigned automatically after the last email if omitted.
+ *
+ * @see https://developers.kit.com/api-reference/sequences/create-a-sequence
+ *
+ * @return mixed|object
+ */
+ public function update_sequence_email(
+ int $sequence_id,
+ int $email_id,
+ ?string $subject = null,
+ ?int $delay_value = null,
+ ?string $delay_unit = null,
+ ?string $preview_text = null,
+ ?string $content = null,
+ ?int $email_template_id = null,
+ ?bool $published = null,
+ ?array $send_days = null,
+ ?int $position = null
+ ) {
+ // Build parameters.
+ $options = ['send_days' => $send_days];
+
+ if (!is_null($subject)) {
+ $options['subject'] = $subject;
+ }
+ if (!is_null($delay_value)) {
+ $options['delay_value'] = $delay_value;
+ }
+ if (!is_null($delay_unit)) {
+ $options['delay_unit'] = $delay_unit;
+ }
+ if (!is_null($preview_text)) {
+ $options['preview_text'] = $preview_text;
+ }
+ if (!is_null($content)) {
+ $options['content'] = $content;
+ }
+ if (!is_null($email_template_id)) {
+ $options['email_template_id'] = $email_template_id;
+ }
+ if (!is_null($published)) {
+ $options['published'] = $published;
+ }
+ if (!is_null($send_days)) {
+ $options['send_days'] = $send_days;
+ }
+ if (!is_null($position)) {
+ $options['position'] = $position;
+ }
+
+ // Send request.
+ return $this->put(
+ sprintf('sequences/%s/emails/%s', $sequence_id, $email_id),
+ $options
+ );
+ }
+
+ /**
+ * Deletes a sequence email.
+ *
+ * @param integer $sequence_id Sequence ID.
+ * @param integer $email_id Email ID.
+ *
+ * @see https://developers.kit.com/api-reference/sequence-emails/delete-a-sequence-email
+ *
+ * @return mixed|object
+ */
+ public function delete_sequence_email(int $sequence_id, int $email_id)
+ {
+ return $this->delete(sprintf('sequences/%s/emails/%s', $sequence_id, $email_id));
+ }
+
+ /**
+ * List snippets
+ *
+ * @param boolean $archived When `true`, returns only archived snippets. Defaults to `false`.
+ * @param boolean $include_content When `true`, includes both the content and document fields for each snippet in the response. Defaults to `false`.
+ * @param string|null $snippet_type Filter snippets by type. Use inline for text snippets or block for rich-text block snippets.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
+ *
+ * @see https://developers.kit.com/api-reference/snippets/list-snippets
+ *
+ * @return false|mixed
+ */
+ public function get_snippets(
+ bool $archived = false,
+ bool $include_content = false,
+ ?string $snippet_type = null,
+ bool $include_total_count = false,
+ string $after_cursor = '',
+ string $before_cursor = '',
+ int $per_page = 100
+ ) {
+ $options = [
+ 'archived' => $archived,
+ 'include_content' => $include_content,
+ ];
+ if (!is_null($snippet_type)) {
+ $options['snippet_type'] = $snippet_type;
+ }
+ return $this->get(
+ 'snippets',
+ $this->build_total_count_and_pagination_params(
+ $options,
+ $include_total_count,
+ $after_cursor,
+ $before_cursor,
+ $per_page
+ )
+ );
+ }
+
+ /**
+ * Create a snippet
+ *
+ * @param string $name Name of the snippet.
+ * @param string $snippet_type Type of snippet. Must be one of: `inline`, `block`.
+ * @param string $content Content of the snippet.
+ *
+ * @see https://developers.kit.com/api-reference/snippets/create-a-snippet
+ *
+ * @return mixed|object
+ */
+ public function create_snippet(
+ string $name,
+ string $snippet_type,
+ string $content
+ ) {
+ $options = [
+ 'name' => $name,
+ 'snippet_type' => $snippet_type,
+ ];
+
+ switch ($snippet_type) {
+ case 'inline':
+ $options['content'] = $content;
+ break;
+
+ case 'block':
+ default:
+ $options['document_attributes'] = ['value_html' => $content];
+ break;
+ }
+
+ // Send request.
+ return $this->post(
+ 'snippets',
+ $options
+ );
+ }
+
+ /**
+ * Get a snippet.
+ *
+ * @param integer $id Snippet ID.
+ *
+ * @see https://developers.kit.com/api-reference/snippets/get-a-snippet
+ *
+ * @return mixed|object
+ */
+ public function get_snippet(int $id)
+ {
+ return $this->get(sprintf('snippets/%s', $id));
+ }
+
+ /**
+ * Updates a snippet
+ *
+ * @param integer $snippet_id Snippet ID.
+ * @param string $name Name of the snippet.
+ * @param string $snippet_type Type of snippet. Must be one of: `inline`, `block`.
+ * @param boolean $archived Pass `true` to archive or `false` to restore the snippet.
+ * @param string $content Content of the snippet.
+ *
+ * @see https://developers.kit.com/api-reference/snippets/update-a-snippet
+ *
+ * @return mixed|object
+ */
+ public function update_snippet(
+ int $snippet_id,
+ string $name = '',
+ string $snippet_type = '',
+ bool $archived = false,
+ string $content = ''
+ ) {
+ $options = [
+ 'name' => $name,
+ 'snippet_type' => $snippet_type,
+ 'archived' => $archived,
+ ];
+
+ switch ($snippet_type) {
+ case 'inline':
+ $options['content'] = $content;
+ break;
+
+ case 'block':
+ default:
+ $options['document_attributes'] = ['value_html' => $content];
+ break;
+ }
+
+ // Iterate through options, removing blank entries.
+ foreach ($options as $key => $value) {
+ if (is_string($value) && strlen($value) === 0) {
+ unset($options[$key]);
+ }
+ }
+
+ // Send request.
+ return $this->put(
+ sprintf('snippets/%s', $snippet_id),
+ $options
+ );
+ }
+
/**
* List tags.
*
- * @param boolean $include_total_count To include the total count of records in the response, use true.
- * @param string $after_cursor Return results after the given pagination cursor.
- * @param string $before_cursor Return results before the given pagination cursor.
- * @param integer $per_page Number of results to return.
+ * @param array $include Additional fields to include: subscriber_count.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
*
* @see https://developers.kit.com/api-reference/tags/list-tags
*
@@ -525,15 +1063,23 @@ public function get_sequence_subscriptions(
* @return mixed|array
*/
public function get_tags(
+ array $include = [],
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
+ // Build parameters.
+ $options = [];
+
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
+
return $this->get(
'tags',
$this->build_total_count_and_pagination_params(
- [],
+ $options,
$include_total_count,
$after_cursor,
$before_cursor,
@@ -594,6 +1140,41 @@ public function create_tags(array $tags, string $callback_url = '')
);
}
+ /**
+ * Bulk delete tags.
+ *
+ * @param array $tag_ids Tag IDs.
+ * @param string $callback_url URL to notify for large batch size when async processing complete.
+ *
+ * @since 2.6.0
+ *
+ * @see https://developers.kit.com/api-reference/tags/bulk-delete-tags
+ *
+ * @return false|mixed
+ */
+ public function delete_tags(array $tag_ids, string $callback_url = '')
+ {
+ // Build parameters.
+ $options = [
+ 'tags' => [],
+ ];
+ foreach ($tag_ids as $i => $tag_id) {
+ $options['tags'][] = [
+ 'id' => (int) $tag_id,
+ ];
+ }
+
+ if (!empty($callback_url)) {
+ $options['callback_url'] = $callback_url;
+ }
+
+ // Send request.
+ return $this->delete(
+ 'bulk/tags',
+ $options
+ );
+ }
+
/**
* Updates the name of a tag.
*
@@ -723,6 +1304,7 @@ public function remove_tag_from_subscriber_by_email(int $tag_id, string $email_a
* @param \DateTime|null $created_before Filter subscribers who have been created before this date.
* @param \DateTime|null $tagged_after Filter subscribers who have been tagged after this date.
* @param \DateTime|null $tagged_before Filter subscribers who have been tagged before this date.
+ * @param boolean $slim When true, omits expensive optional fields from the response.
* @param boolean $include_total_count To include the total count of records in the response, use true.
* @param string $after_cursor Return results after the given pagination cursor.
* @param string $before_cursor Return results before the given pagination cursor.
@@ -739,13 +1321,14 @@ public function get_tag_subscriptions(
?\DateTime $created_before = null,
?\DateTime $tagged_after = null,
?\DateTime $tagged_before = null,
+ bool $slim = false,
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
// Build parameters.
- $options = [];
+ $options = ['slim' => $slim];
if (!empty($subscriber_state)) {
$options['status'] = $subscriber_state;
@@ -809,6 +1392,57 @@ public function get_email_templates(
);
}
+ /**
+ * List posts.
+ *
+ * @param boolean $include_content To include the content field on each post in the response, use true.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
+ *
+ * @since 2.5.0
+ *
+ * @see https://developers.kit.com/api-reference/posts/list-posts
+ *
+ * @return false|mixed
+ */
+ public function get_posts(
+ bool $include_content = false,
+ bool $include_total_count = false,
+ string $after_cursor = '',
+ string $before_cursor = '',
+ int $per_page = 100
+ ) {
+ // Send request.
+ return $this->get(
+ 'posts',
+ $this->build_total_count_and_pagination_params(
+ ['include_content' => $include_content],
+ $include_total_count,
+ $after_cursor,
+ $before_cursor,
+ $per_page
+ )
+ );
+ }
+
+ /**
+ * Get a post.
+ *
+ * @param integer $id Post ID.
+ *
+ * @since 2.5.0
+ *
+ * @see https://developers.kit.com/api-reference/posts/get-a-post
+ *
+ * @return mixed|object
+ */
+ public function get_post(int $id)
+ {
+ return $this->get(sprintf('posts/%s', $id));
+ }
+
/**
* List subscribers.
*
@@ -820,6 +1454,8 @@ public function get_email_templates(
* @param \DateTime|null $updated_before Filter subscribers who have been updated before this date.
* @param string $sort_field Sort Field (id|updated_at|cancelled_at).
* @param string $sort_order Sort Order (asc|desc).
+ * @param array $include Additional fields to include: attribution, tags, location, canceled_at.
+ * @param boolean $slim When true, omits expensive optional fields from the response.
* @param boolean $include_total_count To include the total count of records in the response, use true.
* @param string $after_cursor Return results after the given pagination cursor.
* @param string $before_cursor Return results before the given pagination cursor.
@@ -840,13 +1476,15 @@ public function get_subscribers(
?\DateTime $updated_before = null,
string $sort_field = 'id',
string $sort_order = 'desc',
+ array $include = [],
+ bool $slim = false,
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
// Build parameters.
- $options = [];
+ $options = ['slim' => $slim];
if (!empty($subscriber_state)) {
$options['status'] = $subscriber_state;
@@ -872,6 +1510,9 @@ public function get_subscribers(
if (!empty($sort_order)) {
$options['sort_order'] = $sort_order;
}
+ if (!empty($include)) {
+ $options['include'] = implode(',', $include);
+ }
// Send request.
return $this->get(
@@ -961,17 +1602,22 @@ public function create_subscribers(array $subscribers, string $callback_url = ''
/**
* Filter subscribers based on engagement.
*
- * @param array> $all Array of filter conditions where ALL must be met (AND logic). Each condition can have.
- * - 'type' (string).
- * - 'count_greater_than' (int|null).
- * - 'count_less_than' (int|null).
- * - 'after' (\DateTime|null).
- * - 'before' (\DateTime|null).
- * - 'any' (array|null).
- * @param boolean $include_total_count To include the total count of records in the response, use true.
- * @param string $after_cursor Return results after the given pagination cursor.
- * @param string $before_cursor Return results before the given pagination cursor.
- * @param integer $per_page Number of results to return.
+ * @param list> $all Array of filter conditions where ALL must be met (AND logic). Each condition can have.
+ * - 'type' (string).
+ * - 'count_greater_than' (int|null).
+ * - 'count_less_than' (int|null).
+ * - 'after' (?\DateTime).
+ * - 'before' (?\DateTime).
+ * - 'states' (array).
+ * - 'any' (array|null).
+ * @param string $counting_mode Controls how engagement-filter count thresholds are tallied.
+ * - 'raw' (default) counts every event — five opens of the same email = five.
+ * - 'unique_email' counts distinct emails on which the action occurred.
+ * @param list> $include Array of additional fields to embed on each subscriber row.
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
*
* @since 2.4.0
*
@@ -981,6 +1627,8 @@ public function create_subscribers(array $subscribers, string $callback_url = ''
*/
public function filter_subscribers(
array $all = [],
+ string $counting_mode = 'raw',
+ array $include = [],
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
@@ -991,12 +1639,24 @@ public function filter_subscribers(
foreach ($all as $condition) {
$option = [];
- if (array_key_exists('count_greater_than', $condition) && $condition['count_greater_than'] !== null) {
- $option['count_greater_than'] = $condition['count_greater_than'];
+ if (array_key_exists('type', $condition) && !empty($condition['type'])) {
+ $option['type'] = $condition['type'];
+ }
+
+ if (array_key_exists('count_greater_than', $condition) && is_numeric($condition['count_greater_than'])) {
+ $option['count_greater_than'] = (int) $condition['count_greater_than'];
}
- if (array_key_exists('count_less_than', $condition) && $condition['count_less_than'] !== null) {
- $option['count_less_than'] = $condition['count_less_than'];
+ if (array_key_exists('count_greater_than_or_equal', $condition) && is_numeric($condition['count_greater_than_or_equal'])) {
+ $option['count_greater_than_or_equal'] = (int) $condition['count_greater_than_or_equal'];
+ }
+
+ if (array_key_exists('count_less_than', $condition) && is_numeric($condition['count_less_than'])) {
+ $option['count_less_than'] = (int) $condition['count_less_than'];
+ }
+
+ if (array_key_exists('count_less_than_or_equal', $condition) && is_numeric($condition['count_less_than_or_equal'])) {
+ $option['count_less_than_or_equal'] = (int) $condition['count_less_than_or_equal'];
}
if (array_key_exists('after', $condition) && $condition['after'] instanceof \DateTime) {
@@ -1007,6 +1667,34 @@ public function filter_subscribers(
$option['before'] = $condition['before']->format('Y-m-d');
}
+ if (array_key_exists('states', $condition) && !empty($condition['states'])) {
+ $option['states'] = (array) $condition['states'];
+ }
+
+ if (array_key_exists('subscriber_custom_field_id', $condition) && is_numeric($condition['subscriber_custom_field_id'])) {
+ $option['subscriber_custom_field_id'] = (int) $condition['subscriber_custom_field_id'];
+ }
+
+ if (array_key_exists('value', $condition) && $condition['value'] !== null) {
+ $option['value'] = $condition['value'];
+ }
+
+ if (array_key_exists('comparison', $condition) && $condition['comparison'] !== null) {
+ $option['comparison'] = $condition['comparison'];
+ }
+
+ if (array_key_exists('latitude', $condition) && is_numeric($condition['latitude'])) {
+ $option['latitude'] = (float) $condition['latitude'];
+ }
+
+ if (array_key_exists('longitude', $condition) && is_numeric($condition['longitude'])) {
+ $option['longitude'] = (float) $condition['longitude'];
+ }
+
+ if (array_key_exists('radius', $condition) && $condition['radius'] !== null) {
+ $option['radius'] = $condition['radius'];
+ }
+
if (array_key_exists('any', $condition) && !empty($condition['any'])) {
$option['any'] = (array) $condition['any'];
}
@@ -1017,7 +1705,11 @@ public function filter_subscribers(
return $this->post(
'subscribers/filter',
$this->build_total_count_and_pagination_params(
- ['all' => $options],
+ [
+ 'all' => $options,
+ 'counting_mode' => $counting_mode,
+ 'include' => $include,
+ ],
$include_total_count,
$after_cursor,
$before_cursor,
@@ -1203,26 +1895,47 @@ public function get_subscriber_tags(
/**
* List broadcasts.
*
- * @param boolean $include_total_count To include the total count of records in the response, use true.
- * @param string $after_cursor Return results after the given pagination cursor.
- * @param string $before_cursor Return results before the given pagination cursor.
- * @param integer $per_page Number of results to return.
+ * @param \DateTime|null $sent_after Get broadcasts sent after the given date.
+ * @param \DateTime|null $sent_before Get broadcasts sent before the given date.
+ * @param boolean $slim When true, omits expensive optional fields from the response.
+ * @param string|null $status Get broadcasts with the given status (draft, scheduled, sending, completed, aborted).
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
*
* @see https://developers.kit.com/api-reference/broadcasts/list-broadcasts
*
* @return false|mixed
*/
public function get_broadcasts(
+ ?\DateTime $sent_after = null,
+ ?\DateTime $sent_before = null,
+ bool $slim = false,
+ ?string $status = null,
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
+ // Build parameters.
+ $options = ['slim' => $slim];
+
+ if (!is_null($status)) {
+ $options['status'] = $status;
+ }
+ if (!is_null($sent_after)) {
+ $options['sent_after'] = $sent_after->format('Y-m-d');
+ }
+ if (!is_null($sent_before)) {
+ $options['sent_before'] = $sent_before->format('Y-m-d');
+ }
+
// Send request.
return $this->get(
'broadcasts',
$this->build_total_count_and_pagination_params(
- [],
+ $options,
$include_total_count,
$after_cursor,
$before_cursor,
@@ -1360,7 +2073,7 @@ public function get_broadcast_link_clicks(
return $this->get(
sprintf('broadcasts/%s/clicks', $id),
$this->build_total_count_and_pagination_params(
- [],
+ array(),
false,
$after_cursor,
$before_cursor,
@@ -1372,10 +2085,13 @@ public function get_broadcast_link_clicks(
/**
* List stats for a list of broadcasts.
*
- * @param boolean $include_total_count To include the total count of records in the response, use true.
- * @param string $after_cursor Return results after the given pagination cursor.
- * @param string $before_cursor Return results before the given pagination cursor.
- * @param integer $per_page Number of results to return.
+ * @param \DateTime|null $sent_after Get broadcasts sent after the given date.
+ * @param \DateTime|null $sent_before Get broadcasts sent before the given date.
+ * @param string|null $status Get broadcasts with the given status (draft, scheduled, sending, completed, aborted).
+ * @param boolean $include_total_count To include the total count of records in the response, use true.
+ * @param string $after_cursor Return results after the given pagination cursor.
+ * @param string $before_cursor Return results before the given pagination cursor.
+ * @param integer $per_page Number of results to return.
*
* @since 2.2.1
*
@@ -1384,16 +2100,32 @@ public function get_broadcast_link_clicks(
* @return false|mixed
*/
public function get_broadcasts_stats(
+ ?\DateTime $sent_after = null,
+ ?\DateTime $sent_before = null,
+ ?string $status = null,
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
+ // Build parameters.
+ $options = [];
+
+ if (!is_null($status)) {
+ $options['status'] = $status;
+ }
+ if (!is_null($sent_after)) {
+ $options['sent_after'] = $sent_after->format('Y-m-d');
+ }
+ if (!is_null($sent_before)) {
+ $options['sent_before'] = $sent_before->format('Y-m-d');
+ }
+
// Send request.
return $this->get(
- 'broadcasts/stats',
+ 'broadcasts',
$this->build_total_count_and_pagination_params(
- [],
+ $options,
$include_total_count,
$after_cursor,
$before_cursor,
@@ -1402,7 +2134,6 @@ public function get_broadcasts_stats(
);
}
-
/**
* Update a broadcast.
*
@@ -2053,8 +2784,8 @@ public function post(string $endpoint, array $args = [])
/**
* Performs a PUT request to the API.
*
- * @param string $endpoint API Endpoint.
- * @param array|string> $args Request arguments.
+ * @param string $endpoint API Endpoint.
+ * @param array|boolean|integer|float|string>> $args Request arguments.
*
* @return false|mixed
*/
@@ -2066,8 +2797,8 @@ public function put(string $endpoint, array $args = [])
/**
* Performs a DELETE request to the API.
*
- * @param string $endpoint API Endpoint.
- * @param array|string> $args Request arguments.
+ * @param string $endpoint API Endpoint.
+ * @param array|boolean|integer|float|string>> $args Request arguments.
*
* @return false|mixed
*/
diff --git a/src/class-convertkit-api-v4.php b/src/class-convertkit-api-v4.php
index 611d2bc..a5dfc1b 100644
--- a/src/class-convertkit-api-v4.php
+++ b/src/class-convertkit-api-v4.php
@@ -21,7 +21,7 @@ class ConvertKit_API_V4 {
*
* @var string
*/
- public const VERSION = '2.0.0';
+ public const VERSION = '2.6.0';
/**
* Redirect URI.
@@ -79,6 +79,17 @@ class ConvertKit_API_V4 {
*/
protected $plugin_version;
+ /**
+ * The HTTP status code of the last API response.
+ *
+ * Set by request() after every call. Read via get_last_response_code().
+ *
+ * @since 2.6.0
+ *
+ * @var int
+ */
+ protected $last_response_code = 0;
+
/**
* ConvertKit API endpoints that use the /oauth/ namespace
* i.e. https://api.kit.com/oauth/endpoint
@@ -226,7 +237,20 @@ private function generate_and_store_code_verifier() {
}
/**
- * Base64URL the given code verifier, as PHP has no built in function for this.
+ * Returns the HTTP status code of the last API response.
+ *
+ * @since 2.6.0
+ *
+ * @return int HTTP status code of the last API response (0 if none).
+ */
+ public function get_last_response_code() {
+
+ return $this->last_response_code;
+
+ }
+
+ /**
+ * Generates a PKCE Code Challenge for the given Code Verifier.
*
* @since 2.0.0
*
@@ -1315,6 +1339,19 @@ public function get_html( $url, $body_only = true ) {
);
}
+ // Treat any 4xx or 5xx status as an error.
+ if ( $http_response_code >= 400 ) {
+ return new WP_Error(
+ 'convertkit_api_error',
+ sprintf(
+ /* translators: %1$s: URL, %2$d: HTTP status code */
+ __( 'ConvertKit: Request to %1$s returned HTTP %2$d.', 'convertkit' ),
+ $url,
+ (int) $http_response_code
+ )
+ );
+ }
+
// If the HTML is missing the tag, it's likely to be a legacy form.
// Wrap it in , and tags now, so we can inject the UTF-8 Content-Type meta tag.
if ( strpos( $body, 'get_html( $url, true );
+
+ }
+
/**
* Sets the type attribute for script elements to 'text/javascript',
* where Cloudflare prepends a random string to the type attribute.
@@ -1510,6 +1564,9 @@ public function request( $endpoint, $method = 'get', $params = array(), $retry_i
$http_response_code = wp_remote_retrieve_response_code( $result );
$body = wp_remote_retrieve_body( $result );
+ // Store the HTTP response code.
+ $this->last_response_code = (int) $http_response_code;
+
// If the body is null i.e. a 204 No Content, don't attempt to JSON decode it.
$response = ( ! empty( $body ) ? json_decode( $body, true ) : null );
diff --git a/src/class-convertkit-resource-v4.php b/src/class-convertkit-resource-v4.php
index 9a56a48..57a0d67 100644
--- a/src/class-convertkit-resource-v4.php
+++ b/src/class-convertkit-resource-v4.php
@@ -543,6 +543,16 @@ private function get_all_resources( $resource_type, $per_page = 100 ) {
// Build array of arguments depending on the resource type.
switch ( $resource_type ) {
case 'forms':
+ $args = array(
+ 'active',
+ array(),
+ false,
+ '',
+ '',
+ $per_page,
+ );
+ break;
+
case 'landing_pages':
$args = array(
'active',
@@ -563,6 +573,17 @@ private function get_all_resources( $resource_type, $per_page = 100 ) {
);
break;
+ case 'tags':
+ case 'sequences':
+ $args = array(
+ array(),
+ false,
+ '',
+ '',
+ $per_page,
+ );
+ break;
+
default:
$args = array(
false,
@@ -597,6 +618,16 @@ private function get_all_resources( $resource_type, $per_page = 100 ) {
// Build array of arguments depending on the resource type.
switch ( $resource_type ) {
case 'forms':
+ $args = array(
+ 'active',
+ array(),
+ false,
+ $response['pagination']['end_cursor'],
+ '',
+ $per_page,
+ );
+ break;
+
case 'landing_pages':
$args = array(
'active',
@@ -617,6 +648,17 @@ private function get_all_resources( $resource_type, $per_page = 100 ) {
);
break;
+ case 'tags':
+ case 'sequences':
+ $args = array(
+ array(),
+ false,
+ $response['pagination']['end_cursor'],
+ '',
+ $per_page,
+ );
+ break;
+
default:
$args = array(
false,
diff --git a/tests/Integration/APITest.php b/tests/Integration/APITest.php
index b4a9fc6..bc6c76e 100644
--- a/tests/Integration/APITest.php
+++ b/tests/Integration/APITest.php
@@ -4,6 +4,9 @@
use lucatume\WPBrowser\TestCase\WPTestCase;
+// Load the shared tests trait.
+require_once __DIR__ . '/TestsTrait.php';
+
/**
* Tests for the ConvertKit_API class.
*
@@ -11,6 +14,8 @@
*/
class APITest extends WPTestCase
{
+ use \TestsTrait;
+
/**
* The testing implementation.
*
@@ -87,24 +92,61 @@ public function setUp(): void
require_once 'src/class-convertkit-log.php';
// Initialize the classes we want to test.
- $this->api = new \ConvertKit_API_V4(
+ $rawApi = new \ConvertKit_API_V4(
client_id: $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'],
redirect_uri: $_ENV['CONVERTKIT_OAUTH_REDIRECT_URI'],
access_token: $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'],
refresh_token: $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN']
);
- $this->api_no_data = new \ConvertKit_API_V4(
+ $rawApiNoData = new \ConvertKit_API_V4(
client_id: $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'],
redirect_uri: $_ENV['CONVERTKIT_OAUTH_REDIRECT_URI'],
access_token: $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA'],
refresh_token: $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA']
);
+ // For tests from TestsTrait, use the ObjectResponseProxy to convert array responses to stdClass.
+ if ($this->currentTestIsFromTestsTrait()) {
+ require_once __DIR__ . '/../Support/Helper/WPUnit/ObjectResponseProxy.php';
+ $this->api = new \Helper\WPUnit\ObjectResponseProxy($rawApi);
+ $this->api_no_data = new \Helper\WPUnit\ObjectResponseProxy($rawApiNoData);
+ } else {
+ $this->api = $rawApi;
+ $this->api_no_data = $rawApiNoData;
+ }
+
// Wait a second to avoid hitting a 429 rate limit.
sleep(1);
}
+ /**
+ * Returns true when the currently-executing test method was defined on
+ * the shared TestsTrait rather than on this class.
+ *
+ * PHP reflection preserves the source file of trait-provided methods
+ * even after the trait has been flattened into the composing class, so
+ * we can distinguish the two just by looking at the declaring file.
+ *
+ * @since 2.6.0
+ *
+ * @return bool
+ */
+ private function currentTestIsFromTestsTrait(): bool
+ {
+ try {
+ // PHPUnit 10+ uses name(); older uses getName(false). Prefer the new API.
+ $testName = method_exists($this, 'name')
+ ? $this->name()
+ : $this->getName(false);
+ $method = new \ReflectionMethod($this, $testName);
+ $file = $method->getFileName();
+ return $file !== false && basename($file) === 'TestsTrait.php';
+ } catch (\Throwable $e) {
+ return false;
+ }
+ }
+
/**
* Performs actions after each test.
*
@@ -135,6 +177,45 @@ public function tearDown(): void
parent::tearDown();
}
+ /**
+ * Assert that the given callable produces an API-level error.
+ *
+ * In WP Libs an API-level error surfaces as a WP_Error return value
+ * (never as a thrown exception). We accept both return and throw so
+ * that any input-validation code that throws still counts.
+ *
+ * @since 2.0.5
+ *
+ * @param callable $fn Callable that should fail.
+ * @return void
+ */
+ protected function assertApiError(callable $fn): void
+ {
+ try {
+ $result = $fn();
+ } catch (\Throwable $e) {
+ $this->assertTrue(true, 'Callable threw an exception as expected.');
+ return;
+ }
+ $this->assertInstanceOf(\WP_Error::class, $result);
+ }
+
+ /**
+ * Assert that the last API response had the given HTTP status code.
+ *
+ * Backed by ConvertKit_API_V4::get_last_response_code(), which mirrors
+ * the PHP SDK's getResponseInterface()->getStatusCode().
+ *
+ * @since 2.0.5
+ *
+ * @param int $expected Expected HTTP status code.
+ * @return void
+ */
+ protected function assertLastResponseStatusCode(int $expected): void
+ {
+ $this->assertEquals($expected, $this->api->get_last_response_code());
+ }
+
/**
* Test that a log directory and file are created in the expected location, with .htaccess
* and index.html protection, and that the name and email addresses are masked.
@@ -719,4554 +800,347 @@ public function testGetAccessTokenByAPIKeyAndSecretWithTenantName()
$this->assertArrayHasKey('refresh_token', $result['oauth']);
$this->assertArrayHasKey('expires_at', $result['oauth']);
}
-
- /**
- * Test that supplying valid API credentials to the API class returns the expected account information.
- *
- * @since 1.0.0
- */
- public function testGetAccount()
- {
- $result = $this->api->get_account();
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- $this->assertArrayHasKey('user', $result);
- $this->assertArrayHasKey('account', $result);
-
- $this->assertArrayHasKey('name', $result['account']);
- $this->assertArrayHasKey('plan_type', $result['account']);
- $this->assertArrayHasKey('primary_email_address', $result['account']);
- $this->assertEquals('wordpress@convertkit.com', $result['account']['primary_email_address']);
- }
-
/**
- * Test that get_account_colors() returns the expected data.
+ * Test that get_legacy_forms() returns the expected data.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetAccountColors()
+ public function testGetLegacyForms()
{
- $result = $this->api->get_account_colors();
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
+ $result = $this->api->get_legacy_forms();
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'legacy_landing_pages');
+ $this->assertPaginationExists($result);
+
+ // Iterate through each form, confirming no landing pages were included.
+ foreach ($result['legacy_landing_pages'] as $form) {
+ // Assert shape of object is valid.
+ $this->assertArrayHasKey('id', $form);
+ $this->assertArrayHasKey('name', $form);
+ $this->assertArrayHasKey('created_at', $form);
+ $this->assertArrayHasKey('type', $form);
+ $this->assertArrayHasKey('url', $form);
- $this->assertArrayHasKey('colors', $result);
- $this->assertIsArray($result['colors']);
+ // Assert form is not a landing page i.e it is an embed.
+ $this->assertEquals($form['type'], 'embed');
+ }
}
/**
- * Test that update_account_colors() updates the account's colors.
+ * Test that get_legacy_forms() returns the expected data
+ * when the total count is included.
*
* @since 2.0.0
*
* @return void
*/
- public function testUpdateAccountColors()
+ public function testGetLegacyFormsWithTotalCount()
{
- $result = $this->api->update_account_colors(
- colors: [
- '#111111',
- ]
+ $result = $this->api->get_legacy_forms(
+ include_total_count: true
);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('colors', $result);
- $this->assertIsArray($result['colors']);
- $this->assertEquals($result['colors'][0], '#111111');
- }
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'legacy_landing_pages');
+ $this->assertPaginationExists($result);
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', $result['pagination']);
+ $this->assertGreaterThan(0, $result['pagination']['total_count']);
+ }
/**
- * Test that get_creator_profile() returns the expected data.
+ * Test that get_legacy_landing_pages() returns the expected data.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetCreatorProfile()
+ public function testGetLegacyLandingPages()
{
- $result = $this->api->get_creator_profile();
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
+ $result = $this->api->get_legacy_landing_pages();
+
+ // Assert landing pages and pagination exist.
+ $this->assertDataExists($result, 'legacy_landing_pages');
+ $this->assertPaginationExists($result);
+
+ // Iterate through each landing page, confirming no forms were included.
+ foreach ($result['legacy_landing_pages'] as $form) {
+ // Assert shape of object is valid.
+ $this->assertArrayHasKey('id', $form);
+ $this->assertArrayHasKey('name', $form);
+ $this->assertArrayHasKey('created_at', $form);
+ $this->assertArrayHasKey('type', $form);
+ $this->assertArrayHasKey('url', $form);
- $this->assertArrayHasKey('name', $result['profile']);
- $this->assertArrayHasKey('byline', $result['profile']);
- $this->assertArrayHasKey('bio', $result['profile']);
- $this->assertArrayHasKey('image_url', $result['profile']);
- $this->assertArrayHasKey('profile_url', $result['profile']);
+ // Assert landing page is not a form i.e it is hosted.
+ $this->assertEquals($form['type'], 'hosted');
+ }
}
/**
- * Test that get_email_stats() returns the expected data.
+ * Test that get_landing_pages() returns the expected data
+ * when the total count is included.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetEmailStats()
+ public function testGetLegacyLandingPagesWithTotalCount()
{
- $result = $this->api->get_email_stats();
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
+ $result = $this->api->get_legacy_landing_pages(
+ include_total_count: true
+ );
- $this->assertArrayHasKey('sent', $result['stats']);
- $this->assertArrayHasKey('clicked', $result['stats']);
- $this->assertArrayHasKey('opened', $result['stats']);
- $this->assertArrayHasKey('email_stats_mode', $result['stats']);
- $this->assertArrayHasKey('open_tracking_enabled', $result['stats']);
- $this->assertArrayHasKey('click_tracking_enabled', $result['stats']);
- $this->assertArrayHasKey('starting', $result['stats']);
- $this->assertArrayHasKey('ending', $result['stats']);
- }
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'legacy_landing_pages');
+ $this->assertPaginationExists($result);
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', $result['pagination']);
+ $this->assertGreaterThan(0, $result['pagination']['total_count']);
+ }
/**
- * Test that get_growth_stats() returns the expected data.
+ * Test that add_subscriber_to_form() returns a WP_Error when a legacy
+ * form ID is specified.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetGrowthStats()
+ public function testAddSubscriberToFormWithLegacyFormID()
{
- $result = $this->api->get_growth_stats();
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- $this->assertArrayHasKey('cancellations', $result['stats']);
- $this->assertArrayHasKey('net_new_subscribers', $result['stats']);
- $this->assertArrayHasKey('new_subscribers', $result['stats']);
- $this->assertArrayHasKey('subscribers', $result['stats']);
- $this->assertArrayHasKey('starting', $result['stats']);
- $this->assertArrayHasKey('ending', $result['stats']);
+ $result = $this->api->add_subscriber_to_form(
+ form_id: $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'],
+ subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
+ );
+ $this->assertInstanceOf(\WP_Error::class, $result);
+ $this->assertEquals($result->get_error_code(), $this->errorCode);
}
+
/**
- * Test that get_growth_stats() returns the expected data
- * when a start date is specified.
+ * Test that add_subscriber_to_legacy_form() returns the expected data.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetGrowthStatsWithStartDate()
+ public function testAddSubscriberToLegacyForm()
{
- // Define start and end dates.
- $starting = new \DateTime('now');
- $starting->modify('-7 days');
- $ending = new \DateTime('now');
+ // Create subscriber.
+ $subscriber = $this->api->create_subscriber($this->generateEmailAddress());
- // Send request.
- $result = $this->api->get_growth_stats(
- starting: $starting
+ $this->assertNotInstanceOf(\WP_Error::class, $subscriber);
+ $this->assertIsArray($subscriber);
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $subscriber['subscriber']['id'];
+
+ // Add subscriber to legacy form.
+ $result = $this->api->add_subscriber_to_legacy_form(
+ form_id: (int) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'],
+ subscriber_id: $subscriber['subscriber']['id']
);
$this->assertNotInstanceOf(\WP_Error::class, $result);
$this->assertIsArray($result);
-
- // Confirm response object contains expected keys.
- $this->assertArrayHasKey('cancellations', $result['stats']);
- $this->assertArrayHasKey('net_new_subscribers', $result['stats']);
- $this->assertArrayHasKey('new_subscribers', $result['stats']);
- $this->assertArrayHasKey('subscribers', $result['stats']);
- $this->assertArrayHasKey('starting', $result['stats']);
- $this->assertArrayHasKey('ending', $result['stats']);
-
- // Assert start and end dates were honored.
- $timezone = ( new \DateTime() )->setTimezone(new \DateTimeZone('America/New_York'))->format('P'); // Gets timezone offset for New York (-04:00 during DST, -05:00 otherwise).
- $this->assertEquals($result['stats']['starting'], $starting->format('Y-m-d') . 'T00:00:00' . $timezone);
- $this->assertEquals($result['stats']['ending'], $ending->format('Y-m-d') . 'T23:59:59' . $timezone);
+ $this->assertArrayHasKey('subscriber', $result);
+ $this->assertArrayHasKey('id', $result['subscriber']);
+ $this->assertEquals($result['subscriber']['id'], $subscriber['subscriber']['id']);
}
/**
- * Test that get_growth_stats() returns the expected data
- * when an end date is specified.
+ * Test that add_subscriber_to_legacy_form() returns a WP_Error when an invalid
+ * form ID is specified.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetGrowthStatsWithEndDate()
+ public function testAddSubscriberToLegacyFormWithInvalidFormID()
{
- // Define start and end dates.
- $starting = new \DateTime('now');
- $starting->modify('-90 days');
- $ending = new \DateTime('now');
- $ending->modify('-7 days');
-
- // Send request.
- $result = $this->api->get_growth_stats(
- ending: $ending
+ $result = $this->api->add_subscriber_to_legacy_form(
+ form_id: 12345,
+ subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Confirm response object contains expected keys.
- $this->assertArrayHasKey('cancellations', $result['stats']);
- $this->assertArrayHasKey('net_new_subscribers', $result['stats']);
- $this->assertArrayHasKey('new_subscribers', $result['stats']);
- $this->assertArrayHasKey('subscribers', $result['stats']);
- $this->assertArrayHasKey('starting', $result['stats']);
- $this->assertArrayHasKey('ending', $result['stats']);
-
- // Assert start and end dates were honored.
- $timezone = ( new \DateTime() )->setTimezone(new \DateTimeZone('America/New_York'))->format('P'); // Gets timezone offset for New York (-04:00 during DST, -05:00 otherwise).
- $this->assertEquals($result['stats']['starting'], $starting->format('Y-m-d') . 'T00:00:00' . $timezone);
- $this->assertEquals($result['stats']['ending'], $ending->format('Y-m-d') . 'T23:59:59' . $timezone);
+ $this->assertInstanceOf(\WP_Error::class, $result);
+ $this->assertEquals($result->get_error_code(), $this->errorCode);
}
/**
- * Test that get_forms() returns the expected data.
+ * Test that add_subscriber_to_legacy_form() returns a WP_Error when a non-legacy
+ * form ID is specified.
*
- * @since 1.0.0
+ * @since 2.0.0
*
* @return void
*/
- public function testGetForms()
+ public function testAddSubscriberToLegacyFormWithNonLegacyFormID()
{
- $result = $this->api->get_forms();
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Iterate through each form, confirming no landing pages were included.
- foreach ($result['forms'] as $form) {
- // Assert shape of object is valid.
- $this->assertArrayHasKey('id', $form);
- $this->assertArrayHasKey('name', $form);
- $this->assertArrayHasKey('created_at', $form);
- $this->assertArrayHasKey('type', $form);
- $this->assertArrayHasKey('format', $form);
- $this->assertArrayHasKey('embed_js', $form);
- $this->assertArrayHasKey('embed_url', $form);
- $this->assertArrayHasKey('archived', $form);
-
- // Assert form is not a landing page i.e embed.
- $this->assertEquals($form['type'], 'embed');
-
- // Assert form is not archived.
- $this->assertFalse($form['archived']);
- }
+ $result = $this->api->add_subscriber_to_legacy_form(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
+ );
+ $this->assertInstanceOf(\WP_Error::class, $result);
+ $this->assertEquals($result->get_error_code(), $this->errorCode);
}
/**
- * Test that get_forms() returns the expected data when
- * the status is set to archived.
+ * Test that add_subscriber_to_legacy_form() returns a WP_Error when an invalid
+ * email address is specified.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetFormsWithArchivedStatus()
+ public function testAddSubscriberToLegacyFormWithInvalidSubscriberID()
{
- $result = $this->api->get_forms(
- status: 'archived'
+ $result = $this->api->add_subscriber_to_legacy_form(
+ form_id: (int) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'],
+ subscriber_id: 12345
);
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Iterate through each form, confirming no landing pages were included.
- foreach ($result['forms'] as $form) {
- // Assert shape of object is valid.
- $this->assertArrayHasKey('id', $form);
- $this->assertArrayHasKey('name', $form);
- $this->assertArrayHasKey('created_at', $form);
- $this->assertArrayHasKey('type', $form);
- $this->assertArrayHasKey('format', $form);
- $this->assertArrayHasKey('embed_js', $form);
- $this->assertArrayHasKey('embed_url', $form);
- $this->assertArrayHasKey('archived', $form);
-
- // Assert form is not a landing page i.e embed.
- $this->assertEquals($form['type'], 'embed');
-
- // Assert form is archived.
- $this->assertTrue($form['archived']);
- }
+ $this->assertInstanceOf(\WP_Error::class, $result);
+ $this->assertEquals($result->get_error_code(), $this->errorCode);
}
-
/**
- * Test that get_forms() returns the expected data
- * when the total count is included.
+ * Test that create_tag() returns the expected data.
*
- * @since 2.0.0
+ * @since 1.0.0
*
* @return void
*/
- public function testGetFormsWithTotalCount()
+ public function testCreateTag()
{
- $result = $this->api->get_forms(
- status: 'active',
- include_total_count: true
+ $tagName = 'Tag Test ' . wp_rand();
+
+ // Add mock handler for this API request, as the API doesn't provide
+ // a method to delete tags to cleanup the test.
+ $this->mockResponses(
+ 201,
+ 'Created',
+ wp_json_encode(
+ array(
+ 'tag' => array(
+ 'id' => 12345,
+ 'name' => $tagName,
+ 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z',
+ ),
+ )
+ )
);
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
+ // Send request.
+ $result = $this->api->create_tag($tagName);
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
+ // Assert response contains correct data.
+ $this->assertArrayHasKey('id', $result['tag']);
+ $this->assertArrayHasKey('name', $result['tag']);
+ $this->assertArrayHasKey('created_at', $result['tag']);
+ $this->assertEquals($result['tag']['name'], $tagName);
}
-
/**
- * Test that get_forms() returns the expected data when pagination parameters
- * and per_page limits are specified.
+ * Test that create_tags() returns the expected data.
*
* @since 2.0.0
*
* @return void
*/
- public function testGetFormsPagination()
+ public function testCreateTags()
{
- // Return one form.
- $result = $this->api->get_forms(
- status: 'active',
- per_page: 1
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert a single form was returned.
- $this->assertCount(1, $result['forms']);
-
- // 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_forms(
- status: 'active',
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert a single form was returned.
- $this->assertCount(1, $result['forms']);
-
- // 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_forms(
- status: 'active',
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert a single form was returned.
- $this->assertCount(1, $result['forms']);
-
- // 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 get_legacy_forms() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLegacyForms()
- {
- $result = $this->api->get_legacy_forms();
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'legacy_landing_pages');
- $this->assertPaginationExists($result);
-
- // Iterate through each form, confirming no landing pages were included.
- foreach ($result['legacy_landing_pages'] as $form) {
- // Assert shape of object is valid.
- $this->assertArrayHasKey('id', $form);
- $this->assertArrayHasKey('name', $form);
- $this->assertArrayHasKey('created_at', $form);
- $this->assertArrayHasKey('type', $form);
- $this->assertArrayHasKey('url', $form);
-
- // Assert form is not a landing page i.e it is an embed.
- $this->assertEquals($form['type'], 'embed');
- }
- }
-
- /**
- * Test that get_legacy_forms() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLegacyFormsWithTotalCount()
- {
- $result = $this->api->get_legacy_forms(
- include_total_count: true
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'legacy_landing_pages');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_landing_pages() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetLandingPages()
- {
- $result = $this->api->get_landing_pages();
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Iterate through each landing page, confirming no forms were included.
- foreach ($result['forms'] as $form) {
- // Assert shape of object is valid.
- $this->assertArrayHasKey('id', $form);
- $this->assertArrayHasKey('name', $form);
- $this->assertArrayHasKey('created_at', $form);
- $this->assertArrayHasKey('type', $form);
- $this->assertArrayHasKey('format', $form);
- $this->assertArrayHasKey('embed_js', $form);
- $this->assertArrayHasKey('embed_url', $form);
- $this->assertArrayHasKey('archived', $form);
-
- // Assert form is a landing page i.e. hosted.
- $this->assertEquals($form['type'], 'hosted');
-
- // Assert form is not archived.
- $this->assertFalse($form['archived']);
- }
- }
-
- /**
- * Test that get_landing_pages() returns the expected data when
- * the status is set to archived.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLandingPagesWithArchivedStatus()
- {
- $result = $this->api->get_forms(
- status: 'archived'
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert no landing pages are returned, as the account doesn't have any archived landing pages.
- $this->assertCount(0, $result['forms']);
- }
-
- /**
- * Test that get_landing_pages() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLandingPagesWithTotalCount()
- {
- $result = $this->api->get_landing_pages(
- status: 'active',
- include_total_count: true
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_landing_pages() returns the expected data when pagination parameters
- * and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLandingPagesPagination()
- {
- // Return one landing page.
- $result = $this->api->get_landing_pages(
- status: 'active',
- per_page: 1
- );
-
- // Assert landing pages and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert a single landing page was returned.
- $this->assertCount(1, $result['forms']);
-
- // 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_landing_pages(
- status: 'active',
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert landing pages and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert a single landing page was returned.
- $this->assertCount(1, $result['forms']);
-
- // Assert has_previous_page and has_next_page are correct.
- $this->assertTrue($result['pagination']['has_previous_page']);
- $this->assertFalse($result['pagination']['has_next_page']);
-
- // Use pagination to fetch previous page.
- $result = $this->api->get_landing_pages(
- status: 'active',
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert landing pages and pagination exist.
- $this->assertDataExists($result, 'forms');
- $this->assertPaginationExists($result);
-
- // Assert a single landing page was returned.
- $this->assertCount(1, $result['forms']);
-
- // 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 get_legacy_landing_pages() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLegacyLandingPages()
- {
- $result = $this->api->get_legacy_landing_pages();
-
- // Assert landing pages and pagination exist.
- $this->assertDataExists($result, 'legacy_landing_pages');
- $this->assertPaginationExists($result);
-
- // Iterate through each landing page, confirming no forms were included.
- foreach ($result['legacy_landing_pages'] as $form) {
- // Assert shape of object is valid.
- $this->assertArrayHasKey('id', $form);
- $this->assertArrayHasKey('name', $form);
- $this->assertArrayHasKey('created_at', $form);
- $this->assertArrayHasKey('type', $form);
- $this->assertArrayHasKey('url', $form);
-
- // Assert landing page is not a form i.e it is hosted.
- $this->assertEquals($form['type'], 'hosted');
- }
- }
-
- /**
- * Test that get_landing_pages() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetLegacyLandingPagesWithTotalCount()
- {
- $result = $this->api->get_legacy_landing_pages(
- include_total_count: true
- );
-
- // Assert forms and pagination exist.
- $this->assertDataExists($result, 'legacy_landing_pages');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptions()
- {
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID']
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithTotalCount()
- {
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- include_total_count: true
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified and the subscription status
- * is cancelled.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithCancelledSubscriberState()
- {
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'cancelled'
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertEquals($result['subscribers'][0]['state'], 'cancelled');
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified and the added_after parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithAddedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- added_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['added_at']))
- );
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified and the added_before parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithAddedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- added_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['added_at']))
- );
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified and the created_after parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithCreatedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- created_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified and the created_before parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithCreatedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- created_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_form_subscriptions() returns the expected data
- * when a valid Form ID is specified and pagination parameters
- * and per_page limits are specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsPagination()
- {
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_form_subscriptions() throws a ClientException when an invalid
- * Form ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithInvalidFormID()
- {
- $result = $this->api->get_form_subscriptions(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_form_subscriptions() throws a ClientException when an invalid
- * subscriber state is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithInvalidSubscriberState()
- {
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'not-a-valid-state'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_form_subscriptions() throws a ClientException when invalid
- * pagination parameters are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetFormSubscriptionsWithInvalidPagination()
- {
- $result = $this->api->get_form_subscriptions(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_state: 'active',
- after_cursor: 'not-a-valid-cursor'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_form_by_email() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testAddSubscriberToFormByEmail()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Add subscriber to form.
- $result = $this->api->add_subscriber_to_form_by_email(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- email_address: $emailAddress
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals(
- $result['subscriber']['email_address'],
- $emailAddress
- );
- }
-
- /**
- * Test that add_subscriber_to_form_by_email() returns the expected data
- * when a referrer is specified.
- *
- * @since 2.1.0
- *
- * @return void
- */
- public function testAddSubscriberToFormByEmailWithReferrer()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $subscriber = $this->api->create_subscriber($emailAddress);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to form.
- $result = $this->api->add_subscriber_to_form_by_email(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- email_address: $emailAddress,
- referrer: 'https://mywebsite.com/bfpromo/'
- );
-
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals(
- $result['subscriber']['email_address'],
- $emailAddress
- );
-
- // Assert referrer data set for form subscriber.
- $this->assertEquals(
- $result['subscriber']['referrer'],
- 'https://mywebsite.com/bfpromo/'
- );
- }
-
- /**
- * Test that add_subscriber_to_form_by_email() returns the expected data
- * when a referrer is specified that includes UTM parameters.
- *
- * @since 2.1.0
- *
- * @return void
- */
- public function testAddSubscriberToFormByEmailWithReferrerUTMParams()
- {
- // Define referrer.
- $referrerUTMParams = [
- 'utm_source' => 'facebook',
- 'utm_medium' => 'cpc',
- 'utm_campaign' => 'black_friday',
- 'utm_term' => 'car_owners',
- 'utm_content' => 'get_10_off',
- ];
- $referrer = 'https://mywebsite.com/bfpromo/?' . http_build_query($referrerUTMParams);
-
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $subscriber = $this->api->create_subscriber($emailAddress);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to form.
- $result = $this->api->add_subscriber_to_form_by_email(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- email_address: $emailAddress,
- referrer: $referrer
- );
-
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals(
- $result['subscriber']['email_address'],
- $emailAddress
- );
-
- // Assert referrer data set for form subscriber.
- $this->assertEquals(
- $result['subscriber']['referrer'],
- $referrer
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['source'],
- $referrerUTMParams['utm_source']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['medium'],
- $referrerUTMParams['utm_medium']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['campaign'],
- $referrerUTMParams['utm_campaign']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['term'],
- $referrerUTMParams['utm_term']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['content'],
- $referrerUTMParams['utm_content']
- );
- }
-
- /**
- * Test that add_subscriber_to_form_by_email() returns a WP_Error when an invalid
- * form is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testAddSubscriberToFormByEmailWithInvalidformID()
- {
- $result = $this->api->add_subscriber_to_form_by_email(
- form_id: 12345,
- email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_form_by_email() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testAddSubscriberToFormByEmailWithInvalidEmailAddress()
- {
- $result = $this->api->add_subscriber_to_form_by_email(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- email_address: 'not-an-email-address'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_form() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToForm()
- {
- // Create subscriber.
- $subscriber = $this->api->create_subscriber($this->generateEmailAddress());
-
- $this->assertNotInstanceOf(\WP_Error::class, $subscriber);
- $this->assertIsArray($subscriber);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to form.
- $result = $this->api->add_subscriber_to_form(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_id: $subscriber['subscriber']['id']
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals($result['subscriber']['id'], $subscriber['subscriber']['id']);
- }
-
- /**
- * Test that add_subscriber_to_form() returns the expected data
- * when a referrer is specified.
- *
- * @since 2.1.0
- *
- * @return void
- */
- public function testAddSubscriberToFormWithReferrer()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $subscriber = $this->api->create_subscriber($emailAddress);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to form.
- $result = $this->api->add_subscriber_to_form(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_id: $subscriber['subscriber']['id'],
- referrer: 'https://mywebsite.com/bfpromo/'
- );
-
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals(
- $result['subscriber']['id'],
- $subscriber['subscriber']['id']
- );
-
- // Assert referrer data set for form subscriber.
- $this->assertEquals(
- $result['subscriber']['referrer'],
- 'https://mywebsite.com/bfpromo/'
- );
- }
-
- /**
- * Test that add_subscriber_to_form() returns the expected data
- * when a referrer is specified that includes UTM parameters.
- *
- * @since 2.1.0
- *
- * @return void
- */
- public function testAddSubscriberToFormWithReferrerUTMParams()
- {
- // Define referrer.
- $referrerUTMParams = [
- 'utm_source' => 'facebook',
- 'utm_medium' => 'cpc',
- 'utm_campaign' => 'black_friday',
- 'utm_term' => 'car_owners',
- 'utm_content' => 'get_10_off',
- ];
- $referrer = 'https://mywebsite.com/bfpromo/?' . http_build_query($referrerUTMParams);
-
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $subscriber = $this->api->create_subscriber($emailAddress);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to form.
- $result = $this->api->add_subscriber_to_form(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_id: $subscriber['subscriber']['id'],
- referrer: $referrer
- );
-
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals(
- $result['subscriber']['id'],
- $subscriber['subscriber']['id']
- );
-
- // Assert referrer data set for form subscriber.
- $this->assertEquals(
- $result['subscriber']['referrer'],
- $referrer
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['source'],
- $referrerUTMParams['utm_source']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['medium'],
- $referrerUTMParams['utm_medium']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['campaign'],
- $referrerUTMParams['utm_campaign']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['term'],
- $referrerUTMParams['utm_term']
- );
- $this->assertEquals(
- $result['subscriber']['referrer_utm_parameters']['content'],
- $referrerUTMParams['utm_content']
- );
- }
-
- /**
- * Test that add_subscriber_to_form() returns a WP_Error when an invalid
- * form ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToFormWithInvalidFormID()
- {
- $result = $this->api->add_subscriber_to_form(
- form_id: 12345,
- subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_form() returns a WP_Error when a legacy
- * form ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToFormWithLegacyFormID()
- {
- $result = $this->api->add_subscriber_to_form(
- form_id: $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'],
- subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_form() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToformWithInvalidSubscriberID()
- {
- $result = $this->api->add_subscriber_to_form(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_id: 12345
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_legacy_form() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToLegacyForm()
- {
- // Create subscriber.
- $subscriber = $this->api->create_subscriber($this->generateEmailAddress());
-
- $this->assertNotInstanceOf(\WP_Error::class, $subscriber);
- $this->assertIsArray($subscriber);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to legacy form.
- $result = $this->api->add_subscriber_to_legacy_form(
- form_id: (int) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'],
- subscriber_id: $subscriber['subscriber']['id']
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals($result['subscriber']['id'], $subscriber['subscriber']['id']);
- }
-
- /**
- * Test that add_subscriber_to_legacy_form() returns a WP_Error when an invalid
- * form ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToLegacyFormWithInvalidFormID()
- {
- $result = $this->api->add_subscriber_to_legacy_form(
- form_id: 12345,
- subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_legacy_form() returns a WP_Error when a non-legacy
- * form ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToLegacyFormWithNonLegacyFormID()
- {
- $result = $this->api->add_subscriber_to_legacy_form(
- form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
- subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_legacy_form() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToLegacyFormWithInvalidSubscriberID()
- {
- $result = $this->api->add_subscriber_to_legacy_form(
- form_id: (int) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'],
- subscriber_id: 12345
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_sequences() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSequences()
- {
- $result = $this->api->get_sequences();
-
- // Assert sequences and pagination exist.
- $this->assertDataExists($result, 'sequences');
- $this->assertPaginationExists($result);
-
- // Check first sequence in resultset has expected data.
- $sequence = $result['sequences'][0];
- $this->assertArrayHasKey('id', $sequence);
- $this->assertArrayHasKey('name', $sequence);
- $this->assertArrayHasKey('hold', $sequence);
- $this->assertArrayHasKey('repeat', $sequence);
- $this->assertArrayHasKey('created_at', $sequence);
- }
-
- /**
- * Test that get_sequences() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequencesWithTotalCount()
- {
- $result = $this->api->get_sequences(
- include_total_count: true
- );
-
- // Assert sequences and pagination exist.
- $this->assertDataExists($result, 'sequences');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_sequences() returns the expected data when
- * pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequencesPagination()
- {
- // Return one sequence.
- $result = $this->api->get_sequences(
- per_page: 1
- );
-
- // Assert sequences and pagination exist.
- $this->assertDataExists($result, 'sequences');
- $this->assertPaginationExists($result);
-
- // Assert a single sequence was returned.
- $this->assertCount(1, $result['sequences']);
-
- // 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_sequences(
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert sequences and pagination exist.
- $this->assertDataExists($result, 'sequences');
- $this->assertPaginationExists($result);
-
- // Assert a single sequence was returned.
- $this->assertCount(1, $result['sequences']);
-
- // Assert has_previous_page and has_next_page are correct.
- $this->assertTrue($result['pagination']['has_previous_page']);
- $this->assertFalse($result['pagination']['has_next_page']);
-
- // Use pagination to fetch previous page.
- $result = $this->api->get_sequences(
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert sequences and pagination exist.
- $this->assertDataExists($result, 'sequences');
- $this->assertPaginationExists($result);
-
- // Assert a single sequence was returned.
- $this->assertCount(1, $result['sequences']);
-
- // 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 add_subscriber_to_sequence_by_email() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testAddSubscriberToSequenceByEmail()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Add subscriber to sequence.
- $result = $this->api->add_subscriber_to_sequence_by_email(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- email_address: $emailAddress
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals(
- $result['subscriber']['email_address'],
- $emailAddress
- );
- }
-
- /**
- * Test that add_subscriber_to_sequence_by_email() returns a WP_Error when an invalid
- * sequence is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testAddSubscriberToSequenceByEmailWithInvalidSequenceID()
- {
- $result = $this->api->add_subscriber_to_sequence_by_email(
- sequence_id: 12345,
- email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_sequence_by_email() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testAddSubscriberToSequenceByEmailWithInvalidEmailAddress()
- {
- $result = $this->api->add_subscriber_to_sequence_by_email(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- email_address: 'not-an-email-address'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_sequence() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToSequence()
- {
- // Create subscriber.
- $subscriber = $this->api->create_subscriber($this->generateEmailAddress());
-
- $this->assertNotInstanceOf(\WP_Error::class, $subscriber);
- $this->assertIsArray($subscriber);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $subscriber['subscriber']['id'];
-
- // Add subscriber to sequence.
- $result = $this->api->add_subscriber_to_sequence(
- sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_id: $subscriber['subscriber']['id']
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertEquals($result['subscriber']['id'], $subscriber['subscriber']['id']);
- }
-
- /**
- * Test that add_subscriber_to_sequence() returns a WP_Error when an invalid
- * sequence ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToSequenceWithInvalidSequenceID()
- {
- $result = $this->api->add_subscriber_to_sequence(
- sequence_id: 12345,
- subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that add_subscriber_to_sequence() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testAddSubscriberToSequenceWithInvalidSubscriberID()
- {
- $result = $this->api->add_subscriber_to_sequence(
- sequence_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
- subscriber_id: 12345
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptions()
- {
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID']
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithTotalCount()
- {
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- include_total_count: true
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when a valid Sequence ID is specified and the subscription status
- * is cancelled.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithCancelledSubscriberState()
- {
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'cancelled'
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertEquals($result['subscribers'][0]['state'], 'cancelled');
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when a valid Sequence ID is specified and the added_after parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithAddedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- added_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['added_at']))
- );
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when a valid Sequence ID is specified and the added_before parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithAddedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- added_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['added_at']))
- );
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when a valid Sequence ID is specified and the created_after parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithCreatedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- created_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when a valid Sequence ID is specified and the created_before parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithCreatedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- created_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_sequence_subscriptions() returns the expected data
- * when a valid Sequence ID is specified and pagination parameters
- * and per_page limits are specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsPagination()
- {
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_sequence_subscriptions(
- sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'active',
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_sequence_subscriptions() returns a WP_Error when an invalid
- * Sequence ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithInvalidSequenceID()
- {
- $result = $this->api->get_sequence_subscriptions(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_sequence_subscriptions() returns a WP_Error when an invalid
- * subscriber state is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithInvalidSubscriberState()
- {
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- subscriber_state: 'not-a-valid-state'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_sequence_subscriptions() returns a WP_Error when invalid
- * pagination parameters are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSequenceSubscriptionsWithInvalidPagination()
- {
- $result = $this->api->get_sequence_subscriptions(
- sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
- after_cursor: 'not-a-valid-cursor'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_tags() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetTags()
- {
- $result = $this->api->get_tags();
-
- // Assert sequences and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Check first tag in resultset has expected data.
- $tag = $result['tags'][0];
- $this->assertArrayHasKey('id', $tag);
- $this->assertArrayHasKey('name', $tag);
- $this->assertArrayHasKey('created_at', $tag);
- }
-
- /**
- * Test that get_tags() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagsWithTotalCount()
- {
- $result = $this->api->get_tags(true);
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_tags() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagsPagination()
- {
- $result = $this->api->get_tags(
- per_page: 1
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert a single tag was returned.
- $this->assertCount(1, $result['tags']);
-
- // 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_tags(
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['tags']);
-
- // 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_tags(
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that create_tag() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testCreateTag()
- {
- $tagName = 'Tag Test ' . wp_rand();
-
- // Add mock handler for this API request, as the API doesn't provide
- // a method to delete tags to cleanup the test.
- $this->mockResponses(
- 201,
- 'Created',
- wp_json_encode(
- array(
- 'tag' => array(
- 'id' => 12345,
- 'name' => $tagName,
- 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z',
- ),
- )
- )
- );
-
- // Send request.
- $result = $this->api->create_tag($tagName);
-
- // Assert response contains correct data.
- $this->assertArrayHasKey('id', $result['tag']);
- $this->assertArrayHasKey('name', $result['tag']);
- $this->assertArrayHasKey('created_at', $result['tag']);
- $this->assertEquals($result['tag']['name'], $tagName);
- }
-
- /**
- * Test that create_tag() returns a WP_Error when creating
- * a blank tag.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testCreateTagBlank()
- {
- $result = $this->api->create_tag('');
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_tag() returns the expected data when creating
- * a tag that already exists.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testCreateTagThatExists()
- {
- $result = $this->api->create_tag($_ENV['CONVERTKIT_API_TAG_NAME']);
-
- // Assert response contains correct data.
- $this->assertArrayHasKey('id', $result['tag']);
- $this->assertArrayHasKey('name', $result['tag']);
- $this->assertArrayHasKey('created_at', $result['tag']);
- $this->assertEquals($result['tag']['name'], $_ENV['CONVERTKIT_API_TAG_NAME']);
- }
-
- /**
- * Test that create_tags() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateTags()
- {
- $tagNames = [
- 'Tag Test ' . wp_rand(),
- 'Tag Test ' . wp_rand(),
- ];
-
- // Add mock handler for this API request, as the API doesn't provide
- // a method to delete tags to cleanup the test.
- $this->mockResponses(
- 201,
- 'Created',
- wp_json_encode(
- array(
- 'tags' => array(
- array(
- 'id' => 12345,
- 'name' => $tagNames[0],
- 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z',
- ),
- array(
- 'id' => 23456,
- 'name' => $tagNames[1],
- 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z',
- ),
- ),
- 'failures' => array(),
- )
- )
- );
-
- $result = $this->api->create_tags($tagNames);
-
- // Assert no failures.
- $this->assertCount(0, $result['failures']);
- }
-
- /**
- * Test that create_tags() returns failures when attempting
- * to create blank tags.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateTagsBlank()
- {
- $result = $this->api->create_tags(
- [
- '',
- '',
- ]
- );
-
- // Assert failures.
- $this->assertCount(2, $result['failures']);
- }
-
- /**
- * Test that create_tags() returns the expected data when creating
- * tags that already exist.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateTagsThatExist()
- {
- $result = $this->api->create_tags(
- [
- $_ENV['CONVERTKIT_API_TAG_NAME'],
- $_ENV['CONVERTKIT_API_TAG_NAME_2'],
- ]
- );
-
- // Assert existing tags are returned.
- $this->assertCount(2, $result['tags']);
- $this->assertEquals($result['tags'][1]['name'], $_ENV['CONVERTKIT_API_TAG_NAME']);
- $this->assertEquals($result['tags'][0]['name'], $_ENV['CONVERTKIT_API_TAG_NAME_2']);
- }
-
- /**
- * Test that tag_subscriber_by_email() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testTagSubscriberByEmail()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $this->api->create_subscriber($emailAddress);
-
- // Tag subscriber by email.
- $subscriber = $this->api->tag_subscriber_by_email(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- email_address: $emailAddress
- );
- $this->assertArrayHasKey('subscriber', $subscriber);
- $this->assertArrayHasKey('id', $subscriber['subscriber']);
- $this->assertArrayHasKey('tagged_at', $subscriber['subscriber']);
-
- // Confirm the subscriber is tagged.
- $result = $this->api->get_subscriber_tags($subscriber['subscriber']['id']);
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert correct tag was assigned.
- $this->assertEquals($result['tags'][0]['id'], $_ENV['CONVERTKIT_API_TAG_ID']);
- }
-
- /**
- * Test that tag_subscriber_by_email() returns a WP_Error when an invalid
- * tag is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testTagSubscriberByEmailWithInvalidTagID()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $this->api->create_subscriber($emailAddress);
-
- $result = $this->api->tag_subscriber_by_email(
- tag_id: 12345,
- email_address: $emailAddress
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that tag_subscriber_by_email() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testTagSubscriberByEmailWithInvalidEmailAddress()
- {
- $result = $this->api->tag_subscriber_by_email(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- email_address: 'not-an-email-address'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that tag_subscriber() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testTagSubscriber()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $subscriber = $this->api->create_subscriber($emailAddress);
-
- // Tag subscriber by email.
- $result = $this->api->tag_subscriber(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_id: $subscriber['subscriber']['id']
- );
- $this->assertArrayHasKey('subscriber', $result);
- $this->assertArrayHasKey('id', $result['subscriber']);
- $this->assertArrayHasKey('tagged_at', $result['subscriber']);
-
- // Confirm the subscriber is tagged.
- $result = $this->api->get_subscriber_tags($result['subscriber']['id']);
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert correct tag was assigned.
- $this->assertEquals($result['tags'][0]['id'], $_ENV['CONVERTKIT_API_TAG_ID']);
- }
-
- /**
- * Test that tag_subscriber() returns a WP_Error when an invalid
- * sequence ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testTagSubscriberWithInvalidTagID()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $subscriber = $this->api->create_subscriber($emailAddress);
-
- $result = $this->api->tag_subscriber(
- tag_id: 12345,
- subscriber_id: $subscriber['subscriber']['id']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that tag_subscriber() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testTagSubscriberWithInvalidSubscriberID()
- {
- $result = $this->api->tag_subscriber(
- tag_id: $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_id: 12345
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that remove_tag_from_subscriber() works.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testRemoveTagFromSubscriber()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $this->api->create_subscriber($emailAddress);
-
- // Tag subscriber by email.
- $subscriber = $this->api->tag_subscriber_by_email(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- email_address: $emailAddress
- );
-
- // Remove tag from subscriber.
- $result = $this->api->remove_tag_from_subscriber(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_id: $subscriber['subscriber']['id']
- );
-
- // Confirm that the subscriber no longer has the tag.
- $result = $this->api->get_subscriber_tags($subscriber['subscriber']['id']);
- $this->assertIsArray($result['tags']);
- $this->assertCount(0, $result['tags']);
- }
-
- /**
- * Test that remove_tag_from_subscriber() returns a WP_Error when an invalid
- * tag ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testRemoveTagFromSubscriberWithInvalidTagID()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $this->api->create_subscriber($emailAddress);
-
- // Tag subscriber by email.
- $subscriber = $this->api->tag_subscriber_by_email(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- email_address: $emailAddress
- );
-
- // Remove tag from subscriber.
- $result = $this->api->remove_tag_from_subscriber(
- tag_id: 12345,
- subscriber_id: $subscriber['subscriber']['id']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that remove_tag_from_subscriber() returns a WP_Error when an invalid
- * subscriber ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testRemoveTagFromSubscriberWithInvalidSubscriberID()
- {
- $result = $this->api->remove_tag_from_subscriber(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_id: 12345
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that remove_tag_from_subscriber() works.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testRemoveTagFromSubscriberByEmail()
- {
- // Create subscriber.
- $emailAddress = $this->generateEmailAddress();
- $this->api->create_subscriber($emailAddress);
-
- // Tag subscriber by email.
- $subscriber = $this->api->tag_subscriber_by_email(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- email_address: $emailAddress
- );
-
- // Remove tag from subscriber.
- $result = $this->api->remove_tag_from_subscriber(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_id: $subscriber['subscriber']['id']
- );
-
- // Confirm that the subscriber no longer has the tag.
- $result = $this->api->get_subscriber_tags($subscriber['subscriber']['id']);
- $this->assertIsArray($result['tags']);
- $this->assertCount(0, $result['tags']);
- }
-
- /**
- * Test that remove_tag_from_subscriber() returns a WP_Error when an invalid
- * tag ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testRemoveTagFromSubscriberByEmailWithInvalidTagID()
- {
- $result = $this->api->remove_tag_from_subscriber_by_email(
- tag_id: 12345,
- email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that remove_tag_from_subscriber() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testRemoveTagFromSubscriberByEmailWithInvalidEmailAddress()
- {
- $result = $this->api->remove_tag_from_subscriber_by_email(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- email_address: 'not-an-email-address'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptions()
- {
- $result = $this->api->get_tag_subscriptions( (int) $_ENV['CONVERTKIT_API_TAG_ID']);
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithTotalCount()
- {
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- include_total_count: true
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified and the subscription status
- * is bounced.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithCancelledSubscriberState()
- {
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'cancelled'
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertEquals($result['subscribers'][0]['state'], 'cancelled');
- }
-
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified and the added_after parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithTaggedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- tagged_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['tagged_at']))
- );
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified and the tagged_before parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithTaggedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- tagged_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['tagged_at']))
- );
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified and the created_after parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithCreatedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- created_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified and the created_before parameter
- * is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithCreatedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- created_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_tag_subscriptions() returns the expected data
- * when a valid Tag ID is specified and pagination parameters
- * and per_page limits are specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsPagination()
- {
- $result = $this->api->get_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_tag_subscriptions(
- tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
- subscriber_state: 'active',
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_tag_subscriptions() returns a WP_Error when
- * an invalid Tag ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetTagSubscriptionsWithInvalidTagID()
- {
- $result = $this->api->get_tag_subscriptions(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_subscribers() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribers()
- {
- $result = $this->api->get_subscribers();
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithTotalCount()
- {
- $result = $this->api->get_subscribers(
- include_total_count: true
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_subscribers() returns the expected data when
- * searching by an email address.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersByEmailAddress()
- {
- $result = $this->api->get_subscribers(
- email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert correct subscriber returned.
- $this->assertEquals(
- $result['subscribers'][0]['email_address'],
- $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
- );
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the subscription status is cancelled.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithCancelledSubscriberState()
- {
- $result = $this->api->get_subscribers(
- subscriber_state: 'cancelled'
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertEquals($result['subscribers'][0]['state'], 'cancelled');
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the created_after parameter is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithCreatedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_subscribers(
- created_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertGreaterThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the created_before parameter is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithCreatedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_subscribers(
- created_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Check the correct subscribers were returned.
- $this->assertLessThanOrEqual(
- $date->format('Y-m-d'),
- date('Y-m-d', strtotime($result['subscribers'][0]['created_at']))
- );
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the updated_after parameter is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithUpdatedAfterParam()
- {
- $date = new \DateTime('2022-01-01');
- $result = $this->api->get_subscribers(
- updated_after: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the updated_before parameter is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithUpdatedBeforeParam()
- {
- $date = new \DateTime('2024-01-01');
- $result = $this->api->get_subscribers(
- updated_before: $date
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the sort_field parameter is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithSortFieldParam()
- {
- $result = $this->api->get_subscribers(
- sort_field: 'id'
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert sorting is honored by ID in descending (default) order.
- $this->assertLessThanOrEqual(
- $result['subscribers'][0]['id'],
- $result['subscribers'][1]['id']
- );
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when the sort_order parameter is used.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithSortOrderParam()
- {
- $result = $this->api->get_subscribers(
- sort_field: 'id',
- sort_order: 'asc'
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert sorting is honored by ID (default) in ascending order.
- $this->assertGreaterThanOrEqual(
- $result['subscribers'][0]['id'],
- $result['subscribers'][1]['id']
- );
- }
-
- /**
- * Test that get_subscribers() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersPagination()
- {
- // Return one broadcast.
- $result = $this->api->get_subscribers(
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single subscriber was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_subscribers(
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
-
- // Assert a single broadcast was returned.
- $this->assertCount(1, $result['subscribers']);
-
- // 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_subscribers(
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert subscribers and pagination exist.
- $this->assertDataExists($result, 'subscribers');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_subscribers() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithInvalidEmailAddress()
- {
- $result = $this->api->get_subscribers(
- email_address: 'not-an-email-address'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that get_subscribers() returns a WP_Error when an invalid
- * subscriber state is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithInvalidSubscriberState()
- {
- $result = $this->api->get_subscribers(
- subscriber_state: 'not-a-valid-state'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that get_subscribers() returns a WP_Error when an invalid
- * sort field is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithInvalidSortFieldParam()
- {
- $result = $this->api->get_subscribers(
- sort_field: 'not-a-valid-sort-field'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that get_subscribers() returns a WP_Error when an invalid
- * sort order is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithInvalidSortOrderParam()
- {
- $result = $this->api->get_subscribers(
- sort_order: 'not-a-valid-sort-order'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that get_subscribers() returns a WP_Error when an invalid
- * pagination parameters are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscribersWithInvalidPagination()
- {
- $result = $this->api->get_subscribers(
- after_cursor: 'not-a-valid-cursor'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that create_subscriber() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriber()
- {
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
- }
-
- /**
- * Test that create_subscriber() returns the expected data
- * when a first name is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriberWithFirstName()
- {
- $firstName = 'FirstName';
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber(
- email_address: $emailAddress,
- first_name: $firstName
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
- $this->assertEquals($result['subscriber']['first_name'], $firstName);
- }
-
- /**
- * Test that create_subscriber() returns the expected data
- * when a subscriber state is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriberWithSubscriberState()
- {
- $subscriberState = 'cancelled';
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber(
- email_address: $emailAddress,
- subscriber_state: $subscriberState
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
- $this->assertEquals($result['subscriber']['state'], $subscriberState);
- }
-
- /**
- * Test that create_subscriber() returns the expected data
- * when custom field data is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriberWithCustomFields()
- {
- $lastName = 'LastName';
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber(
- email_address: $emailAddress,
- fields: [
- 'last_name' => $lastName,
- ]
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
- $this->assertEquals($result['subscriber']['fields']['last_name'], $lastName);
- }
-
- /**
- * Test that create_subscriber() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriberWithInvalidEmailAddress()
- {
- $result = $this->api->create_subscriber(
- email_address: 'not-an-email-address'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_subscriber() returns a WP_Error when an invalid
- * subscriber state is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriberWithInvalidSubscriberState()
- {
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber(
- email_address: $emailAddress,
- subscriber_state: 'not-a-valid-state'
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_subscriber() returns the expected data
- * when an invalid custom field is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscriberWithInvalidCustomFields()
- {
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber(
- email_address: $emailAddress,
- fields: [
- 'not_a_custom_field' => 'value',
- ]
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
- }
-
- /**
- * Test that create_subscribers() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscribers()
- {
- $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);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- foreach ($result['subscribers'] as $i => $subscriber) {
- $this->subscriber_ids[] = $subscriber['id'];
- }
-
- // Assert no failures.
- $this->assertCount(0, $result['failures']);
-
- // Assert subscribers exists with correct data.
- foreach ($result['subscribers'] as $i => $subscriber) {
- $this->assertEquals($subscriber['email_address'], $subscribers[ $i ]['email_address']);
- }
- }
-
- /**
- * Test that create_subscribers() returns a WP_Error when no data is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscribersWithBlankData()
- {
- $result = $this->api->create_subscribers(
- [
- [],
- ]
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_subscribers() returns the expected data when invalid email addresses
- * are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateSubscribersWithInvalidEmailAddresses()
- {
- $subscribers = [
- [
- 'email_address' => 'not-an-email-address',
- ],
- [
- 'email_address' => 'not-an-email-address-again',
- ],
- ];
- $result = $this->api->create_subscribers($subscribers);
-
- // Assert no subscribers were added.
- $this->assertCount(0, $result['subscribers']);
- $this->assertCount(2, $result['failures']);
- }
-
- /**
- * Test that get_subscriber_id() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriberID()
- {
- $subscriber_id = $this->api->get_subscriber_id($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']);
- $this->assertIsInt($subscriber_id);
- $this->assertEquals($subscriber_id, (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
- }
-
- /**
- * Test that get_subscriber_id() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriberIDWithInvalidEmailAddress()
- {
- $result = $this->api->get_subscriber_id('not-an-email-address');
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_subscriber_id() return false when no subscriber found
- * matching the given email address.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriberIDWithNotSubscribedEmailAddress()
- {
- $result = $this->api->get_subscriber_id('not-a-subscriber@test.com');
- $this->assertFalse($result);
- }
-
- /**
- * Test that get_subscriber() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriber()
- {
- $result = $this->api->get_subscriber( (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
- $this->assertEquals($result['subscriber']['email_address'], $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']);
- }
-
- /**
- * Test that get_subscriber() returns a WP_Error when an invalid
- * subscriber ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriberWithInvalidSubscriberID()
- {
- $result = $this->api->get_subscriber(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that update_subscriber() works when no changes are made.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUpdateSubscriberWithNoChanges()
- {
- $result = $this->api->update_subscriber($_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
-
- // Assert subscriber exists with correct data.
- $this->assertEquals($result['subscriber']['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
- $this->assertEquals($result['subscriber']['email_address'], $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']);
- }
-
- /**
- * Test that update_subscriber() works when updating the subscriber's first name.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUpdateSubscriberFirstName()
- {
- // Add a subscriber.
- $firstName = 'FirstName';
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber created with no first name.
- $this->assertNull($result['subscriber']['first_name']);
-
- // Get subscriber ID.
- $subscriberID = $result['subscriber']['id'];
-
- // Update subscriber's first name.
- $result = $this->api->update_subscriber(
- subscriber_id: $subscriberID,
- first_name: $firstName
- );
-
- // Assert changes were made.
- $this->assertEquals($result['subscriber']['id'], $subscriberID);
- $this->assertEquals($result['subscriber']['first_name'], $firstName);
- }
-
- /**
- * Test that update_subscriber() works when updating the subscriber's email address.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUpdateSubscriberEmailAddress()
- {
- // Add a subscriber.
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber created.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
-
- // Get subscriber ID.
- $subscriberID = $result['subscriber']['id'];
-
- // Update subscriber's email address.
- $newEmail = $this->generateEmailAddress();
- $result = $this->api->update_subscriber(
- subscriber_id: $subscriberID,
- email_address: $newEmail
- );
-
- // Assert changes were made.
- $this->assertEquals($result['subscriber']['id'], $subscriberID);
- $this->assertEquals($result['subscriber']['email_address'], $newEmail);
- }
-
- /**
- * Test that update_subscriber() works when updating the subscriber's custom fields.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUpdateSubscriberCustomFields()
- {
- // Add a subscriber.
- $lastName = 'LastName';
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set subscriber_id to ensure subscriber is unsubscribed after test.
- $this->subscriber_ids[] = $result['subscriber']['id'];
-
- // Assert subscriber created.
- $this->assertEquals($result['subscriber']['email_address'], $emailAddress);
-
- // Get subscriber ID.
- $subscriberID = $result['subscriber']['id'];
-
- // Update subscriber's custom fields.
- $result = $this->api->update_subscriber(
- subscriber_id: $subscriberID,
- fields: [
- 'last_name' => $lastName,
- ]
- );
-
- // Assert changes were made.
- $this->assertEquals($result['subscriber']['id'], $subscriberID);
- $this->assertEquals($result['subscriber']['fields']['last_name'], $lastName);
- }
-
- /**
- * Test that update_subscriber() returns a WP_Error when an invalid
- * subscriber ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUpdateSubscriberWithInvalidSubscriberID()
- {
- $result = $this->api->update_subscriber(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that unsubscribe_by_email() works with a valid subscriber email address.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUnsubscribeByEmail()
- {
- // Add a subscriber.
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Wait a moment to ensure subscriber is created.
- sleep(3);
-
- // Unsubscribe.
- $this->assertNull($this->api->unsubscribe_by_email($emailAddress));
- }
-
- /**
- * Test that unsubscribe_by_email() returns a WP_Error when an email
- * address is specified that is not subscribed.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUnsubscribeByEmailWithNotSubscribedEmailAddress()
- {
- $result = $this->api->unsubscribe_by_email('not-subscribed@kit.com');
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that unsubscribe_by_email() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testUnsubscribeByEmailWithInvalidEmailAddress()
- {
- $result = $this->api->unsubscribe_by_email('invalid-email');
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that unsubscribe() works with a valid subscriber ID.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testUnsubscribe()
- {
- // Add a subscriber.
- $emailAddress = $this->generateEmailAddress();
- $result = $this->api->create_subscriber($emailAddress);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Wait a moment to ensure subscriber is created.
- sleep(3);
-
- // Unsubscribe.
- $this->assertNull($this->api->unsubscribe($result['subscriber']['id']));
- }
-
- /**
- * Test that unsubscribe() returns a WP_Error when an invalid
- * subscriber ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testUnsubscribeWithInvalidSubscriberID()
- {
- $result = $this->api->unsubscribe(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that get_subscriber_tags() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriberTags()
- {
- $result = $this->api->get_subscriber_tags( (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_subscriber_tags() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscriberTagsWithTotalCount()
- {
- $result = $this->api->get_subscriber_tags(
- subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
- include_total_count: true
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_subscriber_tags() returns a WP_Error when an invalid
- * subscriber ID is specified.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSubscriberTagsWithInvalidSubscriberID()
- {
- $result = $this->api->get_subscriber_tags(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that get_subscriber_tags() returns the expected data
- * when a valid Subscriber ID is specified and pagination parameters
- * and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSubscriberTagsPagination()
- {
- $result = $this->api->get_subscriber_tags(
- subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
- include_total_count: false,
- per_page: 1
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert a single tag was returned.
- $this->assertCount(1, $result['tags']);
-
- // 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_subscriber_tags(
- subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
- include_total_count: false,
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert a single tag was returned.
- $this->assertCount(1, $result['tags']);
-
- // 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_subscriber_tags(
- subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
- include_total_count: false,
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert tags and pagination exist.
- $this->assertDataExists($result, 'tags');
- $this->assertPaginationExists($result);
-
- // Assert a single tag was returned.
- $this->assertCount(1, $result['tags']);
- }
-
- /**
- * Test that get_email_templates() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetEmailTemplates()
- {
- $result = $this->api->get_email_templates();
-
- // Assert email templates and pagination exist.
- $this->assertDataExists($result, 'email_templates');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_email_templates() returns the expected data
- * when the total count is included.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetEmailTemplatesWithTotalCount()
- {
- $result = $this->api->get_email_templates(true);
-
- // Assert email templates and pagination exist.
- $this->assertDataExists($result, 'email_templates');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_email_templates() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetEmailTemplatesPagination()
- {
- // Return one broadcast.
- $result = $this->api->get_email_templates(
- include_total_count: false,
- per_page: 1
- );
-
- // Assert email templates and pagination exist.
- $this->assertDataExists($result, 'email_templates');
- $this->assertPaginationExists($result);
-
- // Assert a single email template was returned.
- $this->assertCount(1, $result['email_templates']);
-
- // 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_email_templates(
- include_total_count: false,
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert email templates and pagination exist.
- $this->assertDataExists($result, 'email_templates');
- $this->assertPaginationExists($result);
-
- // Assert a single email template was returned.
- $this->assertCount(1, $result['email_templates']);
-
- // 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_email_templates(
- include_total_count: false,
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert email templates and pagination exist.
- $this->assertDataExists($result, 'email_templates');
- $this->assertPaginationExists($result);
-
- // Assert a single email template was returned.
- $this->assertCount(1, $result['email_templates']);
-
- // 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 get_broadcasts() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetBroadcastsPagination()
- {
- // Return one broadcast.
- $result = $this->api->get_broadcasts(
- include_total_count: false,
- 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(
- include_total_count: false,
- after_cursor: $result['pagination']['end_cursor'],
- 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->assertTrue($result['pagination']['has_previous_page']);
- $this->assertTrue($result['pagination']['has_next_page']);
-
- // Use pagination to fetch previous page.
- $result = $this->api->get_broadcasts(
- include_total_count: false,
- before_cursor: $result['pagination']['start_cursor'],
- 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']);
- }
-
- /**
- * 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() returns a WP_Error 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() returns a WP_Error 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() returns a WP_Error 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() returns a WP_Error 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_webhooks() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetWebhooksPagination()
- {
- // Create webhooks first.
- $results = [
- $this->api->create_webhook(
- 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
- 'subscriber.subscriber_activate'
- ),
- $this->api->create_webhook(
- 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
- 'subscriber.subscriber_activate'
- ),
- ];
-
- // Set webhook_ids to ensure webhooks are deleted after test.
- $this->webhook_ids = [
- $results[0]['webhook']['id'],
- $results[1]['webhook']['id'],
- ];
-
- // Get webhooks.
- $result = $this->api->get_webhooks(
- include_total_count: false,
- per_page: 1
- );
-
- // Assert webhooks and pagination exist.
- $this->assertDataExists($result, 'webhooks');
- $this->assertPaginationExists($result);
-
- // Assert a single webhook was returned.
- $this->assertCount(1, $result['webhooks']);
-
- // 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_webhooks(
- include_total_count: false,
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert webhooks and pagination exist.
- $this->assertDataExists($result, 'webhooks');
- $this->assertPaginationExists($result);
-
- // Assert a single webhook was returned.
- $this->assertCount(1, $result['webhooks']);
-
- // Assert has_previous_page and has_next_page are correct.
- $this->assertTrue($result['pagination']['has_previous_page']);
- $this->assertFalse($result['pagination']['has_next_page']);
-
- // Use pagination to fetch previous page.
- $result = $this->api->get_webhooks(
- include_total_count: false,
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert webhooks and pagination exist.
- $this->assertDataExists($result, 'webhooks');
- $this->assertPaginationExists($result);
-
- // Assert a single webhook was returned.
- $this->assertCount(1, $result['webhooks']);
- }
-
- /**
- * Test that create_webhook(), get_webhooks() and delete_webhook() works.
- *
- * We do both, so we don't end up with unnecessary webhooks remaining
- * on the ConvertKit account when running tests.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateGetAndDeleteWebhook()
- {
- // Create a webhook first.
- $result = $this->api->create_webhook(
- url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
- event: 'subscriber.subscriber_activate'
- );
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Store ID.
- $id = $result['webhook']['id'];
-
- // Get webhooks.
- $result = $this->api->get_webhooks();
-
- // Assert webhooks and pagination exist.
- $this->assertDataExists($result, 'webhooks');
- $this->assertPaginationExists($result);
-
- // Get webhooks including total count.
- $result = $this->api->get_webhooks(true);
-
- // Assert webhooks and pagination exist.
- $this->assertDataExists($result, 'webhooks');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
-
- // Delete the webhook.
- $result = $this->api->delete_webhook($id);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- }
-
- /**
- * Test that create_webhook() works with an event parameter.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateWebhookWithEventParameter()
- {
- // Create a webhook.
- $url = 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds');
- $result = $this->api->create_webhook(
- url: $url,
- event: 'subscriber.form_subscribe',
- parameter: $_ENV['CONVERTKIT_API_FORM_ID']
- );
-
- // Confirm webhook created with correct data.
- $this->assertArrayHasKey('webhook', $result);
- $this->assertArrayHasKey('id', $result['webhook']);
- $this->assertArrayHasKey('target_url', $result['webhook']);
- $this->assertEquals($result['webhook']['target_url'], $url);
- $this->assertEquals($result['webhook']['event']['name'], 'form_subscribe');
- $this->assertEquals($result['webhook']['event']['form_id'], $_ENV['CONVERTKIT_API_FORM_ID']);
-
- // Delete the webhook.
- $result = $this->api->delete_webhook($result['webhook']['id']);
- }
-
- /**
- * Test that create_webhook() throws an InvalidArgumentException when an invalid
- * event is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateWebhookWithInvalidEvent()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->api->create_webhook(
- url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
- event: 'invalid.event'
- );
- }
-
- /**
- * Test that delete_webhook() returns a WP_Error when an invalid
- * ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testDeleteWebhookWithInvalidID()
- {
- $result = $this->api->delete_webhook(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_custom_fields() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetCustomFields()
- {
- $result = $this->api->get_custom_fields();
-
- // Assert custom fields and pagination exist.
- $this->assertDataExists($result, 'custom_fields');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_custom_fields() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetCustomFieldsWithTotalCount()
- {
- $result = $this->api->get_custom_fields(true);
-
- // Assert custom fields and pagination exist.
- $this->assertDataExists($result, 'custom_fields');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_custom_fields() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetCustomFieldsPagination()
- {
- // Return one custom field.
- $result = $this->api->get_custom_fields(
- include_total_count: false,
- per_page: 1
- );
-
- // Assert custom fields and pagination exist.
- $this->assertDataExists($result, 'custom_fields');
- $this->assertPaginationExists($result);
-
- // Assert a single custom field was returned.
- $this->assertCount(1, $result['custom_fields']);
-
- // 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_custom_fields(
- include_total_count: false,
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert custom fields and pagination exist.
- $this->assertDataExists($result, 'custom_fields');
- $this->assertPaginationExists($result);
-
- // Assert a single custom field was returned.
- $this->assertCount(1, $result['custom_fields']);
-
- // 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_custom_fields(
- include_total_count: false,
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert custom fields and pagination exist.
- $this->assertDataExists($result, 'custom_fields');
- $this->assertPaginationExists($result);
-
- // Assert a single custom field was returned.
- $this->assertCount(1, $result['custom_fields']);
-
- // 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_custom_field() works.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testCreateCustomField()
- {
- $label = 'Custom Field ' . wp_rand();
- $result = $this->api->create_custom_field($label);
-
- // Test array was returned.
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Set custom_field_ids to ensure custom fields are deleted after test.
- $this->custom_field_ids[] = $result['custom_field']['id'];
-
- $this->assertArrayHasKey('custom_field', $result);
- $this->assertArrayHasKey('id', $result['custom_field']);
- $this->assertArrayHasKey('name', $result['custom_field']);
- $this->assertArrayHasKey('key', $result['custom_field']);
- $this->assertArrayHasKey('label', $result['custom_field']);
- $this->assertEquals($result['custom_field']['label'], $label);
- }
-
- /**
- * Test that create_custom_field() returns a WP_Error when a blank
- * label is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateCustomFieldWithBlankLabel()
- {
- $result = $this->api->create_custom_field('');
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_custom_fields() works.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreateCustomFields()
- {
- $labels = [
- 'Custom Field ' . wp_rand(),
- 'Custom Field ' . wp_rand(),
+ $tagNames = [
+ 'Tag Test ' . wp_rand(),
+ 'Tag Test ' . wp_rand(),
];
- $result = $this->api->create_custom_fields($labels);
- // Test array was returned.
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
+ // Add mock handler for this API request, as the API doesn't provide
+ // a method to delete tags to cleanup the test.
+ $this->mockResponses(
+ 201,
+ 'Created',
+ wp_json_encode(
+ array(
+ 'tags' => array(
+ array(
+ 'id' => 12345,
+ 'name' => $tagNames[0],
+ 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z',
+ ),
+ array(
+ 'id' => 23456,
+ 'name' => $tagNames[1],
+ 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z',
+ ),
+ ),
+ 'failures' => array(),
+ )
+ )
+ );
- // Set custom_field_ids to ensure custom fields are deleted after test.
- foreach ($result['custom_fields'] as $index => $customField) {
- $this->custom_field_ids[] = $customField['id'];
- }
+ $result = $this->api->create_tags($tagNames);
// Assert no failures.
$this->assertCount(0, $result['failures']);
-
- // Confirm result is an array comprising of each custom field that was created.
- $this->assertIsArray($result['custom_fields']);
}
-
/**
- * Test that update_custom_field() works.
+ * 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 testUpdateCustomField()
+ public function testCreateUpdateAndDeleteDraftBroadcast()
{
- // Create custom field.
- $label = 'Custom Field ' . wp_rand();
- $result = $this->api->create_custom_field($label);
-
- // Test array was returned.
+ // 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 ID.
- $id = $result['custom_field']['id'];
-
- // Set custom_field_ids to ensure custom fields are deleted after test.
- $this->custom_field_ids[] = $result['custom_field']['id'];
-
- // Change label.
- $newLabel = 'Custom Field ' . wp_rand();
- $this->api->update_custom_field($id, $newLabel);
-
- // Confirm label changed.
- $customFields = $this->api->get_custom_fields();
- foreach ($customFields['custom_fields'] as $customField) {
- if ($customField['id'] === $id) {
- $this->assertEquals($customField['label'], $newLabel);
- }
- }
- }
-
- /**
- * Test that update_custom_field() returns a WP_Error when an
- * invalid custom field ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testUpdateCustomFieldWithInvalidID()
- {
- $result = $this->api->update_custom_field(12345, 'Something');
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
+ // Store Broadcast ID.
+ $broadcastID = $result['broadcast']['id'];
- /**
- * Test that delete_custom_field() works.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testDeleteCustomField()
- {
- // Create custom field.
- $label = 'Custom Field ' . wp_rand();
- $result = $this->api->create_custom_field($label);
+ // 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']);
- // Test array was returned.
+ // 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);
- // Store ID.
- $id = $result['custom_field']['id'];
-
- // Delete custom field as tests passed.
- $this->api->delete_custom_field($id);
-
- // Confirm custom field no longer exists.
- $customFields = $this->api->get_custom_fields();
- foreach ($customFields['custom_fields'] as $customField) {
- $this->assertNotEquals($customField['id'], $id);
- }
- }
+ // 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']);
- /**
- * Test that delete_custom_field() returns a WP_Error when an
- * invalid custom field ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testDeleteCustomFieldWithInvalidID()
- {
- $result = $this->api->delete_custom_field(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
+ // Delete Broadcast.
+ $result = $this->api->delete_broadcast($broadcastID);
+ $this->assertNotInstanceOf(\WP_Error::class, $result);
}
-
/**
* Test that the `form_subscribe()` function returns the expected data.
*
@@ -5570,28 +1444,67 @@ public function testSequenceSubscribeWithInvalidEmailAddress()
/**
* Test that the `get_posts()` function returns expected data.
*
- * @since 1.0.0
+ * @since 1.0.0
+ */
+ public function testGetPosts()
+ {
+ $result = $this->api->get_posts();
+
+ // Test array was returned.
+ $this->assertNotInstanceOf(\WP_Error::class, $result);
+ $this->assertIsArray($result);
+
+ // Test expected response keys exist.
+ $this->assertArrayHasKey('total_posts', $result);
+ $this->assertArrayHasKey('page', $result);
+ $this->assertArrayHasKey('total_pages', $result);
+ $this->assertArrayHasKey('posts', $result);
+
+ // Test first post within posts array.
+ $this->assertArrayHasKey('id', reset($result['posts']));
+ $this->assertArrayHasKey('title', reset($result['posts']));
+ $this->assertArrayHasKey('url', reset($result['posts']));
+ $this->assertArrayHasKey('published_at', reset($result['posts']));
+ $this->assertArrayHasKey('is_paid', reset($result['posts']));
+ }
+
+ /**
+ * Test that get_posts() returns the expected data
+ * when the post content is included.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPostsWithIncludeContent()
+ {
+ $this->markTestSkipped('Kit WordPress Libraries uses the wordpress/posts endpoint, which does not support the include_content parameter.');
+ }
+
+ /**
+ * Test that get_posts() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPostsWithTotalCount()
+ {
+ $this->markTestSkipped('Kit WordPress Libraries uses the wordpress/posts endpoint, which does not support the total_count parameter.');
+ }
+
+ /**
+ * Test that get_posts() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
*/
- public function testGetPosts()
+ public function testGetPostsPagination()
{
- $result = $this->api->get_posts();
-
- // Test array was returned.
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
-
- // Test expected response keys exist.
- $this->assertArrayHasKey('total_posts', $result);
- $this->assertArrayHasKey('page', $result);
- $this->assertArrayHasKey('total_pages', $result);
- $this->assertArrayHasKey('posts', $result);
-
- // Test first post within posts array.
- $this->assertArrayHasKey('id', reset($result['posts']));
- $this->assertArrayHasKey('title', reset($result['posts']));
- $this->assertArrayHasKey('url', reset($result['posts']));
- $this->assertArrayHasKey('published_at', reset($result['posts']));
- $this->assertArrayHasKey('is_paid', reset($result['posts']));
+ $this->markTestSkipped('Kit WordPress Libraries uses the wordpress/posts endpoint, which does not support the pagination parameter.');
}
/**
@@ -5757,7 +1670,7 @@ public function testGetAllPostsWithInvalidPostsPerRequestParameter()
*
* @since 1.3.8
*/
- public function testGetPostByID()
+ public function testGetPost()
{
$result = $this->api->get_post($_ENV['CONVERTKIT_API_POST_ID']);
$this->assertNotInstanceOf(\WP_Error::class, $result);
@@ -5774,19 +1687,6 @@ public function testGetPostByID()
$this->assertArrayHasKey('content', $result);
}
- /**
- * Test that the `get_post()` function returns a WP_Error when an invalid
- * Post ID is specified.
- *
- * @since 1.3.8
- */
- public function testGetPostByInvalidID()
- {
- $result = $this->api->get_post(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
/**
* Test that the `get_products()` function returns expected data.
*
@@ -6010,373 +1910,6 @@ public function testProfilesWithNoSignedSubscriberID()
$this->assertInstanceOf(\WP_Error::class, $result);
$this->assertEquals($result->get_error_code(), $this->errorCode);
}
-
- /**
- * Test that get_purchases() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetPurchases()
- {
- $result = $this->api->get_purchases();
-
- // Assert purchases and pagination exist.
- $this->assertDataExists($result, 'purchases');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_purchases() returns the expected data
- * when the total count is included.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetPurchasesWithTotalCount()
- {
- $result = $this->api->get_purchases(
- include_total_count: true
- );
-
- // Assert purchases and pagination exist.
- $this->assertDataExists($result, 'purchases');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_purchases() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetPurchasesPagination()
- {
- $result = $this->api->get_purchases(
- per_page: 1
- );
-
- // Assert purchases and pagination exist.
- $this->assertDataExists($result, 'purchases');
- $this->assertPaginationExists($result);
-
- // Assert a single purchase was returned.
- $this->assertCount(1, $result['purchases']);
-
- // 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_purchases(
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert purchases and pagination exist.
- $this->assertDataExists($result, 'purchases');
- $this->assertPaginationExists($result);
-
- // Assert a single purchase was returned.
- $this->assertCount(1, $result['purchases']);
-
- // 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_purchases(
- before_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert purchases and pagination exist.
- $this->assertDataExists($result, 'purchases');
- $this->assertPaginationExists($result);
-
- // Assert a single purchase was returned.
- $this->assertCount(1, $result['purchases']);
-
- // 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 get_purchases() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetPurchase()
- {
- $result = $this->api->get_purchases(
- per_page: 1
- );
-
- // Assert purchases and pagination exist.
- $this->assertDataExists($result, 'purchases');
- $this->assertPaginationExists($result);
-
- // Assert a single purchase was returned.
- $this->assertCount(1, $result['purchases']);
-
- // Get ID.
- $id = $result['purchases'][0]['id'];
-
- // Get purchase.
- $result = $this->api->get_purchase($id);
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertEquals($result['purchase']['id'], $id);
- }
-
- /**
- * Test that get_purchases() returns a WP_Error when an invalid
- * purchase ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetPurchaseWithInvalidID()
- {
- $result = $this->api->get_purchase(12345);
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_purchase() returns the expected data.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testCreatePurchase()
- {
- $result = $this->api->create_purchase(
- // Required fields.
- email_address: $this->generateEmailAddress(),
- transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'),
- 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.
- currency: 'usd',
- 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')
- );
-
- $this->assertNotInstanceOf(\WP_Error::class, $result);
- $this->assertIsArray($result);
- $this->assertArrayHasKey('transaction_id', $result['purchase']);
- }
-
- /**
- * Test that create_purchase() returns a WP_Error when an invalid
- * email address is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreatePurchaseWithInvalidEmailAddress()
- {
- $result = $this->api->create_purchase(
- email_address: 'not-an-email-address',
- transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'),
- products: [
- [
- 'name' => 'Floppy Disk (512k)',
- 'sku' => '7890-ijkl',
- 'pid' => 9999,
- 'lid' => 7777,
- 'quantity' => 2,
- 'unit_price' => 5.00,
- ],
- ]
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_purchase() returns a WP_Error when a blank
- * transaction ID is specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreatePurchaseWithBlankTransactionID()
- {
- $result = $this->api->create_purchase(
- email_address: $this->generateEmailAddress(),
- transaction_id: '',
- products: [
- [
- 'name' => 'Floppy Disk (512k)',
- 'sku' => '7890-ijkl',
- 'pid' => 9999,
- 'lid' => 7777,
- 'quantity' => 2,
- 'unit_price' => 5.00,
- ],
- ]
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that create_purchase() returns a WP_Error when no products
- * are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testCreatePurchaseWithNoProducts()
- {
- $result = $this->api->create_purchase(
- email_address: $this->generateEmailAddress(),
- transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'),
- products: []
- );
- $this->assertInstanceOf(\WP_Error::class, $result);
- $this->assertEquals($result->get_error_code(), $this->errorCode);
- }
-
- /**
- * Test that get_segments() returns the expected data.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSegments()
- {
- $result = $this->api->get_segments();
-
- // Assert segments and pagination exist.
- $this->assertDataExists($result, 'segments');
- $this->assertPaginationExists($result);
- }
-
- /**
- * Test that get_segments() returns the expected data
- * when the total count is included.
- *
- * @since 1.0.0
- *
- * @return void
- */
- public function testGetSegmentsWithTotalCount()
- {
- $result = $this->api->get_segments(
- include_total_count: true
- );
-
- // Assert segments and pagination exist.
- $this->assertDataExists($result, 'segments');
- $this->assertPaginationExists($result);
-
- // Assert total count is included.
- $this->assertArrayHasKey('total_count', $result['pagination']);
- $this->assertGreaterThan(0, $result['pagination']['total_count']);
- }
-
- /**
- * Test that get_segments() returns the expected data
- * when pagination parameters and per_page limits are specified.
- *
- * @since 2.0.0
- *
- * @return void
- */
- public function testGetSegmentsPagination()
- {
- $result = $this->api->get_segments(
- per_page: 1
- );
-
- // Assert segments and pagination exist.
- $this->assertDataExists($result, 'segments');
- $this->assertPaginationExists($result);
-
- // Assert a single segment was returned.
- $this->assertCount(1, $result['segments']);
-
- // 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_segments(
- after_cursor: $result['pagination']['end_cursor'],
- per_page: 1
- );
-
- // Assert segments and pagination exist.
- $this->assertDataExists($result, 'segments');
- $this->assertPaginationExists($result);
-
- // Assert a single segment was returned.
- $this->assertCount(1, $result['segments']);
-
- // 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_segments(
- before_cursor: $result['pagination']['start_cursor'],
- per_page: 1
- );
-
- // Assert segments and pagination exist.
- $this->assertDataExists($result, 'segments');
- $this->assertPaginationExists($result);
-
- // Assert a single segment was returned.
- $this->assertCount(1, $result['segments']);
-
- // 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 the `recommendations_script()` function returns expected data
* for a ConvertKit account that has the Creator Network enabled.
@@ -6647,56 +2180,4 @@ public function mockRefreshTokenResponse( $response, $parsed_args, $url )
'http_response' => null,
);
}
-
- /**
- * 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);
- }
-
- /**
- * Generates a unique email address for use in a test, comprising of a prefix,
- * date + time and PHP version number.
- *
- * This ensures that if tests are run in parallel, the same email address
- * isn't used for two tests across parallel testing runs.
- *
- * @since 2.0.0
- *
- * @param string $domain Domain (default: kit.com).
- *
- * @return string
- */
- private function generateEmailAddress($domain = 'kit.com')
- {
- return 'php-sdk-' . date('Y-m-d-H-i-s') . '-php-' . PHP_VERSION_ID . '@' . $domain;
- }
}
diff --git a/tests/Integration/TestsTrait.php b/tests/Integration/TestsTrait.php
new file mode 100644
index 0000000..4becfdd
--- /dev/null
+++ b/tests/Integration/TestsTrait.php
@@ -0,0 +1,6994 @@
+api->get_account();
+ $this->assertInstanceOf('stdClass', $result);
+
+ $result = get_object_vars($result);
+ $this->assertArrayHasKey('user', $result);
+ $this->assertArrayHasKey('account', $result);
+
+ $account = get_object_vars($result['account']);
+ $this->assertArrayHasKey('id', $account);
+ $this->assertArrayHasKey('name', $account);
+ $this->assertArrayHasKey('plan_type', $account);
+ $this->assertArrayHasKey('primary_email_address', $account);
+ $this->assertArrayHasKey('created_at', $account);
+ $this->assertArrayHasKey('plan', $account);
+ $this->assertArrayHasKey('sending_addresses', $account);
+ $this->assertArrayHasKey('timezone', $account);
+
+ $plan = get_object_vars($account['plan']);
+ $this->assertArrayHasKey('plan_type', $plan);
+ $this->assertArrayHasKey('interval', $plan);
+ $this->assertArrayHasKey('subscriber_limit', $plan);
+ $this->assertArrayHasKey('on_trial', $plan);
+ $this->assertArrayHasKey('trial_lapse_date', $plan);
+ $this->assertArrayHasKey('renews_at', $plan);
+ $this->assertArrayHasKey('cancels_at', $plan);
+
+ $timezone = get_object_vars($account['timezone']);
+ $this->assertArrayHasKey('name', $timezone);
+ $this->assertArrayHasKey('friendly_name', $timezone);
+ $this->assertArrayHasKey('utc_offset', $timezone);
+ }
+
+ /**
+ * Test that get_account_colors() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetAccountColors()
+ {
+ $result = $this->api->get_account_colors();
+ $this->assertInstanceOf('stdClass', $result);
+
+ $result = get_object_vars($result);
+ $this->assertArrayHasKey('colors', $result);
+ $this->assertIsArray($result['colors']);
+ }
+
+ /**
+ * Test that update_account_colors() updates the account's colors.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testUpdateAccountColors()
+ {
+ $result = $this->api->update_account_colors([
+ '#111111',
+ ]);
+ $this->assertInstanceOf('stdClass', $result);
+
+ $result = get_object_vars($result);
+ $this->assertArrayHasKey('colors', $result);
+ $this->assertIsArray($result['colors']);
+ $this->assertEquals($result['colors'][0], '#111111');
+ }
+
+ /**
+ * Test that get_creator_profile() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetCreatorProfile()
+ {
+ $result = $this->api->get_creator_profile();
+ $this->assertInstanceOf('stdClass', $result);
+
+ $result = get_object_vars($result);
+ $profile = get_object_vars($result['profile']);
+ $this->assertArrayHasKey('name', $profile);
+ $this->assertArrayHasKey('byline', $profile);
+ $this->assertArrayHasKey('bio', $profile);
+ $this->assertArrayHasKey('image_url', $profile);
+ $this->assertArrayHasKey('profile_url', $profile);
+ }
+
+ /**
+ * Test that get_email_stats() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetEmailStats()
+ {
+ $result = $this->api->get_email_stats();
+ $this->assertInstanceOf('stdClass', $result);
+
+ $result = get_object_vars($result);
+ $stats = get_object_vars($result['stats']);
+ $this->assertArrayHasKey('sent', $stats);
+ $this->assertArrayHasKey('clicked', $stats);
+ $this->assertArrayHasKey('opened', $stats);
+ $this->assertArrayHasKey('email_stats_mode', $stats);
+ $this->assertArrayHasKey('open_tracking_enabled', $stats);
+ $this->assertArrayHasKey('click_tracking_enabled', $stats);
+ $this->assertArrayHasKey('starting', $stats);
+ $this->assertArrayHasKey('ending', $stats);
+ }
+
+ /**
+ * Test that get_growth_stats() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetGrowthStats()
+ {
+ $result = $this->api->get_growth_stats();
+ $this->assertInstanceOf('stdClass', $result);
+
+ $result = get_object_vars($result);
+ $stats = get_object_vars($result['stats']);
+ $this->assertArrayHasKey('cancellations', $stats);
+ $this->assertArrayHasKey('net_new_subscribers', $stats);
+ $this->assertArrayHasKey('new_subscribers', $stats);
+ $this->assertArrayHasKey('subscribers', $stats);
+ $this->assertArrayHasKey('starting', $stats);
+ $this->assertArrayHasKey('ending', $stats);
+ }
+
+ /**
+ * Test that get_growth_stats() returns the expected data
+ * when a start date is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetGrowthStatsWithStartDate()
+ {
+ // Define start and end dates.
+ $starting = new DateTime('now');
+ $starting->modify('-7 days');
+ $ending = new DateTime('now');
+
+ // Send request.
+ $result = $this->api->get_growth_stats(
+ starting: $starting
+ );
+ $this->assertInstanceOf('stdClass', $result);
+
+ // Confirm response object contains expected keys.
+ $result = get_object_vars($result);
+ $stats = get_object_vars($result['stats']);
+ $this->assertArrayHasKey('cancellations', $stats);
+ $this->assertArrayHasKey('net_new_subscribers', $stats);
+ $this->assertArrayHasKey('new_subscribers', $stats);
+ $this->assertArrayHasKey('subscribers', $stats);
+ $this->assertArrayHasKey('starting', $stats);
+ $this->assertArrayHasKey('ending', $stats);
+
+ // Assert start and end dates were honored.
+ // Gets timezone offset for New York (-04:00 during DST, -05:00 otherwise).
+ $timezone = ( new DateTime() )->setTimezone(new DateTimeZone('America/New_York'))->format('P');
+ $this->assertEquals($stats['starting'], $starting->format('Y-m-d') . 'T00:00:00' . $timezone);
+ $this->assertEquals($stats['ending'], $ending->format('Y-m-d') . 'T23:59:59' . $timezone);
+ }
+
+ /**
+ * Test that get_growth_stats() returns the expected data
+ * when an end date is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetGrowthStatsWithEndDate()
+ {
+ // Define start and end dates.
+ $starting = new DateTime('now');
+ $starting->modify('-90 days');
+ $ending = new DateTime('now');
+ $ending->modify('-7 days');
+
+ // Send request.
+ $result = $this->api->get_growth_stats(
+ ending: $ending
+ );
+ $this->assertInstanceOf('stdClass', $result);
+
+ // Confirm response object contains expected keys.
+ $result = get_object_vars($result);
+ $stats = get_object_vars($result['stats']);
+ $this->assertArrayHasKey('cancellations', $stats);
+ $this->assertArrayHasKey('net_new_subscribers', $stats);
+ $this->assertArrayHasKey('new_subscribers', $stats);
+ $this->assertArrayHasKey('subscribers', $stats);
+ $this->assertArrayHasKey('starting', $stats);
+ $this->assertArrayHasKey('ending', $stats);
+
+ // Assert start and end dates were honored.
+ // Gets timezone offset for New York (-04:00 during DST, -05:00 otherwise).
+ $timezone = ( new DateTime() )->setTimezone(new DateTimeZone('America/New_York'))->format('P');
+ $this->assertEquals($stats['starting'], $starting->format('Y-m-d') . 'T00:00:00' . $timezone);
+ $this->assertEquals($stats['ending'], $ending->format('Y-m-d') . 'T23:59:59' . $timezone);
+ }
+
+ /**
+ * Test that get_forms() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetForms()
+ {
+ $result = $this->api->get_forms();
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Iterate through each form, confirming no landing pages were included.
+ foreach ($result->forms as $form) {
+ $form = get_object_vars($form);
+
+ // Assert shape of object is valid.
+ $this->assertArrayHasKey('id', $form);
+ $this->assertArrayHasKey('name', $form);
+ $this->assertArrayHasKey('created_at', $form);
+ $this->assertArrayHasKey('type', $form);
+ $this->assertArrayHasKey('format', $form);
+ $this->assertArrayHasKey('embed_js', $form);
+ $this->assertArrayHasKey('embed_url', $form);
+ $this->assertArrayHasKey('archived', $form);
+
+ // Assert form is not a landing page i.e embed.
+ $this->assertEquals($form['type'], 'embed');
+
+ // Assert form is not archived.
+ $this->assertFalse($form['archived']);
+ }
+ }
+
+ /**
+ * Test that get_forms() returns the expected data when
+ * the status is set to archived.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormsWithArchivedStatus()
+ {
+ $result = $this->api->get_forms(
+ status: 'archived'
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Iterate through each form, confirming no landing pages were included.
+ foreach ($result->forms as $form) {
+ $form = get_object_vars($form);
+
+ // Assert shape of object is valid.
+ $this->assertArrayHasKey('id', $form);
+ $this->assertArrayHasKey('name', $form);
+ $this->assertArrayHasKey('created_at', $form);
+ $this->assertArrayHasKey('type', $form);
+ $this->assertArrayHasKey('format', $form);
+ $this->assertArrayHasKey('embed_js', $form);
+ $this->assertArrayHasKey('embed_url', $form);
+ $this->assertArrayHasKey('archived', $form);
+
+ // Assert form is not a landing page i.e embed.
+ $this->assertEquals($form['type'], 'embed');
+
+ // Assert form is not archived.
+ $this->assertTrue($form['archived']);
+ }
+ }
+
+ /**
+ * Test that get_forms() returns the subscriber count
+ * when included in the `include` argument.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetFormsWithSubscriberCount()
+ {
+ $result = $this->api->get_forms(
+ include: ['subscriber_count']
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert subscriber count is included.
+ $this->assertArrayHasKey('subscriber_count', get_object_vars($result->forms[0]));
+ }
+
+ /**
+ * Test that get_forms() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormsWithTotalCount()
+ {
+ $result = $this->api->get_forms(
+ include_total_count: true
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_forms() returns the expected data when pagination parameters
+ * and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormsPagination()
+ {
+ $result = $this->api->get_forms(
+ per_page: 1
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert a single form was returned.
+ $this->assertCount(1, $result->forms);
+
+ // 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_forms(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert a single form was returned.
+ $this->assertCount(1, $result->forms);
+
+ // 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_forms(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert a single form was returned.
+ $this->assertCount(1, $result->forms);
+
+ // 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 get_landing_pages() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetLandingPages()
+ {
+ $result = $this->api->get_landing_pages();
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Iterate through each landing page, confirming no forms were included.
+ foreach ($result->forms as $form) {
+ $form = get_object_vars($form);
+
+ // Assert shape of object is valid.
+ $this->assertArrayHasKey('id', $form);
+ $this->assertArrayHasKey('name', $form);
+ $this->assertArrayHasKey('created_at', $form);
+ $this->assertArrayHasKey('type', $form);
+ $this->assertArrayHasKey('format', $form);
+ $this->assertArrayHasKey('embed_js', $form);
+ $this->assertArrayHasKey('embed_url', $form);
+ $this->assertArrayHasKey('archived', $form);
+
+ // Assert form is a landing page i.e. hosted.
+ $this->assertEquals($form['type'], 'hosted');
+
+ // Assert form is not archived.
+ $this->assertFalse($form['archived']);
+ }
+ }
+
+ /**
+ * Test that get_landing_pages() returns the expected data when
+ * the status is set to archived.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetLandingPagesWithArchivedStatus()
+ {
+ $result = $this->api->get_forms(
+ status: 'archived'
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert no landing pages are returned, as the account doesn't have any archived landing pages.
+ $this->assertCount(0, $result->forms);
+ }
+
+ /**
+ * Test that get_landing_pages() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetLandingPagesWithTotalCount()
+ {
+ $result = $this->api->get_landing_pages(
+ include_total_count: true
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_landing_pages() returns the expected data when pagination parameters
+ * and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetLandingPagesPagination()
+ {
+ $result = $this->api->get_landing_pages(
+ per_page: 1
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert a single form was returned.
+ $this->assertCount(1, $result->forms);
+
+ // 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_landing_pages(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert a single form was returned.
+ $this->assertCount(1, $result->forms);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertTrue($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+
+ // Use pagination to fetch previous page.
+ $result = $this->api->get_landing_pages(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'forms');
+ $this->assertPaginationExists($result);
+
+ // Assert a single form was returned.
+ $this->assertCount(1, $result->forms);
+
+ // 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 get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptions()
+ {
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID']
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when the slim parameter is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithSlimParameter()
+ {
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ slim: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Confirm custom field values are excluded from the data.
+ $subscriber = get_object_vars($result->subscribers[0]);
+ $this->assertArrayNotHasKey('fields', $subscriber);
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithTotalCount()
+ {
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ include_total_count: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified and the subscription status
+ * is cancelled.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithCancelledSubscriberState()
+ {
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_state: 'cancelled'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertEquals($result->subscribers[0]->state, 'cancelled');
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified and the added_after parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithAddedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ added_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->added_at))
+ );
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified and the added_before parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithAddedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ added_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->added_at))
+ );
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified and the created_after parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithCreatedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ created_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified and the created_before parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithCreatedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ created_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_form_subscriptions() returns the expected data
+ * when a valid Form ID is specified and pagination parameters
+ * and per_page limits are specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsPagination()
+ {
+ $result = $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ per_page: 1
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_form_subscriptions() throws a ClientException when an invalid
+ * Form ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithInvalidFormID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_form_subscriptions(
+ form_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that get_form_subscriptions() throws a ClientException when an invalid
+ * subscriber state is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithInvalidSubscriberState()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_state: 'not-a-valid-state'
+ );
+ });
+ }
+
+ /**
+ * Test that get_form_subscriptions() throws a ClientException when invalid
+ * pagination parameters are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetFormSubscriptionsWithInvalidPagination()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_form_subscriptions(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ after_cursor: 'not-a-valid-cursor'
+ );
+ });
+ }
+
+ /**
+ * Test that get_sequences() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSequences()
+ {
+ $result = $this->api->get_sequences();
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'sequences');
+ $this->assertPaginationExists($result);
+
+ // Check first sequence in resultset has expected data.
+ $sequence = get_object_vars($result->sequences[0]);
+ $this->assertArrayHasKey('id', $sequence);
+ $this->assertArrayHasKey('name', $sequence);
+ $this->assertArrayHasKey('hold', $sequence);
+ $this->assertArrayHasKey('repeat', $sequence);
+ $this->assertArrayHasKey('created_at', $sequence);
+ }
+
+ /**
+ * Test that get_sequences() returns the expected data
+ * when the include parameter is used.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetSequencesWithIncludeParam()
+ {
+ $result = $this->api->get_sequences(
+ include: ['stats']
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'sequences');
+ $this->assertPaginationExists($result);
+
+ // Assert fields are included.
+ $this->assertArrayHasKey('stats', get_object_vars($result->sequences[0]));
+ }
+
+ /**
+ * Test that get_sequences() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequencesWithTotalCount()
+ {
+ $result = $this->api->get_sequences(
+ include_total_count: true
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'sequences');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_sequences() returns the expected data when
+ * pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequencesPagination()
+ {
+ $result = $this->api->get_sequences(
+ per_page: 1
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'sequences');
+ $this->assertPaginationExists($result);
+
+ // Assert a single sequence was returned.
+ $this->assertCount(1, $result->sequences);
+
+ // 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_sequences(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'sequences');
+ $this->assertPaginationExists($result);
+
+ // Assert a single sequence was returned.
+ $this->assertCount(1, $result->sequences);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertTrue($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+
+ // Use pagination to fetch previous page.
+ $result = $this->api->get_sequences(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'sequences');
+ $this->assertPaginationExists($result);
+
+ // Assert a single sequence was returned.
+ $this->assertCount(1, $result->sequences);
+ }
+
+ /**
+ * Test that create_sequence(), update_sequence() and delete_sequence() works.
+ *
+ * We do all tests in a single function, so we don't end up with unnecessary
+ * Sequences remaining on the Kit account when running tests, which might impact
+ * other tests that expect (or do not expect) specific Sequences.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testCreateUpdateAndDeleteSequence()
+ {
+ // Create a sequence.
+ $result = $this->api->create_sequence(
+ name: 'Test Sequence',
+ email_address: 'wordpress@convertkit.com',
+ email_template_id: (int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'],
+ send_days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
+ send_hour: 12,
+ time_zone: 'America/Los_Angeles',
+ active: false,
+ repeat: false,
+ hold: false
+ );
+ $sequenceID = $result->sequence->id;
+
+ // Confirm the Sequence saved.
+ $result = get_object_vars($result->sequence);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Test Sequence', $result['name']);
+ $this->assertEquals('wordpress@convertkit.com', $result['email_address']);
+ $this->assertEquals((int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'], $result['email_template_id']);
+ $this->assertEquals(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], $result['send_days']);
+ $this->assertEquals(12, $result['send_hour']);
+ $this->assertEquals('America/Los_Angeles', $result['time_zone']);
+ $this->assertEquals(false, $result['active']);
+ $this->assertEquals(false, $result['repeat']);
+ $this->assertEquals(false, $result['hold']);
+
+ // Update the existing sequence.
+ $result = $this->api->update_sequence(
+ sequence_id: $sequenceID,
+ name: 'Edited Test Sequence',
+ email_address: 'wordpress@convertkit.com',
+ email_template_id: (int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'],
+ send_days: ['saturday', 'sunday'],
+ send_hour: 13,
+ time_zone: 'America/New_York',
+ active: true,
+ repeat: true,
+ hold: true
+ );
+
+ // Confirm the changes saved.
+ $result = get_object_vars($result->sequence);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Edited Test Sequence', $result['name']);
+ $this->assertEquals('wordpress@convertkit.com', $result['email_address']);
+ $this->assertEquals((int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'], $result['email_template_id']);
+ $this->assertEquals(['saturday', 'sunday'], $result['send_days']);
+ $this->assertEquals(13, $result['send_hour']);
+ $this->assertEquals('America/New_York', $result['time_zone']);
+ $this->assertEquals(true, $result['active']);
+ $this->assertEquals(true, $result['repeat']);
+ $this->assertEquals(true, $result['hold']);
+
+ // Delete Sequence.
+ $this->api->delete_sequence($sequenceID);
+ $this->assertLastResponseStatusCode(204);
+ }
+
+ /**
+ * Test that get_sequence() returns the expected data.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequence()
+ {
+ $result = $this->api->get_sequence((int) $_ENV['CONVERTKIT_API_SEQUENCE_ID']);
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('sequence', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->sequence));
+ }
+
+ /**
+ * Test that get_sequence() returns the expected data.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetSequenceWithIncludeParam()
+ {
+ $result = $this->api->get_sequence(
+ (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ include: ['stats']
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('sequence', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->sequence));
+
+ // Assert stats are included.
+ $this->assertArrayHasKey('stats', get_object_vars($result->sequence));
+ }
+
+ /**
+ * Test that update_sequence() throws a ClientException when an invalid
+ * sequence ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testUpdateSequenceWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_sequence(12345);
+ });
+ }
+
+ /**
+ * Test that delete_sequence() throws a ClientException when an invalid
+ * sequence ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testDeleteSequenceWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->delete_sequence(12345);
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_sequence_by_email() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToSequenceByEmail()
+ {
+ // 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;
+
+ // Add subscriber to sequence.
+ $result = $this->api->add_subscriber_to_sequence_by_email(
+ sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ email_address: $emailAddress
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(
+ get_object_vars($result->subscriber)['email_address'],
+ $emailAddress
+ );
+ }
+
+ /**
+ * Test that add_subscriber_to_sequence_by_email() throws a ClientException when an invalid
+ * sequence is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToSequenceByEmailWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_sequence_by_email(
+ sequence_id: 12345,
+ email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
+ );
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_sequence_by_email() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToSequenceByEmailWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_sequence_by_email(
+ sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ email_address: 'not-an-email-address'
+ );
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_sequence() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToSequence()
+ {
+ // Create subscriber.
+ $subscriber = $this->api->create_subscriber(
+ email_address: $this->generateEmailAddress()
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $subscriber->subscriber->id;
+
+ // Add subscriber to sequence.
+ $result = $this->api->add_subscriber_to_sequence(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ subscriber_id: $subscriber->subscriber->id
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriber->subscriber->id);
+ }
+
+ /**
+ * Test that add_subscriber_to_sequence() throws a ClientException when an invalid
+ * sequence ID is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToSequenceWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_sequence(
+ sequence_id: 12345,
+ subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
+ );
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_sequence() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToSequenceWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_sequence(
+ sequence_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
+ subscriber_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptions()
+ {
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID']
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithTotalCount()
+ {
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ include_total_count: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when a valid Sequence ID is specified and the subscription status
+ * is cancelled.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithCancelledSubscriberState()
+ {
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ subscriber_state: 'cancelled'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertEquals($result->subscribers[0]->state, 'cancelled');
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when a valid Sequence ID is specified and the added_after parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithAddedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ added_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->added_at))
+ );
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when a valid Sequence ID is specified and the added_before parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithAddedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ added_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->added_at))
+ );
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when a valid Sequence ID is specified and the created_after parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithCreatedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ created_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when a valid Sequence ID is specified and the created_before parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithCreatedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ created_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() returns the expected data
+ * when a valid Sequence ID is specified and pagination parameters
+ * and per_page limits are specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsPagination()
+ {
+ $result = $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ per_page: 1
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() throws a ClientException when an invalid
+ * Sequence ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_sequence_subscriptions(
+ sequence_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() throws a ClientException when an invalid
+ * subscriber state is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithInvalidSubscriberState()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ subscriber_state: 'not-a-valid-state'
+ );
+ });
+ }
+
+ /**
+ * Test that get_sequence_subscriptions() throws a ClientException when invalid
+ * pagination parameters are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSequenceSubscriptionsWithInvalidPagination()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_sequence_subscriptions(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ after_cursor: 'not-a-valid-cursor'
+ );
+ });
+ }
+
+ /**
+ * Test that get_sequence_emails() returns the expected data.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmails()
+ {
+ $result = $this->api->get_sequence_emails(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID']
+ );
+
+ // Assert emails and pagination exist.
+ $this->assertDataExists($result, 'emails');
+ $this->assertPaginationExists($result);
+
+ // Check first sequence in resultset has expected data.
+ $email = get_object_vars($result->emails[0]);
+ $this->assertArrayHasKey('id', $email);
+ $this->assertArrayHasKey('sequence_id', $email);
+ $this->assertArrayHasKey('subject', $email);
+ $this->assertArrayHasKey('preview_text', $email);
+ $this->assertArrayHasKey('email_address', $email);
+ $this->assertArrayHasKey('email_template_id', $email);
+ $this->assertArrayHasKey('published', $email);
+ $this->assertArrayHasKey('position', $email);
+ $this->assertArrayHasKey('delay_value', $email);
+ $this->assertArrayHasKey('delay_unit', $email);
+ $this->assertArrayHasKey('send_days', $email);
+ }
+
+ /**
+ * Test that get_sequence_emails() returns the expected data
+ * when the include parameter is used.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailsWithIncludeParam()
+ {
+ $result = $this->api->get_sequence_emails(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ include: ['stats']
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('emails', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->emails[0]));
+
+ // Assert stats are included.
+ $this->assertArrayHasKey('stats', get_object_vars($result->emails[0]));
+ }
+
+ /**
+ * Test that get_sequence_emails() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailsWithTotalCount()
+ {
+ $result = $this->api->get_sequence_emails(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ include_total_count: true
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'emails');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_sequence_emails() returns the expected data when
+ * pagination parameters and per_page limits are specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailsPagination()
+ {
+ $result = $this->api->get_sequence_emails(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ per_page: 1
+ );
+
+ // Assert emails and pagination exist.
+ $this->assertDataExists($result, 'emails');
+ $this->assertPaginationExists($result);
+
+ // Assert a single email was returned.
+ $this->assertCount(1, $result->emails);
+
+ // 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_sequence_emails(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert emails and pagination exist.
+ $this->assertDataExists($result, 'emails');
+ $this->assertPaginationExists($result);
+
+ // Assert a single email was returned.
+ $this->assertCount(1, $result->emails);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertTrue($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+
+ // Use pagination to fetch previous page.
+ $result = $this->api->get_sequence_emails(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert emails and pagination exist.
+ $this->assertDataExists($result, 'emails');
+ $this->assertPaginationExists($result);
+
+ // Assert a single email was returned.
+ $this->assertCount(1, $result->emails);
+ }
+
+ /**
+ * Test that get_sequence_emails() throws a ClientException when an invalid
+ * sequence ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailsWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_sequence_emails(
+ sequence_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that create_sequence_email(), get_sequence_email(), update_sequence_email()
+ * and delete_sequence_email() works.
+ *
+ * We do all tests in a single function, so we don't end up with unnecessary
+ * Sequence Emails remaining on the Kit account when running tests, which might impact
+ * other tests that expect (or do not expect) specific Sequence Emails.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testCreateGetUpdateAndDeleteSequenceEmail()
+ {
+ // Create a sequence email.
+ $result = $this->api->create_sequence_email(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ subject: 'Test Sequence Email',
+ delay_value: 1,
+ delay_unit: 'days',
+ preview_text: 'Test Preview Text',
+ content: 'Test Content',
+ email_template_id: (int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'],
+ published: true,
+ send_days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
+ position: 0
+ );
+ $sequenceEmailID = $result->email->id;
+
+ // Confirm the Sequence Email saved.
+ $result = get_object_vars($result->email);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Test Sequence Email', $result['subject']);
+ $this->assertEquals(1, $result['delay_value']);
+ $this->assertEquals('days', $result['delay_unit']);
+ $this->assertEquals('Test Preview Text', $result['preview_text']);
+ $this->assertEquals('Test Content', $result['content']);
+ $this->assertEquals((int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'], $result['email_template_id']);
+ $this->assertEquals(true, $result['published']);
+ $this->assertEquals(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], $result['send_days']);
+ $this->assertEquals(2, $result['position']);
+
+ // Get the sequence email.
+ $result = $this->api->get_sequence_email(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ email_id: $sequenceEmailID,
+ include: ['stats']
+ );
+
+ // Update the existing sequence email.
+ $result = $this->api->update_sequence_email(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ email_id: $sequenceEmailID,
+ subject: 'Edited Test Sequence Email',
+ preview_text: 'Edited Test Preview Text',
+ content: 'Edited Test Content',
+ delay_value: 2,
+ delay_unit: 'hours',
+ email_template_id: (int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'],
+ published: true,
+ send_days: ['saturday', 'sunday'],
+ position: 2,
+ );
+
+ // Confirm the changes saved.
+ $result = get_object_vars($result->email);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Edited Test Sequence Email', $result['subject']);
+ $this->assertEquals(2, $result['delay_value']);
+ $this->assertEquals('hours', $result['delay_unit']);
+ $this->assertEquals('Edited Test Preview Text', $result['preview_text']);
+ $this->assertEquals('Edited Test Content', $result['content']);
+ $this->assertEquals((int) $_ENV['CONVERTKIT_API_EMAIL_TEMPLATE_ID'], $result['email_template_id']);
+ $this->assertEquals(true, $result['published']);
+ $this->assertEquals(['saturday', 'sunday'], $result['send_days']);
+ $this->assertEquals(2, $result['position']);
+
+ // Delete Sequence Email.
+ $this->api->delete_sequence_email((int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], $sequenceEmailID);
+ $this->assertLastResponseStatusCode(204);
+ }
+
+ /**
+ * Test that get_sequence_email() returns the expected data.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmail()
+ {
+ $result = $this->api->get_sequence_email(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ email_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_EMAIL_ID']
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('id', get_object_vars($result->email));
+ }
+
+ /**
+ * Test that get_sequence_email() returns the expected data
+ * when the include parameter is used.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailWithIncludeParam()
+ {
+ $result = $this->api->get_sequence_email(
+ sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'],
+ email_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_EMAIL_ID'],
+ include: ['stats']
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('id', get_object_vars($result->email));
+
+ // Assert stats are included.
+ $this->assertArrayHasKey('stats', get_object_vars($result->email));
+ }
+
+ /**
+ * Test that get_sequence_email() throws a ClientException when an invalid
+ * sequence ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_sequence_email(12345, 12345);
+ });
+ }
+
+ /**
+ * Test that get_sequence_email() throws a ClientException when an invalid
+ * email ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSequenceEmailWithInvalidEmailID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_sequence_email((int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], 12345);
+ });
+ }
+
+ /**
+ * Test that update_sequence_email() throws a ClientException when an invalid
+ * sequence email ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testUpdateSequenceEmailWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_sequence_email(12345, 12345);
+ });
+ }
+
+ /**
+ * Test that update_sequence_email() throws a ClientException when an invalid
+ * email ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testUpdateSequenceEmailWithInvalidEmailID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_sequence_email((int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], 12345);
+ });
+ }
+
+ /**
+ * Test that delete_sequence_email() throws a ClientException when an invalid
+ * sequence email ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testDeleteSequenceEmailWithInvalidSequenceID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->delete_sequence_email(12345, 12345);
+ });
+ }
+
+ /**
+ * Test that delete_sequence_email() throws a ClientException when an invalid
+ * email ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testDeleteSequenceEmailWithInvalidEmailID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->delete_sequence_email((int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], 12345);
+ });
+ }
+
+ /**
+ * Test that get_snippets() returns the expected data.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSnippets()
+ {
+ $result = $this->api->get_snippets();
+
+ // Assert snippets and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Check first snippet in resultset has expected data.
+ $snippet = get_object_vars($result->snippets[0]);
+ $this->assertArrayHasKey('id', $snippet);
+ $this->assertArrayHasKey('name', $snippet);
+ $this->assertArrayHasKey('snippet_type', $snippet);
+ $this->assertArrayHasKey('archived', $snippet);
+ $this->assertArrayHasKey('key', $snippet);
+ $this->assertArrayHasKey('created_at', $snippet);
+ $this->assertArrayHasKey('updated_at', $snippet);
+ }
+
+ /**
+ * Test that get_snippets() returns the expected data when
+ * the snippet type is inline.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetInlineSnippets()
+ {
+ $result = $this->api->get_snippets(
+ snippet_type: 'inline'
+ );
+
+ // Assert snippets and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert snippets were returned.
+ $this->assertGreaterThan(0, count($result->snippets));
+ }
+
+ /**
+ * Test that get_snippets() returns the expected data when
+ * the snippet type is block.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetBlockSnippets()
+ {
+ $result = $this->api->get_snippets(
+ snippet_type: 'block'
+ );
+
+ // Assert snippets and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert no snippets were returned.
+ $this->assertCount(0, $result->snippets);
+ }
+
+ /**
+ * Test that get_snippets() returns the expected data when
+ * the archived parameter is used.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSnippetsWithArchivedParam()
+ {
+ $result = $this->api->get_snippets(
+ archived: true
+ );
+
+ // Assert snippets and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert snippets were returned.
+ $this->assertGreaterThan(0, count($result->snippets));
+ }
+
+ /**
+ * Test that get_snippets() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSnippetsWithTotalCount()
+ {
+ $result = $this->api->get_snippets(
+ include_total_count: true
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_snippets() returns the expected data when
+ * pagination parameters and per_page limits are specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSnippetsPagination()
+ {
+ $result = $this->api->get_snippets(
+ per_page: 1
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert a single sequence was returned.
+ $this->assertCount(1, $result->snippets);
+
+ // 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_snippets(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert a single sequence was returned.
+ $this->assertCount(1, $result->snippets);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertTrue($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+
+ // Use pagination to fetch previous page.
+ $result = $this->api->get_snippets(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'snippets');
+ $this->assertPaginationExists($result);
+
+ // Assert a single sequence was returned.
+ $this->assertCount(1, $result->snippets);
+ }
+
+ // Note: testCreateSnippet lives on each consumer class (SDK / WP Libs).
+ // It depends on a platform-specific HTTP mock (Guzzle MockHandler in the
+ // SDK, pre_http_request filter in WP Libs), so it cannot live in this
+ // portable trait.
+
+ /**
+ * Test that update_snippet() works.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testUpdateSnippet()
+ {
+ $result = $this->api->update_snippet(
+ snippet_id: (int) $_ENV['CONVERTKIT_API_SNIPPET_ID'],
+ name: 'Edited Test Snippet',
+ snippet_type: 'inline',
+ content: 'Edited Test Content'
+ );
+
+ // Confirm the changes saved.
+ $result = get_object_vars($result->snippet);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Edited Test Snippet', $result['name']);
+ $this->assertEquals('inline', $result['snippet_type']);
+ $this->assertEquals('Edited Test Content', $result['content']);
+ }
+
+ /**
+ * Test that get_snippet() returns the expected data.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSnippet()
+ {
+ $result = $this->api->get_snippet((int) $_ENV['CONVERTKIT_API_SNIPPET_ID']);
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('snippet', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->snippet));
+ }
+
+ /**
+ * Test that get_snippet() throws a ClientException when an invalid
+ * snippet ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSnippetWithInvalidSnippetID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_snippet(12345);
+ });
+ }
+
+ /**
+ * Test that update_snippet() throws a ClientException when an invalid
+ * snippet ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testUpdateSnippetWithInvalidSnippetID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_snippet(12345);
+ });
+ }
+
+ /**
+ * Test that get_tags() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetTags()
+ {
+ $result = $this->api->get_tags();
+
+ // Assert sequences and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Check first tag in resultset has expected data.
+ $tag = get_object_vars($result->tags[0]);
+ $this->assertArrayHasKey('id', $tag);
+ $this->assertArrayHasKey('name', $tag);
+ $this->assertArrayHasKey('created_at', $tag);
+ }
+
+ /**
+ * Test that get_tags() returns the subscriber count
+ * when included in the `include` argument.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testGetTagsWithSubscriberCount()
+ {
+ $result = $this->api->get_tags(
+ include: ['subscriber_count']
+ );
+
+ // Assert forms and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert subscriber count is included.
+ $this->assertArrayHasKey('subscriber_count', get_object_vars($result->tags[0]));
+ }
+
+ /**
+ * Test that get_tags() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagsWithTotalCount()
+ {
+ $result = $this->api->get_tags(
+ include_total_count: true
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_tags() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagsPagination()
+ {
+ $result = $this->api->get_tags(
+ per_page: 1
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert a single tag was returned.
+ $this->assertCount(1, $result->tags);
+
+ // 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_tags(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->tags);
+
+ // 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_tags(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+ }
+
+ // Note: testCreateTag lives on each consumer class (SDK / WP Libs).
+ // Same reason as testCreateSnippet — the create endpoint has no matching
+ // delete endpoint, so tests must mock the HTTP layer to avoid polluting
+ // the account, and the mock is platform-specific.
+
+ /**
+ * Test that create_tag() throws a ClientException when creating
+ * a blank tag.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateTagBlank()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_tag('');
+ });
+ }
+
+ /**
+ * Test that create_tag() returns the expected data when creating
+ * a tag that already exists.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateTagThatExists()
+ {
+ $result = $this->api->create_tag($_ENV['CONVERTKIT_API_TAG_NAME']);
+
+ // Assert response contains correct data.
+ $tag = get_object_vars($result->tag);
+ $this->assertArrayHasKey('id', $tag);
+ $this->assertArrayHasKey('name', $tag);
+ $this->assertArrayHasKey('created_at', $tag);
+ $this->assertEquals($tag['name'], $_ENV['CONVERTKIT_API_TAG_NAME']);
+ }
+
+ /**
+ * Test that create_tags() and delete_tags() returns the expected data.
+ *
+ * @since 1.1.0
+ *
+ * @return void
+ */
+ public function testCreateAndDeleteTags()
+ {
+ $tagNames = [
+ 'Tag Test ' . mt_rand(),
+ 'Tag Test ' . mt_rand(),
+ ];
+
+ // Create tags.
+ $result = $this->api->create_tags($tagNames);
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Build tag IDs array.
+ $ids = [];
+ foreach ($result->tags as $tag) {
+ $ids[] = $tag->id;
+ }
+
+ // Delete tags.
+ $result = $this->api->delete_tags($ids);
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+ }
+
+ /**
+ * Test that create_tags() returns failures when attempting
+ * to create blank tags.
+ *
+ * @since 1.1.0
+ *
+ * @return void
+ */
+ public function testCreateTagsBlank()
+ {
+ $result = $this->api->create_tags([
+ '',
+ '',
+ ]);
+
+ // Assert failures.
+ $this->assertCount(2, $result->failures);
+ }
+
+ /**
+ * Test that create_tags() throws a ClientException when creating
+ * tags that already exists.
+ *
+ * @since 1.1.0
+ *
+ * @return void
+ */
+ public function testCreateTagsThatExist()
+ {
+ $result = $this->api->create_tags(
+ [
+ $_ENV['CONVERTKIT_API_TAG_NAME'],
+ $_ENV['CONVERTKIT_API_TAG_NAME_2'],
+ ]
+ );
+
+ // Assert existing tags are returned.
+ $this->assertCount(2, $result->tags);
+ $this->assertEquals($result->tags[1]->name, $_ENV['CONVERTKIT_API_TAG_NAME']);
+ $this->assertEquals($result->tags[0]->name, $_ENV['CONVERTKIT_API_TAG_NAME_2']);
+ }
+
+ /**
+ * Test that update_tag_name() returns the expected data.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testUpdateTagName()
+ {
+ $result = $this->api->update_tag_name(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ name: $_ENV['CONVERTKIT_API_TAG_NAME'],
+ );
+
+ // Assert existing tag is returned.
+ $this->assertEquals($result->tag->id, (int) $_ENV['CONVERTKIT_API_TAG_ID']);
+ $this->assertEquals($result->tag->name, $_ENV['CONVERTKIT_API_TAG_NAME']);
+ }
+
+ /**
+ * Test that update_tag_name() throws a ClientException when an invalid
+ * tag ID is specified.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testUpdateTagNameWithInvalidTagID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_tag_name(
+ tag_id: 12345,
+ name: $_ENV['CONVERTKIT_API_TAG_NAME'],
+ );
+ });
+ }
+
+ /**
+ * Test that update_tag_name() throws a ClientException when a blank
+ * name is specified.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testUpdateTagNameWithBlankName()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_tag_name(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ name: ''
+ );
+ });
+ }
+
+ /**
+ * Test that get_subscriber_stats() returns the expected data
+ * when using a valid subscriber ID.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testGetSubscriberStats()
+ {
+ $result = $this->api->get_subscriber_stats(
+ id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
+ );
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertArrayHasKey('stats', get_object_vars($result->subscriber));
+ $this->assertArrayHasKey('sent', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('opened', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('clicked', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('bounced', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('open_rate', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('click_rate', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('last_sent', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('last_opened', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('last_clicked', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('sends_since_last_open', get_object_vars($result->subscriber->stats));
+ $this->assertArrayHasKey('sends_since_last_click', get_object_vars($result->subscriber->stats));
+ }
+
+ /**
+ * Test that get_subscriber_stats() throws a ClientException when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testGetSubscriberStatsWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscriber_stats(12345);
+ });
+ }
+
+ /**
+ * Test that tag_subscribers() returns the expected data.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testTagSubscribers()
+ {
+ // Create subscribers.
+ $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);
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ foreach ($result->subscribers as $i => $subscriber) {
+ $this->subscriber_ids[] = $subscriber->id;
+ }
+
+ // Tag subscribers.
+ $result = $this->api->tag_subscribers(
+ [
+ [
+ 'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ 'subscriber_id' => $this->subscriber_ids[0]
+ ],
+ [
+ 'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ 'subscriber_id' => $this->subscriber_ids[1]
+ ],
+ ]
+ );
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Confirm result is an array comprising of each subscriber that was created.
+ $this->assertIsArray($result->subscribers);
+ $this->assertCount(2, $result->subscribers);
+ }
+
+ /**
+ * Test that tag_subscribers() returns failures when an invalid
+ * tag ID is specified.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testTagSubscribersWithInvalidTagID()
+ {
+ // Create subscribers.
+ $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);
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ foreach ($result->subscribers as $i => $subscriber) {
+ $this->subscriber_ids[] = $subscriber->id;
+ }
+
+ // Tag subscribers.
+ $result = $this->api->tag_subscribers(
+ [
+ [
+ 'tag_id' => 12345,
+ 'subscriber_id' => $this->subscriber_ids[0]
+ ],
+ [
+ 'tag_id' => 12345,
+ 'subscriber_id' => $this->subscriber_ids[1]
+ ],
+ ]
+ );
+
+ // Assert failures.
+ $this->assertCount(2, $result->failures);
+ }
+
+ /**
+ * Test that tag_subscribers() returns failures when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testTagSubscribersWithInvalidSubscriberID()
+ {
+ // Tag subscribers that do not exist.
+ $result = $this->api->tag_subscribers(
+ [
+ [
+ 'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ 'subscriber_id' => 12345,
+ ],
+ [
+ 'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ 'subscriber_id' => 67890,
+ ],
+ ]
+ );
+
+ // Assert failures.
+ $this->assertCount(2, $result->failures);
+ }
+
+ /**
+ * Test that tag_subscriber_by_email() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testTagSubscriberByEmail()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Tag subscriber by email.
+ $subscriber = $this->api->tag_subscriber_by_email(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ email_address: $emailAddress,
+ );
+ $this->assertArrayHasKey('subscriber', get_object_vars($subscriber));
+ $this->assertArrayHasKey('id', get_object_vars($subscriber->subscriber));
+ $this->assertArrayHasKey('tagged_at', get_object_vars($subscriber->subscriber));
+
+ // Confirm the subscriber is tagged.
+ $result = $this->api->get_subscriber_tags(
+ subscriber_id: $subscriber->subscriber->id
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert correct tag was assigned.
+ $this->assertEquals($result->tags[0]->id, $_ENV['CONVERTKIT_API_TAG_ID']);
+ }
+
+ /**
+ * Test that tag_subscriber_by_email() throws a ClientException when an invalid
+ * tag is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testTagSubscriberByEmailWithInvalidTagID()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ $this->assertApiError(function () {
+ return $this->api->tag_subscriber_by_email(
+ tag_id: 12345,
+ email_address: $emailAddress
+ );
+ });
+ }
+
+ /**
+ * Test that tag_subscriber_by_email() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testTagSubscriberByEmailWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->tag_subscriber_by_email(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ email_address: 'not-an-email-address'
+ );
+ });
+ }
+
+ /**
+ * Test that tag_subscriber() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testTagSubscriber()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $subscriber = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Tag subscriber by email.
+ $result = $this->api->tag_subscriber(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ subscriber_id: $subscriber->subscriber->id,
+ );
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertArrayHasKey('tagged_at', get_object_vars($result->subscriber));
+
+ // Confirm the subscriber is tagged.
+ $result = $this->api->get_subscriber_tags(
+ subscriber_id: $result->subscriber->id
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert correct tag was assigned.
+ $this->assertEquals($result->tags[0]->id, $_ENV['CONVERTKIT_API_TAG_ID']);
+ }
+
+ /**
+ * Test that tag_subscriber() throws a ClientException when an invalid
+ * sequence ID is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testTagSubscriberWithInvalidTagID()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $subscriber = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ $this->assertApiError(function () {
+ return $this->api->tag_subscriber(
+ tag_id: 12345,
+ subscriber_id: $subscriber->subscriber->id
+ );
+ });
+ }
+
+ /**
+ * Test that tag_subscriber() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testTagSubscriberWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->tag_subscriber(
+ tag_id: $_ENV['CONVERTKIT_API_TAG_ID'],
+ subscriber_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that remove_tag_from_subscriber() works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testRemoveTagFromSubscriber()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Tag subscriber by email.
+ $subscriber = $this->api->tag_subscriber_by_email(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ email_address: $emailAddress,
+ );
+
+ // Remove tag from subscriber.
+ $result = $this->api->remove_tag_from_subscriber(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ subscriber_id: $subscriber->subscriber->id
+ );
+
+ // Confirm that the subscriber no longer has the tag.
+ $result = $this->api->get_subscriber_tags($subscriber->subscriber->id);
+ $this->assertIsArray($result->tags);
+ $this->assertCount(0, $result->tags);
+ }
+
+ /**
+ * Test that remove_tag_from_subscriber() throws a ClientException when an invalid
+ * tag ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testRemoveTagFromSubscriberWithInvalidTagID()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Tag subscriber by email.
+ $subscriber = $this->api->tag_subscriber_by_email(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ email_address: $emailAddress,
+ );
+
+ // Remove tag from subscriber.
+ $this->assertApiError(function () {
+ return $this->api->remove_tag_from_subscriber(
+ tag_id: 12345,
+ subscriber_id: $subscriber->subscriber->id
+ );
+ });
+ }
+
+ /**
+ * Test that remove_tag_from_subscriber() throws a ClientException when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testRemoveTagFromSubscriberWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->remove_tag_from_subscriber(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ subscriber_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that remove_tag_from_subscriber() works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testRemoveTagFromSubscriberByEmail()
+ {
+ // Create subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Tag subscriber by email.
+ $subscriber = $this->api->tag_subscriber_by_email(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ email_address: $emailAddress,
+ );
+
+ // Remove tag from subscriber.
+ $result = $this->api->remove_tag_from_subscriber(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ subscriber_id: $subscriber->subscriber->id
+ );
+
+ // Confirm that the subscriber no longer has the tag.
+ $result = $this->api->get_subscriber_tags($subscriber->subscriber->id);
+ $this->assertIsArray($result->tags);
+ $this->assertCount(0, $result->tags);
+ }
+
+ /**
+ * Test that remove_tag_from_subscriber() throws a ClientException when an invalid
+ * tag ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testRemoveTagFromSubscriberByEmailWithInvalidTagID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->remove_tag_from_subscriber_by_email(
+ tag_id: 12345,
+ email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
+ );
+ });
+ }
+
+ /**
+ * Test that remove_tag_from_subscriber() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testRemoveTagFromSubscriberByEmailWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->remove_tag_from_subscriber_by_email(
+ tag_id: $_ENV['CONVERTKIT_API_TAG_ID'],
+ email_address: 'not-an-email-address'
+ );
+ });
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptions()
+ {
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID']
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when the slim parameter is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsSlim()
+ {
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ slim: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Confirm custom field values are excluded from the data.
+ $broadcast = get_object_vars($result->subscribers[0]);
+ $this->assertArrayNotHasKey('fields', $broadcast);
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithTotalCount()
+ {
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ include_total_count: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified and the subscription status
+ * is cancelled.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithCancelledSubscriberState()
+ {
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ subscriber_state: 'cancelled'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertEquals($result->subscribers[0]->state, 'cancelled');
+ }
+
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified and the added_after parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithTaggedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ tagged_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->tagged_at))
+ );
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified and the tagged_before parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithTaggedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ tagged_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->tagged_at))
+ );
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified and the created_after parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithCreatedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ created_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified and the created_before parameter
+ * is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithCreatedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ created_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified and pagination parameters
+ * and per_page limits are specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsPagination()
+ {
+ $result = $this->api->get_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ per_page: 1
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_tag_subscriptions(
+ tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'],
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_tag_subscriptions() returns the expected data
+ * when a valid Tag ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetTagSubscriptionsWithInvalidTagID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_tag_subscriptions(12345);
+ });
+ }
+
+ /**
+ * Test that add_subscribers_to_forms() returns the expected data.
+ *
+ * @since 2.1.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;
+
+ // 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,
+ ],
+ ]
+ );
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Confirm result is an array comprising of each subscriber that was created.
+ $this->assertIsArray($result->subscribers);
+ }
+
+ /**
+ * Test that add_subscribers_to_forms() returns the expected data
+ * when a referrer URL is specified.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscribersToFormsWithReferrer()
+ {
+ // 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;
+
+ // 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,
+ 'referrer' => 'https://mywebsite.com/bfpromo/',
+ ],
+ [
+ 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID_2'],
+ 'subscriber_id' => $subscriber->subscriber->id,
+ 'referrer' => 'https://mywebsite.com/bfpromo/',
+ ],
+ ]
+ );
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Confirm result is an array comprising of each subscriber that was created.
+ $this->assertIsArray($result->subscribers);
+
+ // Assert referrer data set for subscribers.
+ foreach ($result->subscribers as $subscriber) {
+ $this->assertEquals(
+ $subscriber->referrer,
+ 'https://mywebsite.com/bfpromo/'
+ );
+ }
+ }
+
+ /**
+ * Test that add_subscribers_to_forms() returns the expected data
+ * when a referrer URL with UTM parameters is specified.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscribersToFormsWithReferrerUTMParams()
+ {
+ // Define referrer.
+ $referrerUTMParams = [
+ 'utm_source' => 'facebook',
+ 'utm_medium' => 'cpc',
+ 'utm_campaign' => 'black_friday',
+ 'utm_term' => 'car_owners',
+ 'utm_content' => 'get_10_off',
+ ];
+ $referrer = 'https://mywebsite.com/bfpromo/?' . http_build_query($referrerUTMParams);
+
+ // 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;
+
+ // 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,
+ 'referrer' => $referrer,
+ ],
+ [
+ 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID_2'],
+ 'subscriber_id' => $subscriber->subscriber->id,
+ 'referrer' => $referrer,
+ ],
+ ]
+ );
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Confirm result is an array comprising of each subscriber that was created.
+ $this->assertIsArray($result->subscribers);
+
+ // Assert referrer data set for subscribers.
+ foreach ($result->subscribers as $subscriber) {
+ $this->assertEquals(
+ $subscriber->referrer,
+ $referrer
+ );
+ $this->assertEquals(
+ $subscriber->referrer_utm_parameters->source,
+ $referrerUTMParams['utm_source']
+ );
+ $this->assertEquals(
+ $subscriber->referrer_utm_parameters->medium,
+ $referrerUTMParams['utm_medium']
+ );
+ $this->assertEquals(
+ $subscriber->referrer_utm_parameters->campaign,
+ $referrerUTMParams['utm_campaign']
+ );
+ $this->assertEquals(
+ $subscriber->referrer_utm_parameters->term,
+ $referrerUTMParams['utm_term']
+ );
+ $this->assertEquals(
+ $subscriber->referrer_utm_parameters->content,
+ $referrerUTMParams['utm_content']
+ );
+ }
+ }
+
+ /**
+ * Test that add_subscribers_to_forms() returns the expected errors
+ * when invalid Form IDs are specified.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscribersToFormsWithInvalidFormIDs()
+ {
+ // 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;
+
+ // Add subscribers to forms.
+ $result = $this->api->add_subscribers_to_forms(
+ forms_subscribers_ids: [
+ [
+ 'form_id' => 9999999,
+ 'subscriber_id' => $subscriber->subscriber->id,
+ ],
+ [
+ 'form_id' => 9999999,
+ 'subscriber_id' => $subscriber->subscriber->id,
+ ],
+ ]
+ );
+
+ // Assert failures.
+ $this->assertCount(2, $result->failures);
+ foreach ($result->failures as $failure) {
+ $this->assertEquals(
+ $failure->errors[0],
+ 'Form does not exist'
+ );
+ }
+ }
+
+ /**
+ * Test that add_subscribers_to_forms() returns the expected errors
+ * when invalid Subscriber IDs are specified.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscribersToFormsWithInvalidSubscriberIDs()
+ {
+ // 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;
+
+ // Add subscribers to forms.
+ $result = $this->api->add_subscribers_to_forms(
+ forms_subscribers_ids: [
+ [
+ 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ 'subscriber_id' => 999999,
+ ],
+ [
+ 'form_id' => (int) $_ENV['CONVERTKIT_API_FORM_ID_2'],
+ 'subscriber_id' => 999999,
+ ],
+ ]
+ );
+
+ // Assert failures.
+ $this->assertCount(2, $result->failures);
+ foreach ($result->failures as $failure) {
+ $this->assertEquals(
+ $failure->errors[0],
+ 'Subscriber does not exist'
+ );
+ }
+ }
+
+ /**
+ * Test that add_subscriber_to_form_by_email() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormByEmail()
+ {
+ // 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;
+
+ // Add subscriber to form.
+ $result = $this->api->add_subscriber_to_form_by_email(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ email_address: $emailAddress
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(
+ get_object_vars($result->subscriber)['email_address'],
+ $emailAddress
+ );
+ }
+
+ /**
+ * Test that add_subscriber_to_form_by_email() returns the expected data
+ * when a referrer is specified.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormByEmailWithReferrer()
+ {
+ // 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;
+
+ // Add subscriber to form.
+ $result = $this->api->add_subscriber_to_form_by_email(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ email_address: $emailAddress,
+ referrer: 'https://mywebsite.com/bfpromo/',
+ );
+
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(
+ get_object_vars($result->subscriber)['email_address'],
+ $emailAddress
+ );
+
+ // Assert referrer data set for form subscriber.
+ $this->assertEquals(
+ $result->subscriber->referrer,
+ 'https://mywebsite.com/bfpromo/'
+ );
+ }
+
+ /**
+ * Test that add_subscriber_to_form_by_email() returns the expected data
+ * when a referrer is specified that includes UTM parameters.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormByEmailWithReferrerUTMParams()
+ {
+ // Define referrer.
+ $referrerUTMParams = [
+ 'utm_source' => 'facebook',
+ 'utm_medium' => 'cpc',
+ 'utm_campaign' => 'black_friday',
+ 'utm_term' => 'car_owners',
+ 'utm_content' => 'get_10_off',
+ ];
+ $referrer = 'https://mywebsite.com/bfpromo/?' . http_build_query($referrerUTMParams);
+
+ // 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;
+
+ // Add subscriber to form.
+ $result = $this->api->add_subscriber_to_form_by_email(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ email_address: $emailAddress,
+ referrer: $referrer,
+ );
+
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(
+ get_object_vars($result->subscriber)['email_address'],
+ $emailAddress
+ );
+
+ // Assert referrer data set for form subscriber.
+ $this->assertEquals(
+ $result->subscriber->referrer,
+ $referrer
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->source,
+ $referrerUTMParams['utm_source']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->medium,
+ $referrerUTMParams['utm_medium']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->campaign,
+ $referrerUTMParams['utm_campaign']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->term,
+ $referrerUTMParams['utm_term']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->content,
+ $referrerUTMParams['utm_content']
+ );
+ }
+
+ /**
+ * Test that add_subscriber_to_form_by_email() throws a ClientException when an invalid
+ * form ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormByEmailWithInvalidFormID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_form_by_email(
+ form_id: 12345,
+ email_address: $this->generateEmailAddress()
+ );
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_form() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormByEmailWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_form_by_email(
+ form_id: $_ENV['CONVERTKIT_API_FORM_ID'],
+ email_address: 'not-an-email-address'
+ );
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_form() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToForm()
+ {
+ // Create subscriber.
+ $subscriber = $this->api->create_subscriber(
+ email_address: $this->generateEmailAddress()
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $subscriber->subscriber->id;
+
+ $result = $this->api->add_subscriber_to_form(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_id: $subscriber->subscriber->id
+ );
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriber->subscriber->id);
+ }
+
+ /**
+ * Test that add_subscriber_to_form() returns the expected data
+ * when a referrer is specified.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormWithReferrer()
+ {
+ // Create subscriber.
+ $subscriber = $this->api->create_subscriber(
+ email_address: $this->generateEmailAddress()
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $subscriber->subscriber->id;
+
+ // Add subscriber to form.
+ $result = $this->api->add_subscriber_to_form(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_id: $subscriber->subscriber->id,
+ referrer: 'https://mywebsite.com/bfpromo/',
+ );
+
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriber->subscriber->id);
+
+ // Assert referrer data set for form subscriber.
+ $this->assertEquals(
+ $result->subscriber->referrer,
+ 'https://mywebsite.com/bfpromo/'
+ );
+ }
+
+ /**
+ * Test that add_subscriber_to_form() returns the expected data
+ * when a referrer is specified that includes UTM parameters.
+ *
+ * @since 2.1.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormWithReferrerUTMParams()
+ {
+ // Define referrer.
+ $referrerUTMParams = [
+ 'utm_source' => 'facebook',
+ 'utm_medium' => 'cpc',
+ 'utm_campaign' => 'black_friday',
+ 'utm_term' => 'car_owners',
+ 'utm_content' => 'get_10_off',
+ ];
+ $referrer = 'https://mywebsite.com/bfpromo/?' . http_build_query($referrerUTMParams);
+
+ // Create subscriber.
+ $subscriber = $this->api->create_subscriber(
+ email_address: $this->generateEmailAddress()
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $subscriber->subscriber->id;
+
+ // Add subscriber to form.
+ $result = $this->api->add_subscriber_to_form(
+ form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_id: $subscriber->subscriber->id,
+ referrer: $referrer,
+ );
+
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertArrayHasKey('subscriber', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->subscriber));
+ $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriber->subscriber->id);
+
+ // Assert referrer data set for form subscriber.
+ $this->assertEquals(
+ $result->subscriber->referrer,
+ $referrer
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->source,
+ $referrerUTMParams['utm_source']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->medium,
+ $referrerUTMParams['utm_medium']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->campaign,
+ $referrerUTMParams['utm_campaign']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->term,
+ $referrerUTMParams['utm_term']
+ );
+ $this->assertEquals(
+ $result->subscriber->referrer_utm_parameters->content,
+ $referrerUTMParams['utm_content']
+ );
+ }
+
+ /**
+ * Test that add_subscriber_to_form() throws a ClientException when an invalid
+ * form ID is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormWithInvalidFormID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_form(
+ form_id: 12345,
+ subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
+ );
+ });
+ }
+
+ /**
+ * Test that add_subscriber_to_form() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testAddSubscriberToFormWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->add_subscriber_to_form(
+ form_id: $_ENV['CONVERTKIT_API_FORM_ID'],
+ subscriber_id: 12345
+ );
+ });
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribers()
+ {
+ $result = $this->api->get_subscribers();
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the slim parameter is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithSlimParameter()
+ {
+ $result = $this->api->get_subscribers(
+ slim: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Confirm custom field values are excluded from the data.
+ $subscriber = get_object_vars($result->subscribers[0]);
+ $this->assertArrayNotHasKey('fields', $subscriber);
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithTotalCount()
+ {
+ $result = $this->api->get_subscribers(
+ include_total_count: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data when
+ * searching by an email address.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersByEmailAddress()
+ {
+ $result = $this->api->get_subscribers(
+ email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert correct subscriber returned.
+ $this->assertEquals(
+ $result->subscribers[0]->email_address,
+ $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']
+ );
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the subscription status is cancelled.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithCancelledSubscriberState()
+ {
+ $result = $this->api->get_subscribers(
+ subscriber_state: 'cancelled'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertEquals($result->subscribers[0]->state, 'cancelled');
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the created_after parameter is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithCreatedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_subscribers(
+ created_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertGreaterThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the created_before parameter is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithCreatedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_subscribers(
+ created_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Check the correct subscribers were returned.
+ $this->assertLessThanOrEqual(
+ $date->format('Y-m-d'),
+ date('Y-m-d', strtotime($result->subscribers[0]->created_at))
+ );
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the updated_after parameter is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithUpdatedAfterParam()
+ {
+ $date = new \DateTime('2022-01-01');
+ $result = $this->api->get_subscribers(
+ updated_after: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the updated_before parameter is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithUpdatedBeforeParam()
+ {
+ $date = new \DateTime('2024-01-01');
+ $result = $this->api->get_subscribers(
+ updated_before: $date
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the sort_field parameter is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithSortFieldParam()
+ {
+ $result = $this->api->get_subscribers(
+ sort_field: 'id'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert sorting is honored by ID in descending (default) order.
+ $this->assertLessThanOrEqual(
+ $result->subscribers[0]->id,
+ $result->subscribers[1]->id
+ );
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the sort_order parameter is used.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithSortOrderParam()
+ {
+ $result = $this->api->get_subscribers(
+ sort_order: 'asc'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert sorting is honored by ID (default) in ascending order.
+ $this->assertGreaterThanOrEqual(
+ $result->subscribers[0]->id,
+ $result->subscribers[1]->id
+ );
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when the include parameter is used.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithIncludeParam()
+ {
+ $result = $this->api->get_subscribers(
+ include: ['tags']
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert fields are included.
+ $this->assertArrayHasKey('tags', get_object_vars($result->subscribers[0]));
+ }
+
+ /**
+ * Test that get_subscribers() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersPagination()
+ {
+ $result = $this->api->get_subscribers(
+ per_page: 1
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_subscribers(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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_subscribers(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_subscribers() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscribers(
+ email_address: 'not-an-email-address'
+ );
+ });
+ }
+
+ /**
+ * Test that get_subscribers() throws a ClientException when an invalid
+ * subscriber state is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithInvalidSubscriberState()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscribers(
+ subscriber_state: 'not-an-valid-state'
+ );
+ });
+ }
+
+ /**
+ * Test that get_subscribers() throws a ClientException when an invalid
+ * sort field is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithInvalidSortFieldParam()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscribers(
+ sort_field: 'not-a-valid-sort-field'
+ );
+ });
+ }
+
+ /**
+ * Test that get_subscribers() throws a ClientException when an invalid
+ * sort order is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithInvalidSortOrderParam()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscribers(
+ sort_order: 'not-a-valid-sort-order'
+ );
+ });
+ }
+
+ /**
+ * Test that get_subscribers() throws a ClientException when an invalid
+ * pagination parameters are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscribersWithInvalidPagination()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscribers(
+ after_cursor: 'not-a-valid-cursor'
+ );
+ });
+ }
+
+ /**
+ * Test that create_subscriber() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriber()
+ {
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber exists with correct data.
+ $this->assertEquals($result->subscriber->email_address, $emailAddress);
+ }
+
+ /**
+ * Test that create_subscriber() returns the expected data
+ * when a first name is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriberWithFirstName()
+ {
+ $firstName = 'FirstName';
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress,
+ first_name: $firstName
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber exists with correct data.
+ $this->assertEquals($result->subscriber->email_address, $emailAddress);
+ $this->assertEquals($result->subscriber->first_name, $firstName);
+ }
+
+ /**
+ * Test that create_subscriber() returns the expected data
+ * when a subscriber state is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriberWithSubscriberState()
+ {
+ $subscriberState = 'cancelled';
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress,
+ subscriber_state: $subscriberState
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber exists with correct data.
+ $this->assertEquals($result->subscriber->email_address, $emailAddress);
+ $this->assertEquals($result->subscriber->state, $subscriberState);
+ }
+
+ /**
+ * Test that create_subscriber() returns the expected data
+ * when custom field data is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriberWithCustomFields()
+ {
+ $lastName = 'LastName';
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress,
+ fields: [
+ 'last_name' => $lastName
+ ]
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber exists with correct data.
+ $this->assertEquals($result->subscriber->email_address, $emailAddress);
+ $this->assertEquals($result->subscriber->fields->last_name, $lastName);
+ }
+
+ /**
+ * Test that create_subscriber() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriberWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_subscriber(
+ email_address: 'not-an-email-address'
+ );
+ });
+ }
+
+ /**
+ * Test that create_subscriber() throws a ClientException when an invalid
+ * subscriber state is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriberWithInvalidSubscriberState()
+ {
+ $emailAddress = $this->generateEmailAddress();
+ $this->assertApiError(function () use ($emailAddress) {
+ return $this->api->create_subscriber(
+ email_address: $emailAddress,
+ subscriber_state: 'not-a-valid-state'
+ );
+ });
+ }
+
+ /**
+ * Test that create_subscriber() returns the expected warnings
+ * when an invalid custom field is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscriberWithInvalidCustomFields()
+ {
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress,
+ fields: [
+ 'not_a_custom_field' => 'value'
+ ]
+ );
+ $this->assertArrayHasKey('warnings', get_object_vars($result));
+ }
+
+ /**
+ * Test that create_subscribers() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscribers()
+ {
+ $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);
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ foreach ($result->subscribers as $i => $subscriber) {
+ $this->subscriber_ids[] = $subscriber->id;
+ }
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Assert subscribers exists with correct data.
+ foreach ($result->subscribers as $i => $subscriber) {
+ $this->assertEquals($subscriber->email_address, $subscribers[$i]['email_address']);
+ }
+ }
+
+ /**
+ * Test that create_subscribers() throws a ClientException when no data is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscribersWithBlankData()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_subscribers([
+ [],
+ ]);
+ });
+ }
+
+ /**
+ * Test that create_subscribers() returns the expected data when invalid email addresses
+ * are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreateSubscribersWithInvalidEmailAddresses()
+ {
+ $subscribers = [
+ [
+ 'email_address' => 'not-an-email-address',
+ ],
+ [
+ 'email_address' => 'not-an-email-address-again',
+ ],
+ ];
+ $result = $this->api->create_subscribers($subscribers);
+
+ // Assert no subscribers were added.
+ $this->assertCount(0, $result->subscribers);
+ $this->assertCount(2, $result->failures);
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribers()
+ {
+ $result = $this->api->filter_subscribers(
+ [
+ [
+ 'type' => 'opens',
+ 'count_greater_than' => 10,
+ 'count_less_than' => 100,
+ 'after' => new \DateTime('2024-01-01'),
+ 'before' => new \DateTime('2027-01-01'),
+ 'states' => [
+ 'active',
+ ],
+ ]
+ ]
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data
+ * when multiple all conditions are specified.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersWithMultipleConditions()
+ {
+ $result = $this->api->filter_subscribers(
+ [
+ [
+ 'type' => 'opens',
+ 'count_greater_than' => 10,
+ 'count_less_than' => 100,
+ 'after' => new \DateTime('2024-01-01'),
+ 'before' => new \DateTime('2027-01-01'),
+ 'states' => [
+ 'active',
+ ],
+ ],
+ [
+ 'type' => 'clicks',
+ 'count_greater_than' => 1,
+ 'count_less_than' => 100,
+ 'after' => new \DateTime('2024-01-01'),
+ 'before' => new \DateTime('2027-01-01'),
+ ]
+ ]
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data
+ * when a counting mode is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersWithCountingMode()
+ {
+ $result = $this->api->filter_subscribers(
+ all: [
+ [
+ 'type' => 'opens',
+ 'count_greater_than' => 10,
+ 'count_less_than' => 100,
+ 'after' => new \DateTime('2024-01-01'),
+ 'before' => new \DateTime('2027-01-01'),
+ 'states' => [
+ 'active',
+ ],
+ ]
+ ],
+ counting_mode: 'unique_email'
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data
+ * when the `include` parameter is specified.
+ *
+ * @since 2.6.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersWithInclude()
+ {
+ $result = $this->api->filter_subscribers(
+ all: [
+ [
+ 'type' => 'opens',
+ 'count_greater_than' => 0,
+ 'count_less_than' => 100,
+ 'after' => new \DateTime('2024-01-01'),
+ 'before' => new \DateTime('2029-01-01'),
+ 'states' => [
+ 'active',
+ ],
+ ]
+ ],
+ include: [
+ [
+ 'type' => 'tags',
+ ],
+ ]
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert tags are included.
+ $this->assertArrayHasKey('tags', get_object_vars($result->subscribers[0]));
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data
+ * when no parameters are specified.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersWithNoParameters()
+ {
+ $result = $this->api->filter_subscribers();
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that filter_subscribers() throws a ServerException
+ * when invalid parameters are specified.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersWithInvalidParameters()
+ {
+ $this->assertApiError(function () {
+ return $this->api->filter_subscribers(
+ [
+ [
+ 'foo' => 'bar',
+ ],
+ [
+ 'type' => 'not-a-real-type',
+ ]
+ ]
+ );
+ });
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersWithTotalCount()
+ {
+ $result = $this->api->filter_subscribers(
+ include_total_count: true
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that filter_subscribers() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testFilterSubscribersPagination()
+ {
+ $result = $this->api->filter_subscribers(
+ per_page: 1
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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->filter_subscribers(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // 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->filter_subscribers(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert subscribers and pagination exist.
+ $this->assertDataExists($result, 'subscribers');
+ $this->assertPaginationExists($result);
+
+ // Assert a single subscriber was returned.
+ $this->assertCount(1, $result->subscribers);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertTrue($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+ }
+
+ /**
+ * Test that get_subscriber_id() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberID()
+ {
+ $subscriber_id = $this->api->get_subscriber_id($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']);
+ $this->assertIsInt($subscriber_id);
+ $this->assertEquals($subscriber_id, (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
+ }
+
+ /**
+ * Test that get_subscriber_id() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberIDWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscriber_id('not-an-email-address');
+ });
+ }
+
+ /**
+ * Test that get_subscriber_id() return false when no subscriber found
+ * matching the given email address.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberIDWithNotSubscribedEmailAddress()
+ {
+ $result = $this->api->get_subscriber_id('not-a-subscriber@test.com');
+ $this->assertFalse($result);
+ }
+
+ /**
+ * Test that get_subscriber() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriber()
+ {
+ $result = $this->api->get_subscriber((int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
+
+ // Assert subscriber exists with correct data.
+ $this->assertEquals($result->subscriber->id, $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
+ $this->assertEquals($result->subscriber->email_address, $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']);
+ }
+
+ /**
+ * Test that get_subscriber() throws a ClientException when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscriber(12345);
+ });
+ }
+
+ /**
+ * Test that update_subscriber() works when no changes are made.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateSubscriberWithNoChanges()
+ {
+ $result = $this->api->update_subscriber($_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
+
+ // Assert subscriber exists with correct data.
+ $this->assertEquals($result->subscriber->id, $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
+ $this->assertEquals($result->subscriber->email_address, $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']);
+ }
+
+ /**
+ * Test that update_subscriber() works when updating the subscriber's first name.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateSubscriberFirstName()
+ {
+ // Add a subscriber.
+ $firstName = 'FirstName';
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber created with no first name.
+ $this->assertNull($result->subscriber->first_name);
+
+ // Get subscriber ID.
+ $subscriberID = $result->subscriber->id;
+
+ // Update subscriber's first name.
+ $result = $this->api->update_subscriber(
+ subscriber_id: $subscriberID,
+ first_name: $firstName
+ );
+
+ // Assert changes were made.
+ $this->assertEquals($result->subscriber->id, $subscriberID);
+ $this->assertEquals($result->subscriber->first_name, $firstName);
+ }
+
+ /**
+ * Test that update_subscriber() works when updating the subscriber's email address.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateSubscriberEmailAddress()
+ {
+ // Add a subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber created.
+ $this->assertEquals($result->subscriber->email_address, $emailAddress);
+
+ // Get subscriber ID.
+ $subscriberID = $result->subscriber->id;
+
+ // Update subscriber's email address.
+ $newEmail = $this->generateEmailAddress();
+ $result = $this->api->update_subscriber(
+ subscriber_id: $subscriberID,
+ email_address: $newEmail
+ );
+
+ // Assert changes were made.
+ $this->assertEquals($result->subscriber->id, $subscriberID);
+ $this->assertEquals($result->subscriber->email_address, $newEmail);
+ }
+
+ /**
+ * Test that update_subscriber() works when updating the subscriber's custom fields.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateSubscriberCustomFields()
+ {
+ // Add a subscriber.
+ $lastName = 'LastName';
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ $this->subscriber_ids[] = $result->subscriber->id;
+
+ // Assert subscriber created.
+ $this->assertEquals($result->subscriber->email_address, $emailAddress);
+
+ // Get subscriber ID.
+ $subscriberID = $result->subscriber->id;
+
+ // Update subscriber's custom fields.
+ $result = $this->api->update_subscriber(
+ subscriber_id: $subscriberID,
+ fields: [
+ 'last_name' => $lastName,
+ ]
+ );
+
+ // Assert changes were made.
+ $this->assertEquals($result->subscriber->id, $subscriberID);
+ $this->assertEquals($result->subscriber->fields->last_name, $lastName);
+ }
+
+ /**
+ * Test that update_subscriber() throws a ClientException when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateSubscriberWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_subscriber(12345);
+ });
+ }
+
+ /**
+ * Test that unsubscribe_by_email() works with a valid subscriber email address.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUnsubscribeByEmail()
+ {
+ // Add a subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Wait a moment to ensure subscriber is created.
+ sleep(3);
+
+ // Unsubscribe.
+ $this->assertNull($this->api->unsubscribe_by_email($emailAddress));
+ }
+
+ /**
+ * Test that unsubscribe_by_email() throws a ClientException when an email
+ * address is specified that is not subscribed.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUnsubscribeByEmailWithNotSubscribedEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->unsubscribe_by_email('not-subscribed@kit.com');
+ });
+ }
+
+ /**
+ * Test that unsubscribe_by_email() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUnsubscribeByEmailWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->unsubscribe_by_email('invalid-email');
+ });
+ }
+
+ /**
+ * Test that unsubscribe() works with a valid subscriber ID.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testUnsubscribe()
+ {
+ // Add a subscriber.
+ $emailAddress = $this->generateEmailAddress();
+ $result = $this->api->create_subscriber(
+ email_address: $emailAddress
+ );
+
+ // Wait a moment to ensure subscriber is created.
+ sleep(3);
+
+ // Unsubscribe.
+ $this->assertNull($this->api->unsubscribe($result->subscriber->id));
+ }
+
+ /**
+ * Test that unsubscribe() throws a ClientException when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testUnsubscribeWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->unsubscribe(12345);
+ });
+ }
+
+ /**
+ * Test that get_subscriber_tags() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberTags()
+ {
+ $result = $this->api->get_subscriber_tags((int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']);
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_subscriber_tags() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberTagsWithTotalCount()
+ {
+ $result = $this->api->get_subscriber_tags(
+ subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
+ include_total_count: true
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_subscriber_tags() throws a ClientException when an invalid
+ * subscriber ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberTagsWithInvalidSubscriberID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_subscriber_tags(12345);
+ });
+ }
+
+ /**
+ * Test that get_subscriber_tags() returns the expected data
+ * when a valid Subscriber ID is specified and pagination parameters
+ * and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSubscriberTagsPagination()
+ {
+ $result = $this->api->get_subscriber_tags(
+ subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
+ per_page: 1
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert a single tag was returned.
+ $this->assertCount(1, $result->tags);
+
+ // 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_subscriber_tags(
+ subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert a single tag was returned.
+ $this->assertCount(1, $result->tags);
+
+ // 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_subscriber_tags(
+ subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'],
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert tags and pagination exist.
+ $this->assertDataExists($result, 'tags');
+ $this->assertPaginationExists($result);
+
+ // Assert a single tag was returned.
+ $this->assertCount(1, $result->tags);
+ }
+
+ /**
+ * Test that get_email_templates() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetEmailTemplates()
+ {
+ $result = $this->api->get_email_templates();
+
+ // Assert email templates and pagination exist.
+ $this->assertDataExists($result, 'email_templates');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_email_templates() returns the expected data
+ * when the total count is included.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetEmailTemplatesWithTotalCount()
+ {
+ $result = $this->api->get_email_templates(
+ include_total_count: true
+ );
+
+ // Assert email templates and pagination exist.
+ $this->assertDataExists($result, 'email_templates');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_email_templates() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetEmailTemplatesPagination()
+ {
+ $result = $this->api->get_email_templates(
+ per_page: 1
+ );
+
+ // Assert email templates and pagination exist.
+ $this->assertDataExists($result, 'email_templates');
+ $this->assertPaginationExists($result);
+
+ // Assert a single email template was returned.
+ $this->assertCount(1, $result->email_templates);
+
+ // 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_email_templates(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert email templates and pagination exist.
+ $this->assertDataExists($result, 'email_templates');
+ $this->assertPaginationExists($result);
+
+ // Assert a single email template was returned.
+ $this->assertCount(1, $result->email_templates);
+
+ // 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_email_templates(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert email templates and pagination exist.
+ $this->assertDataExists($result, 'email_templates');
+ $this->assertPaginationExists($result);
+
+ // Assert a single email template was returned.
+ $this->assertCount(1, $result->email_templates);
+
+ // 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 get_posts() returns the expected data.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPosts()
+ {
+ $result = $this->api->get_posts();
+
+ // Assert posts and pagination exist.
+ $this->assertDataExists($result, 'posts');
+ $this->assertPaginationExists($result);
+
+ // Assert content is not included.
+ $this->assertArrayNotHasKey('content', get_object_vars($result->posts[0]));
+ }
+
+ /**
+ * Test that get_posts() returns the expected data
+ * when the post content is included.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPostsWithIncludeContent()
+ {
+ $result = $this->api->get_posts(
+ include_content: true,
+ per_page: 1
+ );
+
+ // Assert posts and pagination exist.
+ $this->assertDataExists($result, 'posts');
+ $this->assertPaginationExists($result);
+
+ // Assert content is included.
+ $this->assertArrayHasKey('content', get_object_vars($result->posts[0]));
+ }
+
+ /**
+ * Test that get_posts() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPostsWithTotalCount()
+ {
+ $result = $this->api->get_posts(
+ include_total_count: true
+ );
+
+ // Assert posts and pagination exist.
+ $this->assertDataExists($result, 'posts');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_posts() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPostsPagination()
+ {
+ $result = $this->api->get_posts(
+ per_page: 1
+ );
+
+ // Assert posts and pagination exist.
+ $this->assertDataExists($result, 'posts');
+ $this->assertPaginationExists($result);
+
+ // Assert a single post was returned.
+ $this->assertCount(1, $result->posts);
+
+ // 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_posts(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert posts and pagination exist.
+ $this->assertDataExists($result, 'posts');
+ $this->assertPaginationExists($result);
+
+ // Assert a single post was returned.
+ $this->assertCount(1, $result->posts);
+
+ // 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_posts(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert posts and pagination exist.
+ $this->assertDataExists($result, 'posts');
+ $this->assertPaginationExists($result);
+
+ // Assert a single post was returned.
+ $this->assertCount(1, $result->posts);
+
+ // 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 get_post() returns the expected data.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPost()
+ {
+ $result = $this->api->get_post($_ENV['CONVERTKIT_API_POST_ID']);
+ $result = get_object_vars($result->post);
+ $this->assertEquals($result['id'], $_ENV['CONVERTKIT_API_POST_ID']);
+ }
+
+ /**
+ * Test that get_post() throws a ClientException when an invalid
+ * post ID is specified.
+ *
+ * @since 2.5.0
+ *
+ * @return void
+ */
+ public function testGetPostWithInvalidPostID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_post(12345);
+ });
+ }
+
+ /**
+ * Test that get_broadcasts() returns the expected data
+ * when a valid sent_after date is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsWithSentAfter()
+ {
+ $date = new DateTime('now');
+ $date->modify('-4 years');
+ $result = $this->api->get_broadcasts(
+ sent_after: $date,
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert the expected number of broadcasts were returned.
+ $this->assertCount(4, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts() returns no broadcasts
+ * when a sent_after date is specified that is after all broadcasts.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsWithSentAfterNow()
+ {
+ $date = new DateTime('now');
+ $date->modify('-1 day');
+ $result = $this->api->get_broadcasts(
+ sent_after: $date,
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert no broadcasts were returned.
+ $this->assertCount(0, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts() returns the expected data
+ * when a valid sent_before date is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsWithSentBefore()
+ {
+ $date = new DateTime('now');
+ $result = $this->api->get_broadcasts(
+ sent_before: new DateTime('now'),
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert the expected number of broadcasts were returned.
+ $this->assertCount(12, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts() returns the expected data
+ * when the slim parameter is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsSlim()
+ {
+ $result = $this->api->get_broadcasts(
+ slim: true
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Confirm content, public_url, email_address, email_template and subscriber_filter are excluded from the data.
+ $broadcast = get_object_vars($result->broadcasts[0]);
+ $this->assertArrayNotHasKey('content', $broadcast);
+ $this->assertArrayNotHasKey('public_url', $broadcast);
+ $this->assertArrayNotHasKey('email_address', $broadcast);
+ $this->assertArrayNotHasKey('email_template', $broadcast);
+ $this->assertArrayNotHasKey('subscriber_filter', $broadcast);
+ }
+
+ /**
+ * Test that get_broadcasts() returns the expected data
+ * when the completed status is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsWithCompletedStatus()
+ {
+ $result = $this->api->get_broadcasts(
+ status: 'completed'
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ }
+
+ /**
+ * Test that get_broadcasts() returns the expected data
+ * when the aborted status is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsWithAbortedStatus()
+ {
+ $result = $this->api->get_broadcasts(
+ status: 'aborted'
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert no broadcasts were returned.
+ $this->assertCount(0, $result->broadcasts);
+ }
+
+ /**
+ * 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 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateAndUpdateDraftBroadcast()
+ {
+ // Create a broadcast first.
+ $result = $this->api->create_broadcast(
+ subject: 'Test Subject',
+ content: 'Test Content',
+ description: 'Test Broadcast from PHP SDK',
+ );
+ $broadcastID = $result->broadcast->id;
+
+ // Confirm the Broadcast saved.
+ $result = get_object_vars($result->broadcast);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Test Subject', $result['subject']);
+ $this->assertEquals('Test Content', $result['content']);
+ $this->assertEquals('Test Broadcast from PHP SDK', $result['description']);
+ $this->assertEquals(null, $result['published_at']);
+ $this->assertEquals(null, $result['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 PHP SDK'
+ );
+
+ // Confirm the changes saved.
+ $result = get_object_vars($result->broadcast);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('New Test Subject', $result['subject']);
+ $this->assertEquals('New Test Content', $result['content']);
+ $this->assertEquals('New Test Broadcast from PHP SDK', $result['description']);
+ $this->assertEquals(null, $result['published_at']);
+ $this->assertEquals(null, $result['send_at']);
+
+ // Delete Broadcast.
+ $this->api->delete_broadcast($broadcastID);
+ $this->assertLastResponseStatusCode(204);
+ }
+
+ /**
+ * Test that create_broadcast() works when specifying valid published_at and send_at values.
+ *
+ * @since 1.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 PHP SDK',
+ public: true,
+ published_at: $publishedAt,
+ send_at: $sendAt
+ );
+ $broadcastID = $result->broadcast->id;
+
+ // Set broadcast_id to ensure broadcast is deleted after test.
+ $this->broadcast_ids[] = $broadcastID;
+
+ // Confirm the Broadcast saved.
+ $result = get_object_vars($result->broadcast);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertEquals('Test Subject', $result['subject']);
+ $this->assertEquals('Test Content', $result['content']);
+ $this->assertEquals('Test Broadcast from PHP SDK', $result['description']);
+ $this->assertEquals(
+ $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . 'Z',
+ $result['published_at']
+ );
+ $this->assertEquals(
+ $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . 'Z',
+ $result['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']);
+ $result = get_object_vars($result->broadcast);
+ $this->assertEquals($result['id'], $_ENV['CONVERTKIT_API_BROADCAST_ID']);
+ }
+
+ /**
+ * Test that get_broadcast() throws a ClientException when an invalid
+ * broadcast ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetBroadcastWithInvalidBroadcastID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_broadcast(12345);
+ });
+ }
+
+ /**
+ * Test that get_broadcast_stats() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetBroadcastStats()
+ {
+ $result = $this->api->get_broadcast_stats($_ENV['CONVERTKIT_API_BROADCAST_ID']);
+ $result = get_object_vars($result->broadcast);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertArrayHasKey('stats', $result);
+ $this->assertEquals($result['stats']->recipients, 1);
+ $this->assertEquals($result['stats']->open_rate, 0);
+ $this->assertEquals($result['stats']->click_rate, 0);
+ $this->assertEquals($result['stats']->unsubscribes, 0);
+ $this->assertEquals($result['stats']->total_clicks, 0);
+ }
+
+ /**
+ * Test that get_broadcast_stats() throws a ClientException when an invalid
+ * broadcast ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetBroadcastStatsWithInvalidBroadcastID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_broadcast_stats(12345);
+ });
+ }
+
+ /**
+ * Test that get_broadcast_link_clicks() returns the expected data.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testGetBroadcastLinkClicks()
+ {
+ // Get broadcast link clicks.
+ $result = $this->api->get_broadcast_link_clicks(
+ $_ENV['CONVERTKIT_API_BROADCAST_ID'],
+ per_page: 1
+ );
+
+ // Assert clicks and pagination exist.
+ $this->assertDataExists($result->broadcast, 'clicks');
+ $this->assertPaginationExists($result);
+
+ // Assert a single click was returned.
+ $this->assertCount(1, $result->broadcast->clicks);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertFalse($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+ }
+
+ /**
+ * Test that get_broadcast_link_clicks() throws a ClientException when an invalid
+ * broadcast ID is specified.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testGetBroadcastLinkClicksWithInvalidBroadcastID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_broadcast_link_clicks(12345);
+ });
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns the expected data.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStats()
+ {
+ // Get broadcasts stats.
+ $result = $this->api->get_broadcasts_stats(
+ 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);
+
+ // Store the Broadcast ID to check it's different from the next broadcast.
+ $id = $result->broadcasts[0]->id;
+
+ // 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_stats(
+ 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 the broadcast ID is different from the previous broadcast.
+ $this->assertNotEquals($id, $result->broadcasts[0]->id);
+
+ // 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_stats(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert a single webhook was returned.
+ $this->assertCount(1, $result->broadcasts);
+
+ // Assert the broadcast ID matches the first broadcast.
+ $this->assertEquals($id, $result->broadcasts[0]->id);
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns the expected data
+ * when a valid sent_after date is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStatsWithSentAfter()
+ {
+ $date = new DateTime('now');
+ $date->modify('-4 years');
+ $result = $this->api->get_broadcasts_stats(
+ sent_after: $date,
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert the expected number of broadcasts were returned.
+ $this->assertCount(4, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns no broadcasts
+ * when a sent_after date is specified that is after all broadcasts.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStatsWithSentAfterNow()
+ {
+ $date = new DateTime('now');
+ $date->modify('-1 day');
+ $result = $this->api->get_broadcasts_stats(
+ sent_after: $date,
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert no broadcasts were returned.
+ $this->assertCount(0, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns the expected data
+ * when a valid sent_before date is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStatsWithSentBefore()
+ {
+ $date = new DateTime('now');
+ $result = $this->api->get_broadcasts_stats(
+ sent_before: new DateTime('now'),
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert the expected number of broadcasts were returned.
+ $this->assertCount(12, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns the expected data
+ * when the completed status is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStatsWithCompletedStatus()
+ {
+ $result = $this->api->get_broadcasts_stats(
+ status: 'completed'
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert the expected number of broadcasts were returned.
+ $this->assertCount(12, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns the expected data
+ * when the aborted status is specified.
+ *
+ * @since 2.5
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStatsWithAbortedStatus()
+ {
+ $result = $this->api->get_broadcasts_stats(
+ status: 'aborted'
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert the expected number of broadcasts were returned.
+ $this->assertCount(0, $result->broadcasts);
+ }
+
+ /**
+ * Test that get_broadcasts_stats() returns the expected data
+ * when the total count is included.
+ *
+ * @since 2.2.1
+ *
+ * @return void
+ */
+ public function testGetBroadcastsStatsWithTotalCount()
+ {
+ $result = $this->api->get_broadcasts_stats(
+ include_total_count: true
+ );
+
+ // Assert broadcasts and pagination exist.
+ $this->assertDataExists($result, 'broadcasts');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that update_broadcast() throws a ClientException when an invalid
+ * broadcast ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateBroadcastWithInvalidBroadcastID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_broadcast(12345);
+ });
+ }
+
+ /**
+ * Test that delete_broadcast() throws a ClientException when an invalid
+ * broadcast ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testDeleteBroadcastWithInvalidBroadcastID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->delete_broadcast(12345);
+ });
+ }
+
+ /**
+ * Test that get_webhooks() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetWebhooksPagination()
+ {
+ // Create webhooks first.
+ $results = [
+ $this->api->create_webhook(
+ url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
+ event: 'subscriber.subscriber_activate',
+ ),
+ $this->api->create_webhook(
+ url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
+ event: 'subscriber.subscriber_activate',
+ ),
+ ];
+
+ // Set webhook_ids to ensure webhooks are deleted after test.
+ $this->webhook_ids = [
+ $results[0]->webhook->id,
+ $results[1]->webhook->id,
+ ];
+
+ // Get webhooks.
+ $result = $this->api->get_webhooks(
+ per_page: 1
+ );
+
+ // Assert webhooks and pagination exist.
+ $this->assertDataExists($result, 'webhooks');
+ $this->assertPaginationExists($result);
+
+ // Assert a single webhook was returned.
+ $this->assertCount(1, $result->webhooks);
+
+ // 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_webhooks(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert webhooks and pagination exist.
+ $this->assertDataExists($result, 'webhooks');
+ $this->assertPaginationExists($result);
+
+ // Assert a single webhook was returned.
+ $this->assertCount(1, $result->webhooks);
+
+ // Assert has_previous_page and has_next_page are correct.
+ $this->assertTrue($result->pagination->has_previous_page);
+ $this->assertFalse($result->pagination->has_next_page);
+
+ // Use pagination to fetch previous page.
+ $result = $this->api->get_webhooks(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert webhooks and pagination exist.
+ $this->assertDataExists($result, 'webhooks');
+ $this->assertPaginationExists($result);
+
+ // Assert a single webhook was returned.
+ $this->assertCount(1, $result->webhooks);
+ }
+
+ /**
+ * Test that create_webhook(), get_webhooks() and delete_webhook() works.
+ *
+ * We do both, so we don't end up with unnecessary webhooks remaining
+ * on the ConvertKit account when running tests.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateGetAndDeleteWebhook()
+ {
+ // Create a webhook first.
+ $result = $this->api->create_webhook(
+ url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
+ event: 'subscriber.subscriber_activate',
+ );
+ $id = $result->webhook->id;
+
+ // Get webhooks.
+ $result = $this->api->get_webhooks();
+
+ // Assert webhooks and pagination exist.
+ $this->assertDataExists($result, 'webhooks');
+ $this->assertPaginationExists($result);
+
+ // Get webhooks including total count.
+ $result = $this->api->get_webhooks(
+ include_total_count: true
+ );
+
+ // Assert webhooks and pagination exist.
+ $this->assertDataExists($result, 'webhooks');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+
+ // Delete the webhook.
+ $result = $this->api->delete_webhook($id);
+ }
+
+ /**
+ * Test that create_webhook() works with an event parameter.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateWebhookWithEventParameter()
+ {
+ // Create a webhook.
+ $url = 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds');
+ $result = $this->api->create_webhook(
+ url: $url,
+ event: 'custom_field.field_value_updated',
+ parameter: $_ENV['CONVERTKIT_API_CUSTOM_FIELD_ID']
+ );
+
+ // Confirm webhook created with correct data.
+ $this->assertArrayHasKey('webhook', get_object_vars($result));
+ $this->assertArrayHasKey('id', get_object_vars($result->webhook));
+ $this->assertArrayHasKey('account_id', get_object_vars($result->webhook));
+ $this->assertArrayHasKey('event', get_object_vars($result->webhook));
+ $this->assertArrayHasKey('target_url', get_object_vars($result->webhook));
+ $this->assertEquals($result->webhook->target_url, $url);
+ $this->assertEquals($result->webhook->event->name, 'field_value_updated');
+ $this->assertEquals($result->webhook->event->custom_field_id, $_ENV['CONVERTKIT_API_CUSTOM_FIELD_ID']);
+
+ // Delete the webhook.
+ $result = $this->api->delete_webhook($result->webhook->id);
+ }
+
+ /**
+ * Test that create_webhook() throws an InvalidArgumentException when an invalid
+ * event is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateWebhookWithInvalidEvent()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_webhook(
+ url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
+ event: 'invalid.event'
+ );
+ });
+ }
+
+ /**
+ * Test that delete_webhook() throws a ClientException when an invalid
+ * ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testDeleteWebhookWithInvalidID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->delete_webhook(12345);
+ });
+ }
+
+ /**
+ * Test that get_custom_fields() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetCustomFields()
+ {
+ $result = $this->api->get_custom_fields();
+
+ // Assert custom fields and pagination exist.
+ $this->assertDataExists($result, 'custom_fields');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_custom_fields() returns the expected data
+ * when the total count is included.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetCustomFieldsWithTotalCount()
+ {
+ $result = $this->api->get_custom_fields(
+ include_total_count: true
+ );
+
+ // Assert custom fields and pagination exist.
+ $this->assertDataExists($result, 'custom_fields');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_custom_fields() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetCustomFieldsPagination()
+ {
+ $result = $this->api->get_custom_fields(
+ per_page: 1
+ );
+
+ // Assert custom fields and pagination exist.
+ $this->assertDataExists($result, 'custom_fields');
+ $this->assertPaginationExists($result);
+
+ // Assert a single custom field was returned.
+ $this->assertCount(1, $result->custom_fields);
+
+ // 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_custom_fields(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert custom fields and pagination exist.
+ $this->assertDataExists($result, 'custom_fields');
+ $this->assertPaginationExists($result);
+
+ // Assert a single custom field was returned.
+ $this->assertCount(1, $result->custom_fields);
+
+ // 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_custom_fields(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert custom fields and pagination exist.
+ $this->assertDataExists($result, 'custom_fields');
+ $this->assertPaginationExists($result);
+
+ // Assert a single custom field was returned.
+ $this->assertCount(1, $result->custom_fields);
+ }
+
+ /**
+ * Test that create_custom_field() works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateCustomField()
+ {
+ $label = 'Custom Field ' . mt_rand();
+ $result = $this->api->create_custom_field($label);
+
+ // Set custom_field_ids to ensure custom fields are deleted after test.
+ $this->custom_field_ids[] = $result->custom_field->id;
+
+ $result = get_object_vars($result->custom_field);
+ $this->assertArrayHasKey('id', $result);
+ $this->assertArrayHasKey('name', $result);
+ $this->assertArrayHasKey('key', $result);
+ $this->assertArrayHasKey('label', $result);
+ $this->assertEquals($result['label'], $label);
+ }
+
+ /**
+ * Test that create_custom_field() throws a ClientException when a blank
+ * label is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateCustomFieldWithBlankLabel()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_custom_field('');
+ });
+ }
+
+ /**
+ * Test that create_custom_fields() works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreateCustomFields()
+ {
+ $labels = [
+ 'Custom Field ' . mt_rand(),
+ 'Custom Field ' . mt_rand(),
+ ];
+ $result = $this->api->create_custom_fields($labels);
+
+ // Set custom_field_ids to ensure custom fields are deleted after test.
+ foreach ($result->custom_fields as $index => $customField) {
+ $this->custom_field_ids[] = $customField->id;
+ }
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Confirm result is an array comprising of each custom field that was created.
+ $this->assertIsArray($result->custom_fields);
+ }
+
+ /**
+ * Test that update_subscriber_custom_field_values() works.
+ *
+ * @since 2.4.0
+ *
+ * @return void
+ */
+ public function testUpdateSubscriberCustomFieldValues()
+ {
+ // Create subscribers.
+ $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);
+
+ // Set subscriber_id to ensure subscriber is unsubscribed after test.
+ foreach ($result->subscribers as $i => $subscriber) {
+ $this->subscriber_ids[] = $subscriber->id;
+ }
+
+ // Bulk update subscriber custom field values.
+ $result = $this->api->update_subscriber_custom_field_values(
+ [
+ [
+ 'subscriber_id' => $this->subscriber_ids[0],
+ 'subscriber_custom_field_id' => (int) $_ENV['CONVERTKIT_API_CUSTOM_FIELD_ID'],
+ 'value' => '100',
+ ],
+ [
+ 'subscriber_id' => $this->subscriber_ids[1],
+ 'subscriber_custom_field_id' => (int) $_ENV['CONVERTKIT_API_CUSTOM_FIELD_ID'],
+ 'value' => '200',
+ ],
+ ]
+ );
+
+ // Assert no failures.
+ $this->assertCount(0, $result->failures);
+
+ // Confirm result is an array comprising of each custom field value that was updated.
+ $this->assertIsArray($result->custom_field_values);
+ $this->assertCount(2, $result->custom_field_values);
+ }
+
+ /**
+ * Test that update_custom_field() works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateCustomField()
+ {
+ // Create custom field.
+ $label = 'Custom Field ' . mt_rand();
+ $result = $this->api->create_custom_field($label);
+ $id = $result->custom_field->id;
+
+ // Set custom_field_ids to ensure custom fields are deleted after test.
+ $this->custom_field_ids[] = $result->custom_field->id;
+
+ // Change label.
+ $newLabel = 'Custom Field ' . mt_rand();
+ $this->api->update_custom_field($id, $newLabel);
+
+ // Confirm label changed.
+ $customFields = $this->api->get_custom_fields();
+ foreach ($customFields->custom_fields as $customField) {
+ if ($customField->id === $id) {
+ $this->assertEquals($customField->label, $newLabel);
+ }
+ }
+ }
+
+ /**
+ * Test that update_custom_field() throws a ClientException when an
+ * invalid custom field ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testUpdateCustomFieldWithInvalidID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->update_custom_field(12345, 'Something');
+ });
+ }
+
+ /**
+ * Test that delete_custom_field() works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testDeleteCustomField()
+ {
+ // Create custom field.
+ $label = 'Custom Field ' . mt_rand();
+ $result = $this->api->create_custom_field($label);
+ $id = $result->custom_field->id;
+
+ // Delete custom field as tests passed.
+ $this->api->delete_custom_field($id);
+
+ // Confirm custom field no longer exists.
+ $customFields = $this->api->get_custom_fields();
+ foreach ($customFields->custom_fields as $customField) {
+ $this->assertNotEquals($customField->id, $id);
+ }
+ }
+
+ /**
+ * Test that delete_custom_field() throws a ClientException when an
+ * invalid custom field ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testDeleteCustomFieldWithInvalidID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->delete_custom_field(12345);
+ });
+ }
+
+ /**
+ * Test that get_purchases() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetPurchases()
+ {
+ $result = $this->api->get_purchases();
+
+ // Assert purchases and pagination exist.
+ $this->assertDataExists($result, 'purchases');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_purchases() returns the expected data
+ * when the total count is included.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetPurchasesWithTotalCount()
+ {
+ $result = $this->api->get_purchases(
+ include_total_count: true
+ );
+
+ // Assert purchases and pagination exist.
+ $this->assertDataExists($result, 'purchases');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_purchases() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetPurchasesPagination()
+ {
+ $result = $this->api->get_purchases(
+ per_page: 1
+ );
+
+ // Assert purchases and pagination exist.
+ $this->assertDataExists($result, 'purchases');
+ $this->assertPaginationExists($result);
+
+ // Assert a single purchase was returned.
+ $this->assertCount(1, $result->purchases);
+
+ // 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_purchases(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert purchases and pagination exist.
+ $this->assertDataExists($result, 'purchases');
+ $this->assertPaginationExists($result);
+
+ // Assert a single purchase was returned.
+ $this->assertCount(1, $result->purchases);
+
+ // 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_purchases(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert purchases and pagination exist.
+ $this->assertDataExists($result, 'purchases');
+ $this->assertPaginationExists($result);
+
+ // Assert a single purchase was returned.
+ $this->assertCount(1, $result->purchases);
+
+ // 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 get_purchase() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetPurchase()
+ {
+ // Get ID of first purchase.
+ $purchases = $this->api->get_purchases(
+ per_page: 1
+ );
+ $id = $purchases->purchases[0]->id;
+
+ // Get purchase.
+ $result = $this->api->get_purchase($id);
+ $this->assertInstanceOf('stdClass', $result);
+ $this->assertEquals($purchases->purchases[0]->id, $id);
+ }
+
+ /**
+ * Test that get_purchases() throws a ClientException when an invalid
+ * purchase ID is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetPurchaseWithInvalidID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_purchase(12345);
+ });
+ }
+
+ /**
+ * Test that create_purchase() returns the expected data.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testCreatePurchase()
+ {
+ $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'),
+ );
+
+ $this->assertInstanceOf('stdClass', $purchase);
+ $this->assertArrayHasKey('transaction_id', get_object_vars($purchase->purchase));
+ }
+
+ /**
+ * Test that create_purchase() throws a ClientException when an invalid
+ * email address is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreatePurchaseWithInvalidEmailAddress()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_purchase(
+ email_address: 'not-an-email-address',
+ 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,
+ ],
+ ],
+ );
+ });
+ }
+
+ /**
+ * Test that create_purchase() throws a ClientException when a blank
+ * transaction ID is specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreatePurchaseWithBlankTransactionID()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_purchase(
+ email_address: $this->generateEmailAddress(),
+ transaction_id: '',
+ currency: 'usd',
+ products: [
+ [
+ 'name' => 'Floppy Disk (512k)',
+ 'sku' => '7890-ijkl',
+ 'pid' => 9999,
+ 'lid' => 7777,
+ 'quantity' => 2,
+ 'unit_price' => 5.00,
+ ],
+ ],
+ );
+ });
+ }
+
+ /**
+ * Test that create_purchase() throws a ClientException when no products
+ * are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testCreatePurchaseWithNoProducts()
+ {
+ $this->assertApiError(function () {
+ return $this->api->create_purchase(
+ email_address: $this->generateEmailAddress(),
+ transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'),
+ currency: 'usd',
+ products: [],
+ );
+ });
+ }
+
+ /**
+ * Test that get_segments() returns the expected data.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSegments()
+ {
+ $result = $this->api->get_segments();
+
+ // Assert segments and pagination exist.
+ $this->assertDataExists($result, 'segments');
+ $this->assertPaginationExists($result);
+ }
+
+ /**
+ * Test that get_segments() returns the expected data
+ * when the total count is included.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetSegmentsWithTotalCount()
+ {
+ $result = $this->api->get_segments(
+ include_total_count: true
+ );
+
+ // Assert segments and pagination exist.
+ $this->assertDataExists($result, 'segments');
+ $this->assertPaginationExists($result);
+
+ // Assert total count is included.
+ $this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
+ $this->assertGreaterThan(0, $result->pagination->total_count);
+ }
+
+ /**
+ * Test that get_segments() returns the expected data
+ * when pagination parameters and per_page limits are specified.
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function testGetSegmentsPagination()
+ {
+ $result = $this->api->get_segments(
+ per_page: 1
+ );
+
+ // Assert segments and pagination exist.
+ $this->assertDataExists($result, 'segments');
+ $this->assertPaginationExists($result);
+
+ // Assert a single segment was returned.
+ $this->assertCount(1, $result->segments);
+
+ // 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_segments(
+ per_page: 1,
+ after_cursor: $result->pagination->end_cursor
+ );
+
+ // Assert segments and pagination exist.
+ $this->assertDataExists($result, 'segments');
+ $this->assertPaginationExists($result);
+
+ // Assert a single segment was returned.
+ $this->assertCount(1, $result->segments);
+
+ // 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_segments(
+ per_page: 1,
+ before_cursor: $result->pagination->start_cursor
+ );
+
+ // Assert segments and pagination exist.
+ $this->assertDataExists($result, 'segments');
+ $this->assertPaginationExists($result);
+
+ // Assert a single segment was returned.
+ $this->assertCount(1, $result->segments);
+
+ // 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 fetching a legacy form's markup works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetResourceLegacyForm()
+ {
+ $markup = $this->api->get_resource($_ENV['CONVERTKIT_API_LEGACY_FORM_URL']);
+
+ // Assert that the markup is HTML.
+ $this->assertTrue($this->isHtml($markup));
+
+ // Confirm that encoding works correctly.
+ $this->assertStringContainsString('Vantar þinn ungling sjálfstraust í stærðfræði?', $markup);
+ }
+
+ /**
+ * Test that fetching a landing page's markup works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetResourceLandingPage()
+ {
+ $markup = $this->api->get_resource($_ENV['CONVERTKIT_API_LANDING_PAGE_CHARACTER_ENCODING_URL']);
+
+ // Assert that the markup is HTML.
+ $this->assertTrue($this->isHtml($markup));
+
+ // Confirm that encoding works correctly.
+ $this->assertStringContainsString('Vantar þinn ungling sjálfstraust í stærðfræði?', $markup);
+ }
+
+ /**
+ * Test that fetching a legacy landing page's markup works.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetResourceLegacyLandingPage()
+ {
+ $markup = $this->api->get_resource($_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_URL']);
+
+ // Assert that the markup is HTML.
+ $this->assertTrue($this->isHtml($markup));
+
+ // Confirm that encoding works correctly.
+ $this->assertStringContainsString('Legacy Landing Page', $markup);
+ }
+
+ /**
+ * Test that get_resource() throws an InvalidArgumentException when an invalid
+ * URL is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetResourceInvalidURL()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_resource('not-a-url');
+ });
+ }
+
+ /**
+ * Test that get_resource() throws a ClientException when an inaccessible
+ * URL is specified.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function testGetResourceInaccessibleURL()
+ {
+ $this->assertApiError(function () {
+ return $this->api->get_resource('https://kit.com/a/url/that/does/not/exist');
+ });
+ }
+
+ /**
+ * Generates a unique email address for use in a test, comprising of a prefix,
+ * date + time and PHP version number.
+ *
+ * This ensures that if tests are run in parallel, the same email address
+ * isn't used for two tests across parallel testing runs.
+ *
+ * @since 1.0.0
+ *
+ * @param string $domain Domain (default: kit.com).
+ *
+ * @return string
+ */
+ public function generateEmailAddress($domain = 'kit.com')
+ {
+ return 'php-sdk-' . date('Y-m-d-H-i-s') . '-php-' . PHP_VERSION_ID . '@' . $domain;
+ }
+
+ /**
+ * Checks if string is html.
+ *
+ * @since 1.0.0
+ *
+ * @param string $string Possible HTML.
+ * @return bool
+ */
+ public function isHtml($string)
+ {
+ return preg_match("/<[^<]+>/", $string, $m) != 0;
+ }
+
+ /**
+ * Helper method to assert the given key exists as an array in the API response.
+ *
+ * Accepts either a stdClass object (PHP SDK, Guzzle-decoded) or an
+ * associative array (WP Libs, wp_remote_retrieve_body -> json_decode true),
+ * so the same trait file works verbatim in both repos.
+ *
+ * @since 2.0.0
+ *
+ * @param object|array $result API Result.
+ * @param string $key Key.
+ */
+ public function assertDataExists($result, $key)
+ {
+ $result = is_object($result) ? get_object_vars($result) : $result;
+ $this->assertArrayHasKey($key, $result);
+ $this->assertIsArray($result[$key]);
+ }
+
+ /**
+ * Helper method to assert pagination object exists in response.
+ *
+ * Accepts either a stdClass object (PHP SDK) or an associative array
+ * (WP Libs), so the same trait file works verbatim in both repos.
+ *
+ * @since 2.0.0
+ *
+ * @param object|array $result API Result.
+ */
+ public function assertPaginationExists($result)
+ {
+ $result = is_object($result) ? get_object_vars($result) : $result;
+ $this->assertArrayHasKey('pagination', $result);
+ $pagination = is_object($result['pagination']) ? get_object_vars($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);
+ }
+}
diff --git a/tests/Support/Helper/WPUnit/API.php b/tests/Support/Helper/WPUnit/API.php
deleted file mode 100644
index 732d736..0000000
--- a/tests/Support/Helper/WPUnit/API.php
+++ /dev/null
@@ -1,50 +0,0 @@
-{yourFunctionName}.
- */
-class API extends \Codeception\Module
-{
- /**
- * Sends a request to the ConvertKit API, typically used to read an endpoint to confirm
- * that data in an Acceptance Test was added/edited/deleted successfully.
- *
- * @param string $endpoint Endpoint.
- * @param string $method Method (GET|POST|PUT).
- * @param array $params Endpoint Parameters.
- * @return array
- */
- public function apiRequest($endpoint, $method = 'GET', $params = array())
- {
- // Build query parameters.
- $params = array_merge(
- $params,
- [
- 'api_key' => $_ENV['CONVERTKIT_API_KEY'],
- 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'],
- ]
- );
-
- // Send request.
- try {
- $client = new \GuzzleHttp\Client();
- $result = $client->request(
- $method,
- 'https://api.kit.com/v3/' . $endpoint . '?' . http_build_query($params),
- [
- 'headers' => [
- 'Accept-Encoding' => 'gzip',
- 'timeout' => 5,
- ],
- ]
- );
-
- // Return JSON decoded response.
- return json_decode($result->getBody()->getContents(), true);
- } catch (\GuzzleHttp\Exception\ClientException $e) {
- return [];
- }
- }
-}
diff --git a/tests/Support/Helper/WPUnit/ObjectResponseProxy.php b/tests/Support/Helper/WPUnit/ObjectResponseProxy.php
new file mode 100644
index 0000000..cdea12f
--- /dev/null
+++ b/tests/Support/Helper/WPUnit/ObjectResponseProxy.php
@@ -0,0 +1,85 @@
+api = $api;
+ }
+
+ /**
+ * Forward every method call to the wrapped API. If the return value is
+ * an array, convert it recursively to a nested `stdClass` so the trait's
+ * object-style assertions work verbatim.
+ *
+ * @param string $name Method name.
+ * @param array $args Positional arguments.
+ * @return mixed
+ */
+ public function __call($name, $args)
+ {
+ $result = $this->api->$name(...$args);
+
+ if (is_array($result)) {
+ // Recursive array -> nested stdClass via a round-trip through JSON.
+ // Cheap, dependency-free, and preserves nested pagination objects.
+ return json_decode(wp_json_encode($result));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Forward property reads (e.g. debug flags, tokens) to the wrapped API.
+ *
+ * @param string $name Property name.
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->api->$name;
+ }
+
+ /**
+ * Forward property writes to the wrapped API.
+ *
+ * @param string $name Property name.
+ * @param mixed $value Value.
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $this->api->$name = $value;
+ }
+
+ /**
+ * Expose the wrapped raw API. Useful for tests that occasionally need
+ * the array-returning behaviour.
+ *
+ * @return object
+ */
+ public function raw()
+ {
+ return $this->api;
+ }
+}