From 5bb228217b5edd6973f60799755f8bdbb13d11b1 Mon Sep 17 00:00:00 2001 From: Daniel Seifert Date: Wed, 1 Jan 2025 22:25:47 +0100 Subject: [PATCH] modify subscriber entity and its tests --- src/Entities/Subscriber.php | 405 +++++++---- .../integration/Resources/SubscriberTest.php | 37 ++ tests/unit/Entities/SubscriberTest.php | 628 ++++++++++++++++++ 3 files changed, 941 insertions(+), 129 deletions(-) create mode 100644 tests/unit/Entities/SubscriberTest.php diff --git a/src/Entities/Subscriber.php b/src/Entities/Subscriber.php index d9f8c15..ac206fb 100644 --- a/src/Entities/Subscriber.php +++ b/src/Entities/Subscriber.php @@ -15,163 +15,67 @@ namespace D3\KlicktippPhpClient\Entities; +use D3\KlicktippPhpClient\Exceptions\BaseException; +use D3\KlicktippPhpClient\Resources\Subscriber as SubscriberEndpoint; use DateTime; use Doctrine\Common\Collections\ArrayCollection; +use Exception; class Subscriber extends ArrayCollection { public const STATUS_SUBSCRIBED = 'subscribed'; public const BOUNCE_NOTBOUNCED = 'Not Bounced'; - public function getId(): string + private ?SubscriberEndpoint $endpoint; + + public function __construct(array $elements = [], ?SubscriberEndpoint $endpoint = null) { - return $this->get('id') ?? ''; + $this->endpoint = $endpoint; + parent::__construct($elements); } - public function getListId(): string + public function getId(): ?string { - return $this->get('listid') ?? ''; + return $this->get('id'); } - public function getOptinTime(): string + public function getListId(): ?string { - return $this->get('optin') ?? ''; + return $this->get('listid'); } - public function getOptinIp(): string + public function getOptinTime(): ?DateTime { - return $this->get('optin_ip') ?? ''; + return $this->getDateTimeFromValue($this->get('optin')); } - public function getEmailAddress(): string + public function isOptedIn(): bool { - return $this->get('email') ?? ''; + return $this->getOptinTime() !== null && + $this->getOptinTime() > new DateTime('0000-00-00 00:00:00') && + $this->getOptinTime() < new DateTime(); } - public function getStatus(): string + public function getOptinIp(): ?string { - return $this->get('status') ?? ''; + return $this->get('optin_ip'); } - public function getBounce(): string + public function getEmailAddress(): ?string { - return $this->get('bounce') ?? ''; + return $this->get('email'); } - public function getDate(): string + public function changeEmailAddress(string $emailAddress): void { - return $this->get('date') ?? ''; + $this->set('email', $emailAddress); + + // use persist method to send to Klicktipp } - public function getIp(): string + public function getStatus(): ?string { - return $this->get('ip') ?? ''; - } - - public function getUnsubscription(): string - { - return $this->get('unsubscription') ?? ''; - } - - public function getUnsubscriptionIp(): string - { - return $this->get('unsubscription_ip') ?? ''; - } - - public function getReferrer(): string - { - return $this->get('referrer') ?? ''; - } - - public function getSmsPhone(): string - { - return $this->get('sms_phone') ?? ''; - } - - public function getSmsStatus(): string - { - return $this->get('sms_status') ?? ''; - } - - public function getSmsBounce(): string - { - return $this->get('sms_bounce') ?? ''; - } - - public function getSmsUnsubscription(): string - { - return $this->get('sms_unsubscription') ?? ''; - } - - public function getSmsReferrer(): string - { - return $this->get('sms_referrer') ?? ''; - } - - public function getField(string $fieldId): string - { - // ToDo: should we throw fieldNotSetException - return $this->get('field'.trim($fieldId)) ?? ''; - } - - public function getTags(): array - { - return $this->get('tags') ?? []; - } - - public function getManualTags(): array - { - return $this->get('manual_tags') ?? []; - } - - public function getManualTag(string $tagId): string - { - return $this->getManualTags()[$tagId] ?? ''; - } - - public function getSmartTags(): array - { - return $this->get('smart_tags') ?? []; - } - - public function getSmartTag(string $tagId): string - { - return $this->getSmartTags()[$tagId] ?? ''; - } - - public function getStartedCampaigns(): array - { - return $this->get('campaigns_started') ?? []; - } - - public function getStartedCampaign(string $campaignId): string - { - return $this->getStartedCampaigns()[$campaignId] ?? ''; - } - - public function getFinishedCampaigns(): array - { - return $this->get('campaigns_finished') ?? []; - } - - public function getFinishedCampaign(string $campaignId): string - { - return $this->getFinishedCampaigns()[$campaignId] ?? ''; - } - - public function getSentNotificationEmails(): array - { - return $this->get('notification_emails_sent') ?? []; - } - - public function getOutbound(): array - { - return $this->get('outbound') ?? []; - } - - public function getOpenedNotificationEmails(): array - { - return $this->get('notification_emails_opened') ?? []; + return $this->get('status'); } public function isSubscribed(): bool @@ -179,19 +83,262 @@ class Subscriber extends ArrayCollection return $this->getStatus() === self::STATUS_SUBSCRIBED; } - public function isOptedIn(): bool + public function getBounce(): ?string { - return $this->getOptinTime() != '0000-00-00 00:00:00' && - new DateTime($this->getOptinTime()) < new DateTime(); + return $this->get('bounce'); } public function isBounced(): bool { - return $this->getBounce() != self::BOUNCE_NOTBOUNCED; + return $this->getBounce() !== self::BOUNCE_NOTBOUNCED; + } + + public function getDate(): ?DateTime + { + return $this->getDateTimeFromValue($this->get('date')); + } + + public function getIp(): ?string + { + return $this->get('ip'); + } + + public function getUnsubscription(): ?DateTime + { + return $this->getDateTimeFromValue($this->get('unsubscription')); + } + + public function getUnsubscriptionIp(): ?string + { + return $this->get('unsubscription_ip'); + } + + public function getReferrer(): ?string + { + return $this->get('referrer'); + } + + public function getSmsPhone(): ?string + { + return $this->get('sms_phone'); + } + + /** + * @throws BaseException + */ + public function setSmsPhone(string $smsPhone): void + { + $this->set('sms_phone', $smsPhone); + + // use persist method to send to Klicktipp + } + + public function getSmsStatus(): ?string + { + return $this->get('sms_status'); + } + + public function getSmsBounce(): ?string + { + return $this->get('sms_bounce'); + } + + public function getSmsDate(): ?DateTime + { + return $this->getDateTimeFromValue($this->get('sms_date')); + } + + public function getSmsUnsubscription(): ?string + { + return $this->getDateTimeFromValue($this->get('sms_unsubscription')); + } + + public function getSmsReferrer(): ?string + { + return $this->get('sms_referrer'); + } + + public function getFields(): ArrayCollection + { + return $this->filter( + function ($value, $key) { + return str_starts_with($key, 'field'); + } + ); + } + + public function getField(string $fieldId): ?string + { + return $this->getFields()->get($this->getFieldLongName($fieldId)); + } + + /** + * @throws BaseException + */ + public function setField(string $fieldId, string $value): void + { + $this->set($this->getFieldLongName($fieldId), $value); + + // use persist method to send to Klicktipp + } + + protected function getFieldLongName(string $fieldId): ?string + { + return str_starts_with($fieldId, 'field') ? trim($fieldId) : 'field'.trim($fieldId); + } + + public function getTags(): ArrayCollection + { + return new ArrayCollection($this->get('tags') ?? []); } public function isTagSet(string $tagId): bool { - return in_array($tagId, $this->getTags()); + return $this->getTags()->contains($tagId); } + + /** + * manuelle Tags + */ + public function getManualTags(): ArrayCollection + { + return new ArrayCollection($this->get('manual_tags') ?? []); + } + + public function isManualTagSet(string $tagId): bool + { + return $this->getManualTags()->containsKey($tagId); + } + + public function getManualTagTime(string $tagId): ?DateTime + { + return $this->getDateTimeFromValue($this->getManualTags()->get($tagId)); + } + + public function getSmartTags(): ArrayCollection + { + return new ArrayCollection($this->get('smart_tags') ?? []); + } + + public function getSmartTagTime(string $tagId): ?DateTime + { + return $this->getDateTimeFromValue($this->getSmartTags()->get($tagId)); + } + + /** + * Kampagne gestartet + */ + public function getStartedCampaigns(): ArrayCollection + { + return new ArrayCollection($this->get('campaigns_started') ?? []); + } + + public function getStartedCampaignTime(string $campaignId): ?DateTime + { + return $this->getDateTimeFromValue($this->getStartedCampaigns()->get($campaignId)); + } + + /** + * Kampagne beendet + */ + public function getFinishedCampaigns(): ArrayCollection + { + return new ArrayCollection($this->get('campaigns_finished') ?? []); + } + + public function getFinishedCampaignTime(string $campaignId): ?DateTime + { + return $this->getDateTimeFromValue($this->getFinishedCampaigns()->get($campaignId)); + } + + /** + * Email (Marketing Cockpit) erhalten + */ + public function getSentNotificationEmails(): ArrayCollection + { + return new ArrayCollection($this->get('notification_emails_sent') ?? []); + } + + /** + * Email (Marketing Cockpit) geoeffnet + */ + public function getOpenedNotificationEmails(): ArrayCollection + { + return new ArrayCollection($this->get('notification_emails_opened') ?? []); + } + + /** + * Email (Marketing Cockpit) geklickt + */ + public function getClickedNotificationEmails(): ArrayCollection + { + return new ArrayCollection($this->get('notification_emails_clicked') ?? []); + } + + /** + * Email (Marketing Cockpit) im Webbrowser angesehen + */ + public function getViewedNotificationEmails(): ArrayCollection + { + return new ArrayCollection($this->get('notification_emails_viewed') ?? []); + } + + /** + * Outbound ausgeloest + */ + public function getOutbounds(): ArrayCollection + { + return new ArrayCollection($this->get('outbound') ?? []); + } + + /** + * @return bool + * @throws BaseException + */ + public function persist(): bool + { + return $this->endpoint?->update( + $this->getId(), + $this->getFields()->toArray(), + $this->getEmailAddress(), + $this->getSmsPhone() + ); + } + + protected function getDateTimeFromValue($value): ?DateTime + { + try { + return $value ? + new DateTime((string)$value) : + null; + } catch (Exception) { + return null; + } + } + + // missing getters (return is timestamp list) + + // smart_links SmartLinks + // emails_sent Newsletter / Autoresponder erhalten + // emails_opened Newsletter / Autoresponder geoeffnet + // emails_clicked Newsletter / Autoresponder geklickt + // emails_viewed Newsletter / Autoresponder im Webbrowser angesehen + // conversions Newsletter / Autoresponder Conversion-Pixel geladen + // kajabi_activated Kajabi Membership aktiviert + // kajabi_deactivated Kajabi Membership deaktiviert + // taggingpixel_triggered Tagging-Pixel ausgeloest + // notification_sms_sent SMS (Marketing Cockpit) erhalten + // notification_sms_clicked SMS (Marketing Cockpit) geklickt + // api_subscriptions Via API-Key eingetragen + // email_subscriptions Via E-Mail eingetragen + // sms_subscriptions Via SMS eingetragen + // form_subscriptions Via Anmeldeformular + // facebook_subscriptions Via Facebook-Button eingetragen + // payments Zahlung eingegangen + // refunds Rueckzahlung ausgeloest + // chargebacks Chargeback ausgeloest + // rebills_canceled Abo gekuendigt + // rebills_resumed Abo wiederaufgenommen + // rebills_expired Letzten Tag des Abo erreicht + // digistore_affiliations Affiliate eines Digistore24-Produkts } diff --git a/tests/integration/Resources/SubscriberTest.php b/tests/integration/Resources/SubscriberTest.php index 687e6fa..587e680 100644 --- a/tests/integration/Resources/SubscriberTest.php +++ b/tests/integration/Resources/SubscriberTest.php @@ -32,6 +32,22 @@ use ReflectionException; */ class SubscriberTest extends IntegrationTestCase { + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Resources\Model::__construct + */ + public function testConstruct(): void + { + $connection = $this->getConnectionMock(new Response(200, [], json_encode([]))); + $sut = new Subscriber($connection); + + $this->assertSame( + $connection, + $this->getValue($sut, 'connection') + ); + } + /** * @test * @throws ReflectionException @@ -654,4 +670,25 @@ class SubscriberTest extends IntegrationTestCase ['myMailAddress'] ); } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Resources\Model::convertDataArrayToUrlParameters + */ + public function testConvertDataArrayToUrlParameters() + { + $sut = $this->getMockBuilder(Subscriber::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertSame( + ['fields[field1]' => 'value1'], + $this->callMethod( + $sut, + 'convertDataArrayToUrlParameters', + [['field1' => 'value1']] + ) + ); + } } diff --git a/tests/unit/Entities/SubscriberTest.php b/tests/unit/Entities/SubscriberTest.php new file mode 100644 index 0000000..ee4a37d --- /dev/null +++ b/tests/unit/Entities/SubscriberTest.php @@ -0,0 +1,628 @@ + + * @link https://www.oxidmodule.com + */ + +namespace D3\KlicktippPhpClient\tests\unit\Entities; + +use D3\KlicktippPhpClient\Entities\Subscriber; +use D3\KlicktippPhpClient\tests\TestCase; +use DateTime; +use Doctrine\Common\Collections\ArrayCollection; +use Generator; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; +use ReflectionException; + +/** + * @coversNothing + */ +class SubscriberTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + $this->entity = new Subscriber( + [ + "id" => "155988456", + "listid" => "368370", + "optin" => "28.12.2024 22:52:09", + "optin_ip" => "0.0.0.0 - By API Request", + "email" => "testsubscriber@mydomain.com", + "status" => "Opt-In Pending", + "bounce" => "Not Bounced", + "date" => "2024-12-24", + "ip" => "0.0.0.0 - By API Request", + "unsubscription" => "unsubscription fixture", + "unsubscription_ip" => "0.0.0.0", + "referrer" => "referrer fixture", + "sms_phone" => "1234567890", + "sms_status" => "sms status fixture", + "sms_bounce" => "sms bounce fixture", + "sms_date" => "2024-12-23", + "sms_unsubscription" => "sms unsubscription fixture", + "sms_referrer" => "sms referrer fixture", + "fieldFirstName" => "", + "fieldLastName" => "", + "fieldCompanyName" => "", + "fieldStreet1" => "", + "fieldStreet2" => "", + "fieldCity" => "", + "fieldState" => "", + "fieldZip" => "", + "fieldCountry" => "", + "fieldPrivatePhone" => "", + "fieldMobilePhone" => "", + "fieldPhone" => "", + "fieldFax" => "", + "fieldWebsite" => "", + "fieldBirthday" => "", + "fieldLeadValue" => "", + "tags" => [ + "12494453", + "12494463", + ], + "manual_tags" => [ + "12594453" => "125959453", + "12594454" => "125960453", + "12594455" => "125961453", + ], + "smart_tags" => [ + "12594456" => "125959453", + "12594457" => "125960453", + "12594458" => "125961453", + "12594459" => "125961453", + ], + "campaigns_started" => [ + "12594456" => "125959453", + ], + "campaigns_finished" => [ + "12594456" => "125959453", + "12594457" => "125959453", + ], + "notification_emails_sent" => [ + "1570256" => "1730508478", + "1570257" => "1730508479", + "1570258" => "1730508480", + ], + "notification_emails_opened" => [ + "1570256" => "1730508478", + "1570257" => "1730508479", + "1570258" => "1730508480", + "1570259" => "1730508481", + ], + "notification_emails_clicked" => [ + "1570256" => "1730508478", + "1570257" => "1730508479", + "1570258" => "1730508480", + "1570259" => "1730508481", + "1570260" => "1730508482", + ], + "notification_emails_viewed" => [ + "1570256" => "1730508478", + "1570257" => "1730508479", + "1570258" => "1730508480", + "1570259" => "1730508481", + "1570260" => "1730508482", + "1570261" => "1730508483", + ], + "outbound" => [ + "1570256" => "1730508478", + ], + ] + ); + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::__construct + */ + public function testConstruct(): void + { + $elements = [ + 'key1' => 'value1', + 'key2' => 'value2', + ]; + + $endpoint = $this->getMockBuilder(\D3\KlicktippPhpClient\Resources\Subscriber::class) + ->disableOriginalConstructor() + ->getMock(); + + $sut = new Subscriber($elements, $endpoint); + + $this->assertSame( + $elements, + $sut->toArray() + ); + $this->assertSame( + $endpoint, + $this->getValue($sut, 'endpoint') + ); + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getId + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getListId + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getOptinIp + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getEmailAddress + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getStatus + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getBounce + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getIp + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getUnsubscriptionIp + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getReferrer + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmsPhone + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmsStatus + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmsBounce + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmsReferrer + * @dataProvider getSomethingDataProvider + */ + public function testGetSomething(string $methodName, string $expectedValue) + { + $this->assertSame( + $expectedValue, + $this->callMethod($this->entity, $methodName) + ); + } + + public static function getSomethingDataProvider(): Generator + { + yield ['getId', '155988456']; + yield ['getListId', '368370']; + yield ['getOptinIp', '0.0.0.0 - By API Request']; + yield ['getEmailAddress', 'testsubscriber@mydomain.com']; + yield ['getStatus', 'Opt-In Pending']; + yield ['getBounce', 'Not Bounced']; + yield ['getIp', '0.0.0.0 - By API Request']; + yield ['getUnsubscriptionIp', '0.0.0.0']; + yield ['getReferrer', 'referrer fixture']; + yield ['getSmsPhone', '1234567890']; + yield ['getSmsStatus', 'sms status fixture']; + yield ['getSmsBounce', 'sms bounce fixture']; + yield ['getSmsReferrer', 'sms referrer fixture']; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getOptinTime + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getDate + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getUnsubscription + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmsDate + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmsUnsubscription + * @dataProvider getTimesDataProvider + */ + public function testGetTimes($methodName): void + { + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['getDateTimeFromValue']) + ->getMock(); + $sut->expects($this->once())->method('getDateTimeFromValue'); + + $this->callMethod($sut, $methodName); + } + + public static function getTimesDataProvider(): Generator + { + yield ['getOptinTime']; + yield ['getDate']; + yield ['getUnsubscription']; + yield ['getSmsDate']; + yield ['getSmsUnsubscription']; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getDateTimeFromValue + * @dataProvider getDateTimeFromValueDataProvider + */ + public function testGetDateTimeFromValue(?string $value, ?DateTime $expectedValue): void + { + $this->assertEquals( + $expectedValue, + $this->callMethod( + $this->entity, + 'getDateTimeFromValue', + [$value] + ) + ); + } + + public static function getDateTimeFromValueDataProvider(): Generator + { + yield 'null' => [null, null]; + yield 'valid date' => ['2024-12-24', new DateTime('2024-12-24')]; + yield 'valid date time' => ['28.12.2024 22:52:09', new DateTime('2024-12-28 22:52:09')]; + yield 'invalid' => ['', null]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::isOptedIn + * @dataProvider isOptedinDataProvider + */ + public function testIsOptedIn(?DateTime $optinTime, bool $expected): void + { + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['getOptinTime']) + ->getMock(); + $sut->method('getOptinTime')->willReturn($optinTime); + + $this->assertSame( + $expected, + $this->callMethod($sut, 'isOptedIn') + ); + } + + public static function isOptedinDataProvider(): Generator + { + yield 'null' => [null, false]; + yield 'zero date' => [new DateTime('0000-00-00'), false]; + yield 'in past' => [new DateTime('1980-12-24'), true]; + yield 'in future' => [new DateTime('2040-12-24'), false]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::isSubscribed + * @dataProvider isSubscribedDataProvider + */ + public function testIsSubscribed(?string $status, bool $expected): void + { + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['getStatus']) + ->getMock(); + $sut->method('getStatus')->willReturn($status); + + $this->assertSame( + $expected, + $this->callMethod($sut, 'isSubscribed') + ); + } + + public static function isSubscribedDataProvider(): Generator + { + yield 'null' => [null, false]; + yield 'empty term' => ['', false]; + yield 'wrong term' => ['fixture', false]; + yield 'right term' => [Subscriber::STATUS_SUBSCRIBED, true]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::isBounced + * @dataProvider isBouncedDataProvider + */ + public function testIsBounced(?string $status, bool $expected): void + { + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['getBounce']) + ->getMock(); + $sut->method('getBounce')->willReturn($status); + + $this->assertSame( + $expected, + $this->callMethod($sut, 'isBounced') + ); + } + + public static function isBouncedDataProvider(): Generator + { + yield 'null' => [null, true]; + yield 'empty term' => ['', true]; + yield 'right term' => ['fixture', true]; + yield 'wrong term' => [Subscriber::BOUNCE_NOTBOUNCED, false]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::changeEmailAddress + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::setSmsPhone + * @dataProvider changeEmailAddressDataProvider + */ + public function testChangeEmailAddress( + string $testMethodName, + string $fieldName + ): void { + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['set']) + ->setConstructorArgs([['id' => 'foo']]) + ->getMock(); + $sut->expects($this->once())->method('set')->with( + $this->identicalTo($fieldName) + ); + + $this->callMethod( + $sut, + $testMethodName, + ['newValue'] + ); + } + + public static function changeEmailAddressDataProvider(): Generator + { + yield 'change email has endpoint' => ['changeEmailAddress', 'email']; + yield 'change email has no endpoint' => ['changeEmailAddress', 'email']; + yield 'set phone has endpoint' => ['setSmsPhone', 'sms_phone']; + yield 'set phone has no endpoint' => ['setSmsPhone', 'sms_phone']; + } + + /** + * @test + * @return void + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getFields + */ + public function testGetFields() + { + $fields = $this->callMethod( + $this->entity, + 'getFields', + ); + + $this->assertInstanceOf(ArrayCollection::class, $fields); + $this->assertCount(16, $fields); + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getField + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getFieldLongName + * @dataProvider getFieldDataProvider + */ + public function testGetField(string $fieldName, string $expectedFieldName): void + { + $arrayCollectionMock = $this->getMockBuilder(ArrayCollection::class) + ->onlyMethods(['get']) + ->getMock(); + $arrayCollectionMock->expects($this->once())->method('get')->with( + $this->identicalTo($expectedFieldName) + )->willReturn('expected'); + + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['getFields']) + ->getMock(); + $sut->method('getFields')->willReturn($arrayCollectionMock); + + $this->assertSame( + 'expected', + $this->callMethod( + $sut, + 'getField', + [$fieldName] + ) + ); + } + + public static function getFieldDataProvider(): Generator + { + yield 'short field name' => ['FirstName', 'fieldFirstName']; + yield 'long field name' => ['fieldLastName', 'fieldLastName']; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::setField + * @dataProvider setFieldDataProvider + */ + public function testSetField( + string $fieldName, + string $longFieldName + ): void { + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['set']) + ->setConstructorArgs([['id' => 'foo']]) + ->getMock(); + $sut->expects($this->once())->method('set')->with( + $this->identicalTo($longFieldName) + ); + + $this->callMethod( + $sut, + 'setField', + [$fieldName, 'newValue'] + ); + } + + public static function setFieldDataProvider(): Generator + { + yield 'short field name' => ['FirstName', 'fieldFirstName']; + yield 'long field name' => ['fieldLastName', 'fieldLastName']; + } + + /** + * @test + * @return void + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getTags + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getManualTags + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmartTags + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getStartedCampaigns + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getFinishedCampaigns + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSentNotificationEmails + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getOpenedNotificationEmails + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getClickedNotificationEmails + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getViewedNotificationEmails + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getOutbounds + * @dataProvider getTagsDataProvider + */ + public function testGetTags(string $methodName, int $expectedCount) + { + $tags = $this->callMethod( + $this->entity, + $methodName, + ); + + $this->assertInstanceOf(ArrayCollection::class, $tags); + $this->assertCount($expectedCount, $tags); + } + + public static function getTagsDataProvider(): Generator + { + yield ['getTags', 2]; + yield ['getManualTags', 3]; + yield ['getSmartTags', 4]; + yield ['getStartedCampaigns', 1]; + yield ['getFinishedCampaigns', 2]; + yield ['getSentNotificationEmails', 3]; + yield ['getOpenedNotificationEmails', 4]; + yield ['getClickedNotificationEmails', 5]; + yield ['getViewedNotificationEmails', 6]; + yield ['getOutbounds', 1]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::isTagSet + * @dataProvider isTagSetDataProvider + */ + public function testIsTagSet(string $searchTagId, bool $expected) + { + $tagList = new ArrayCollection([ + "12494453", + "12494463", + ]); + + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['getTags']) + ->getMock(); + $sut->method('getTags')->willReturn($tagList); + + $this->assertSame( + $expected, + $this->callMethod( + $sut, + 'isTagSet', + [$searchTagId] + ) + ); + } + + public static function isTagSetDataProvider(): Generator + { + yield 'existing tag' => ['12494463', true]; + yield 'missing tag' => ['12495463', false]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::persist + * @dataProvider persistDataProvider + */ + public function testPersist( + bool $endpointSet, + InvokedCount $endpointInvocation, + ?bool $expectedReturn + ): void { + $endpointMock = $this->getMockBuilder(\D3\KlicktippPhpClient\Resources\Subscriber::class) + ->disableOriginalConstructor() + ->onlyMethods(['update']) + ->getMock(); + $endpointMock->expects($endpointInvocation)->method('update')->willReturn(true); + + $sut = new Subscriber(['id' => 'foo'], $endpointSet ? $endpointMock : null); + + $this->assertSame( + $expectedReturn, + $this->callMethod( + $sut, + 'persist' + ) + ); + } + + public static function persistDataProvider(): Generator + { + yield 'has endpoint' => [true, self::once(), true]; + yield 'has no endpoint' => [false, self::never(), null]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::isManualTagSet + * @dataProvider isManualTagSetDataProvider + */ + public function testIsManualTagSet(string $searchTagId, bool $expected) + { + $this->assertSame( + $expected, + $this->callMethod( + $this->entity, + 'isManualTagSet', + [$searchTagId] + ) + ); + } + + public static function isManualTagSetDataProvider(): Generator + { + yield 'existing tag' => ['12594453', true]; + yield 'missing tag' => ['12605453', false]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getManualTagTime + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getSmartTagTime + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getStartedCampaignTime + * @covers \D3\KlicktippPhpClient\Entities\Subscriber::getFinishedCampaignTime + * @dataProvider getTagDataProvider + */ + public function testGetTag(string $testMethodName, string $invokedMethodName) + { + $fixtureDate = '2024-12-24 18:00:00'; + $fixture = new DateTime($fixtureDate); + + $tagsMock = $this->getMockBuilder(ArrayCollection::class) + ->disableOriginalConstructor() + ->onlyMethods(['get']) + ->getMock(); + $tagsMock->expects($this->once())->method('get')->with( + $this->identicalTo('searchTagId') + )->willReturn($fixtureDate); + + $sut = $this->getMockBuilder(Subscriber::class) + ->onlyMethods([$invokedMethodName]) + ->getMock(); + $sut->method($invokedMethodName)->willReturn($tagsMock); + + $this->assertEquals( + $fixture, + $this->callMethod( + $sut, + $testMethodName, + ['searchTagId'] + ) + ); + } + + public static function getTagDataProvider(): Generator + { + yield ['getManualTagTime', 'getManualTags']; + yield ['getSmartTagTime', 'getSmartTags']; + yield ['getStartedCampaignTime', 'getStartedCampaigns']; + yield ['getFinishedCampaignTime', 'getFinishedCampaigns']; + } +}