From 3a2c2b4159d6718ebcbe14d11db00449b49cac31 Mon Sep 17 00:00:00 2001 From: Tatevik Date: Thu, 19 Jun 2025 18:57:26 +0400 Subject: [PATCH 1/2] ISSUE-122: deleteLeavingBlacklist --- config/services/managers.yml | 5 + config/services/repositories.yml | 15 ++ .../Messaging/Model/UserMessageBounce.php | 13 +- .../Messaging/Model/UserMessageForward.php | 10 +- .../Service/Manager/SubscriberManager.php | 8 +- .../Service/SubscriberDeletionService.php | 96 ++++++++ .../Service/SubscriberDeletionServiceTest.php | 145 +++++++++++ .../Service/SubscriberDeletionServiceTest.php | 233 ++++++++++++++++++ 8 files changed, 512 insertions(+), 13 deletions(-) create mode 100644 src/Domain/Subscription/Service/SubscriberDeletionService.php create mode 100644 tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php create mode 100644 tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php diff --git a/config/services/managers.yml b/config/services/managers.yml index e0f18d4e..edd3a30f 100644 --- a/config/services/managers.yml +++ b/config/services/managers.yml @@ -55,3 +55,8 @@ services: PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager: autowire: true autoconfigure: true + + PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService: + autowire: true + autoconfigure: true + public: true diff --git a/config/services/repositories.yml b/config/services/repositories.yml index 5e4f4473..af452145 100644 --- a/config/services/repositories.yml +++ b/config/services/repositories.yml @@ -85,3 +85,18 @@ services: parent: PhpList\Core\Domain\Common\Repository\AbstractRepository arguments: - PhpList\Core\Domain\Analytics\Model\UserMessageView + + PhpList\Core\Domain\Analytics\Repository\LinkTrackUmlClickRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Analytics\Model\LinkTrackUmlClick + + PhpList\Core\Domain\Messaging\Repository\UserMessageRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Messaging\Model\UserMessage + + PhpList\Core\Domain\Subscription\Repository\SubscriberHistoryRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Subscription\Model\SubscriberHistory diff --git a/src/Domain/Messaging/Model/UserMessageBounce.php b/src/Domain/Messaging/Model/UserMessageBounce.php index c842867f..ccb05597 100644 --- a/src/Domain/Messaging/Model/UserMessageBounce.php +++ b/src/Domain/Messaging/Model/UserMessageBounce.php @@ -25,7 +25,7 @@ class UserMessageBounce implements DomainModel, Identity private ?int $id = null; #[ORM\Column(name: 'user', type: 'integer')] - private int $user; + private int $userId; #[ORM\Column(name: 'message', type: 'integer')] private int $messageId; @@ -36,8 +36,9 @@ class UserMessageBounce implements DomainModel, Identity #[ORM\Column(name: 'time', type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])] private DateTime $createdAt; - public function __construct() + public function __construct(int $bounce) { + $this->bounce = $bounce; $this->createdAt = new DateTime(); } @@ -46,9 +47,9 @@ public function getId(): ?int return $this->id; } - public function getUser(): int + public function getUserId(): int { - return $this->user; + return $this->userId; } public function getMessageId(): int @@ -66,9 +67,9 @@ public function getCreatedAt(): DateTime return $this->createdAt; } - public function setUser(int $user): self + public function setUserId(int $userId): self { - $this->user = $user; + $this->userId = $userId; return $this; } diff --git a/src/Domain/Messaging/Model/UserMessageForward.php b/src/Domain/Messaging/Model/UserMessageForward.php index a9432e45..9b2a8ef4 100644 --- a/src/Domain/Messaging/Model/UserMessageForward.php +++ b/src/Domain/Messaging/Model/UserMessageForward.php @@ -23,7 +23,7 @@ class UserMessageForward implements DomainModel, Identity private ?int $id = null; #[ORM\Column(name: 'user', type: 'integer')] - private int $user; + private int $userId; #[ORM\Column(name: 'message', type: 'integer')] private int $messageId; @@ -47,9 +47,9 @@ public function getId(): ?int return $this->id; } - public function getUser(): int + public function getUserId(): int { - return $this->user; + return $this->userId; } public function getMessageId(): int @@ -72,9 +72,9 @@ public function getCreatedAt(): DateTime return $this->createdAt; } - public function setUser(int $user): self + public function setUserId(int $userId): self { - $this->user = $user; + $this->userId = $userId; return $this; } diff --git a/src/Domain/Subscription/Service/Manager/SubscriberManager.php b/src/Domain/Subscription/Service/Manager/SubscriberManager.php index 4c142b15..93420795 100644 --- a/src/Domain/Subscription/Service/Manager/SubscriberManager.php +++ b/src/Domain/Subscription/Service/Manager/SubscriberManager.php @@ -11,6 +11,7 @@ use PhpList\Core\Domain\Subscription\Model\Dto\UpdateSubscriberDto; use PhpList\Core\Domain\Subscription\Model\Subscriber; use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository; +use PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Messenger\MessageBusInterface; @@ -19,15 +20,18 @@ class SubscriberManager private SubscriberRepository $subscriberRepository; private EntityManagerInterface $entityManager; private MessageBusInterface $messageBus; + private SubscriberDeletionService $subscriberDeletionService; public function __construct( SubscriberRepository $subscriberRepository, EntityManagerInterface $entityManager, - MessageBusInterface $messageBus + MessageBusInterface $messageBus, + SubscriberDeletionService $subscriberDeletionService ) { $this->subscriberRepository = $subscriberRepository; $this->entityManager = $entityManager; $this->messageBus = $messageBus; + $this->subscriberDeletionService = $subscriberDeletionService; } public function createSubscriber(CreateSubscriberDto $subscriberDto): Subscriber @@ -90,7 +94,7 @@ public function updateSubscriber(UpdateSubscriberDto $subscriberDto): Subscriber public function deleteSubscriber(Subscriber $subscriber): void { - $this->subscriberRepository->remove($subscriber); + $this->subscriberDeletionService->deleteLeavingBlacklist($subscriber); } public function createFromImport(ImportSubscriberDto $subscriberDto): Subscriber diff --git a/src/Domain/Subscription/Service/SubscriberDeletionService.php b/src/Domain/Subscription/Service/SubscriberDeletionService.php new file mode 100644 index 00000000..9ad3388a --- /dev/null +++ b/src/Domain/Subscription/Service/SubscriberDeletionService.php @@ -0,0 +1,96 @@ +linkTrackUmlClickRepository = $linkTrackUmlClickRepository; + $this->entityManager = $entityManager; + $this->userMessageRepository = $userMessageRepository; + $this->subscriberAttributeValueRepository = $subscriberAttributeValueRepository; + $this->subscriberHistoryRepository = $subscriberHistoryRepository; + $this->userMessageBounceRepository = $userMessageBounceRepository; + $this->userMessageForwardRepository = $userMessageForwardRepository; + $this->userMessageViewRepository = $userMessageViewRepository; + $this->subscriptionRepository = $subscriptionRepository; + } + + public function deleteLeavingBlacklist(Subscriber $subscriber): void + { + $linkTrackUmlClick = $this->linkTrackUmlClickRepository->findBy(['userId' => $subscriber->getId()]); + foreach ($linkTrackUmlClick as $click) { + $this->entityManager->remove($click); + } + + $subscriptions = $this->subscriptionRepository->findBy(['subscriber' => $subscriber]); + foreach ($subscriptions as $subscription) { + $this->entityManager->remove($subscription); + } + + $userMessages = $this->userMessageRepository->findBy(['user' => $subscriber]); + foreach ($userMessages as $message) { + $this->entityManager->remove($message); + } + + $subscriberAttributes = $this->subscriberAttributeValueRepository->findBy(['subscriber' => $subscriber]); + foreach ($subscriberAttributes as $attribute) { + $this->entityManager->remove($attribute); + } + + $subscriberHistory = $this->subscriberHistoryRepository->findBy(['subscriber' => $subscriber]); + foreach ($subscriberHistory as $history) { + $this->entityManager->remove($history); + } + + $userMessageBounces = $this->userMessageBounceRepository->findBy(['userId' => $subscriber->getId()]); + foreach ($userMessageBounces as $bounce) { + $this->entityManager->remove($bounce); + } + + $userMessageForwards = $this->userMessageForwardRepository->findBy(['userId' => $subscriber->getId()]); + foreach ($userMessageForwards as $forward) { + $this->entityManager->remove($forward); + } + + $userMessageViews = $this->userMessageViewRepository->findBy(['userId' => $subscriber->getId()]); + foreach ($userMessageViews as $view) { + $this->entityManager->remove($view); + } + + $this->entityManager->remove($subscriber); + } +} diff --git a/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php b/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php new file mode 100644 index 00000000..ec91345d --- /dev/null +++ b/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php @@ -0,0 +1,145 @@ +loadSchema(); + + $this->subscriberDeletionService = self::getContainer()->get(SubscriberDeletionService::class); + $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); + } + + protected function tearDown(): void + { + $schemaTool = new SchemaTool($this->entityManager); + $schemaTool->dropDatabase(); + parent::tearDown(); + } + + public function testDeleteSubscriberWithRelatedDataDoesNotThrowDoctrineError(): void + { + $admin = new Administrator(); + $this->entityManager->persist($admin); + + $msg = new Message( + new MessageFormat(true, MessageFormat::FORMAT_TEXT), + new MessageSchedule(1, null, 3, null, null), + new MessageMetadata('done'), + new MessageContent('Owned by Admin 1!'), + new MessageOptions(), + $admin + ); + $this->entityManager->persist($msg); + + $subscriber = new Subscriber(); + $subscriber->setEmail('test-delete@example.com'); + $subscriber->setConfirmed(true); + $subscriber->setHtmlEmail(true); + $subscriber->setBlacklisted(false); + $subscriber->setDisabled(false); + $this->entityManager->persist($subscriber); + $this->entityManager->flush(); + + $subscriberId = $subscriber->getId(); + $this->assertNotNull($subscriberId, 'Subscriber ID should not be null'); + + $subscriberList = new SubscriberList(); + $subscriberList->setDescription('Test List Description'); + $this->entityManager->persist($subscriberList); + + $subscription = new Subscription(); + $subscription->setSubscriber($subscriber); + $subscription->setSubscriberList($subscriberList); + $this->entityManager->persist($subscription); + + $linkTrackUmlClick = new LinkTrackUmlClick(); + $linkTrackUmlClick->setMessageId(1); + $linkTrackUmlClick->setUserId($subscriberId); + $this->entityManager->persist($linkTrackUmlClick); + + $userMessage = new UserMessage($subscriber, $msg); + $userMessage->setStatus('sent'); + $this->entityManager->persist($userMessage); + + $userMessageBounce = new UserMessageBounce(1); + $userMessageBounce->setUserId($subscriberId); + $userMessageBounce->setMessageId(1); + $this->entityManager->persist($userMessageBounce); + + $userMessageForward = new UserMessageForward(); + $userMessageForward->setUserId($subscriberId); + $userMessageForward->setMessageId(1); + $this->entityManager->persist($userMessageForward); + + $userMessageView = new UserMessageView(); + $userMessageView->setMessageId(1); + $userMessageView->setUserid($subscriberId); + $this->entityManager->persist($userMessageView); + + $this->entityManager->flush(); + + try { + $this->subscriberDeletionService->deleteLeavingBlacklist($subscriber); + $this->entityManager->flush(); + $this->assertTrue(true, 'No exception was thrown'); + } catch (Exception $e) { + $this->fail('Exception was thrown: ' . $e->getMessage()); + } + + // Verify the subscriber was deleted + $deletedSubscriber = $this->entityManager->getRepository(Subscriber::class)->find($subscriberId); + $this->assertNull($deletedSubscriber, 'Subscriber should be deleted'); + + // Verify related entities were deleted + $subscriptions = $this->entityManager->getRepository(Subscription::class)->findBy(['subscriber' => $subscriber]); + $this->assertEmpty($subscriptions, 'Subscriptions should be deleted'); + + $linkTrackUmlClicks = $this->entityManager->getRepository(LinkTrackUmlClick::class)->findBy(['userId' => $subscriberId]); + $this->assertEmpty($linkTrackUmlClicks, 'LinkTrackUmlClicks should be deleted'); + + $userMessages = $this->entityManager->getRepository(UserMessage::class)->findBy(['user' => $subscriber]); + $this->assertEmpty($userMessages, 'UserMessages should be deleted'); + + $userMessageBounces = $this->entityManager->getRepository(UserMessageBounce::class)->findBy(['userId' => $subscriberId]); + $this->assertEmpty($userMessageBounces, 'UserMessageBounces should be deleted'); + + $userMessageForwards = $this->entityManager->getRepository(UserMessageForward::class)->findBy(['userId' => $subscriberId]); + $this->assertEmpty($userMessageForwards, 'UserMessageForwards should be deleted'); + + $userMessageViews = $this->entityManager->getRepository(UserMessageView::class)->findBy(['userId' => $subscriberId]); + $this->assertEmpty($userMessageViews, 'UserMessageViews should be deleted'); + } +} diff --git a/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php b/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php new file mode 100644 index 00000000..284f8b37 --- /dev/null +++ b/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php @@ -0,0 +1,233 @@ +linkTrackUmlClickRepository = $this->createMock(LinkTrackUmlClickRepository::class); + $this->entityManager = $this->createMock(EntityManagerInterface::class); + $this->userMessageRepository = $this->createMock(UserMessageRepository::class); + $this->subscriberRepository = $this->createMock(SubscriberRepository::class); + $this->subscriberAttributeValueRepository = $this->createMock(SubscriberAttributeValueRepository::class); + $this->subscriberHistoryRepository = $this->createMock(SubscriberHistoryRepository::class); + $this->userMessageBounceRepository = $this->createMock(UserMessageBounceRepository::class); + $this->userMessageForwardRepository = $this->createMock(UserMessageForwardRepository::class); + $this->userMessageViewRepository = $this->createMock(UserMessageViewRepository::class); + + $this->service = new SubscriberDeletionService( + $this->linkTrackUmlClickRepository, + $this->entityManager, + $this->userMessageRepository, + $this->subscriberRepository, + $this->subscriberAttributeValueRepository, + $this->subscriberHistoryRepository, + $this->userMessageBounceRepository, + $this->userMessageForwardRepository, + $this->userMessageViewRepository + ); + } + + public function testDeleteLeavingBlacklistRemovesAllRelatedData(): void + { + $subscriber = $this->createMock(Subscriber::class); + $subscriberId = 123; + $subscriber->method('getId')->willReturn($subscriberId); + + $subscription = $this->createMock(Subscription::class); + $subscriptions = new ArrayCollection([$subscription]); + $subscriber->method('getSubscriptions')->willReturn($subscriptions); + + $linkTrackUmlClick = $this->createMock(LinkTrackUmlClick::class); + $this->linkTrackUmlClickRepository + ->method('findBy') + ->with(['userid' => $subscriberId]) + ->willReturn([$linkTrackUmlClick]); + $this->linkTrackUmlClickRepository + ->expects($this->once()) + ->method('remove') + ->with($linkTrackUmlClick); + + $this->entityManager + ->expects($this->once()) + ->method('remove') + ->with($subscription); + + $userMessage = $this->createMock(UserMessage::class); + $this->userMessageRepository + ->method('findBy') + ->with(['user' => $subscriber]) + ->willReturn([$userMessage]); + $this->userMessageRepository + ->expects($this->once()) + ->method('remove') + ->with($userMessage); + + $subscriberAttribute = $this->createMock(SubscriberAttributeValue::class); + $this->subscriberAttributeValueRepository + ->method('findBy') + ->with(['subscriber' => $subscriber]) + ->willReturn([$subscriberAttribute]); + $this->subscriberAttributeValueRepository + ->expects($this->once()) + ->method('remove') + ->with($subscriberAttribute); + + $subscriberHistory = $this->createMock(SubscriberHistory::class); + $this->subscriberHistoryRepository + ->method('findBy') + ->with(['subscriber' => $subscriber]) + ->willReturn([$subscriberHistory]); + $this->subscriberHistoryRepository + ->expects($this->once()) + ->method('remove') + ->with($subscriberHistory); + + $userMessageBounce = $this->createMock(UserMessageBounce::class); + $this->userMessageBounceRepository + ->method('findBy') + ->with(['userId' => $subscriberId]) + ->willReturn([$userMessageBounce]); + $this->userMessageBounceRepository + ->expects($this->once()) + ->method('remove') + ->with($userMessageBounce); + + $userMessageForward = $this->createMock(UserMessageForward::class); + $this->userMessageForwardRepository + ->method('findBy') + ->with(['userId' => $subscriberId]) + ->willReturn([$userMessageForward]); + $this->userMessageForwardRepository + ->expects($this->once()) + ->method('remove') + ->with($userMessageForward); + + $userMessageView = $this->createMock(UserMessageView::class); + $this->userMessageViewRepository + ->method('findBy') + ->with(['userid' => $subscriberId]) + ->willReturn([$userMessageView]); + $this->userMessageViewRepository + ->expects($this->once()) + ->method('remove') + ->with($userMessageView); + + $this->subscriberRepository + ->expects($this->once()) + ->method('remove') + ->with($subscriber); + + $this->service->deleteLeavingBlacklist($subscriber); + } + + public function testDeleteLeavingBlacklistHandlesEmptyRelatedData(): void + { + $subscriber = $this->createMock(Subscriber::class); + $subscriberId = 123; + $subscriber->method('getId')->willReturn($subscriberId); + + $emptySubscriptions = new ArrayCollection(); + $subscriber->method('getSubscriptions')->willReturn($emptySubscriptions); + + $this->linkTrackUmlClickRepository + ->method('findBy') + ->with(['userid' => $subscriberId]) + ->willReturn([]); + $this->linkTrackUmlClickRepository + ->expects($this->never()) + ->method('remove'); + + $this->userMessageRepository + ->method('findBy') + ->with(['user' => $subscriber]) + ->willReturn([]); + $this->userMessageRepository + ->expects($this->never()) + ->method('remove'); + + $this->subscriberAttributeValueRepository + ->method('findBy') + ->with(['subscriber' => $subscriber]) + ->willReturn([]); + $this->subscriberAttributeValueRepository + ->expects($this->never()) + ->method('remove'); + + $this->subscriberHistoryRepository + ->method('findBy') + ->with(['subscriber' => $subscriber]) + ->willReturn([]); + $this->subscriberHistoryRepository + ->expects($this->never()) + ->method('remove'); + + $this->userMessageBounceRepository + ->method('findBy') + ->with(['userId' => $subscriberId]) + ->willReturn([]); + $this->userMessageBounceRepository + ->expects($this->never()) + ->method('remove'); + + $this->userMessageForwardRepository + ->method('findBy') + ->with(['userId' => $subscriberId]) + ->willReturn([]); + $this->userMessageForwardRepository + ->expects($this->never()) + ->method('remove'); + + $this->userMessageViewRepository + ->method('findBy') + ->with(['userid' => $subscriberId]) + ->willReturn([]); + $this->userMessageViewRepository + ->expects($this->never()) + ->method('remove'); + + $this->subscriberRepository + ->expects($this->once()) + ->method('remove') + ->with($subscriber); + + $this->service->deleteLeavingBlacklist($subscriber); + } +} From 442b139659e3c20cafac7c50ab185d20e8792f1d Mon Sep 17 00:00:00 2001 From: Tatevik Date: Fri, 20 Jun 2025 21:00:32 +0400 Subject: [PATCH 2/2] ISSUE-124: style + test fix --- .../Service/SubscriberDeletionService.php | 105 +++++++----------- .../Service/SubscriberDeletionServiceTest.php | 32 +++--- .../Service/SubscriberDeletionServiceTest.php | 73 ++++-------- .../Service/SubscriberManagerTest.php | 48 ++++++-- 4 files changed, 122 insertions(+), 136 deletions(-) diff --git a/src/Domain/Subscription/Service/SubscriberDeletionService.php b/src/Domain/Subscription/Service/SubscriberDeletionService.php index 9ad3388a..9681a49d 100644 --- a/src/Domain/Subscription/Service/SubscriberDeletionService.php +++ b/src/Domain/Subscription/Service/SubscriberDeletionService.php @@ -17,80 +17,61 @@ class SubscriberDeletionService { - private LinkTrackUmlClickRepository $linkTrackUmlClickRepository; + private LinkTrackUmlClickRepository $linkTrackUmlClickRepo; private EntityManagerInterface $entityManager; - private UserMessageRepository $userMessageRepository; - private SubscriberAttributeValueRepository $subscriberAttributeValueRepository; - private SubscriberHistoryRepository $subscriberHistoryRepository; - private UserMessageBounceRepository $userMessageBounceRepository; - private UserMessageForwardRepository $userMessageForwardRepository; - private UserMessageViewRepository $userMessageViewRepository; - private SubscriptionRepository $subscriptionRepository; + private UserMessageRepository $userMessageRepo; + private SubscriberAttributeValueRepository $subscriberAttrValueRepo; + private SubscriberHistoryRepository $subscriberHistoryRepo; + private UserMessageBounceRepository $userMessageBounceRepo; + private UserMessageForwardRepository $userMessageForwardRepo; + private UserMessageViewRepository $userMessageViewRepo; + private SubscriptionRepository $subscriptionRepo; public function __construct( - LinkTrackUmlClickRepository $linkTrackUmlClickRepository, + LinkTrackUmlClickRepository $linkTrackUmlClickRepo, EntityManagerInterface $entityManager, - UserMessageRepository $userMessageRepository, - SubscriberAttributeValueRepository $subscriberAttributeValueRepository, - SubscriberHistoryRepository $subscriberHistoryRepository, - UserMessageBounceRepository $userMessageBounceRepository, - UserMessageForwardRepository $userMessageForwardRepository, - UserMessageViewRepository $userMessageViewRepository, - SubscriptionRepository $subscriptionRepository + UserMessageRepository $userMessageRepo, + SubscriberAttributeValueRepository $subscriberAttrValueRepo, + SubscriberHistoryRepository $subscriberHistoryRepo, + UserMessageBounceRepository $userMessageBounceRepo, + UserMessageForwardRepository $userMessageForwardRepo, + UserMessageViewRepository $userMessageViewRepo, + SubscriptionRepository $subscriptionRepo, ) { - $this->linkTrackUmlClickRepository = $linkTrackUmlClickRepository; + $this->linkTrackUmlClickRepo = $linkTrackUmlClickRepo; $this->entityManager = $entityManager; - $this->userMessageRepository = $userMessageRepository; - $this->subscriberAttributeValueRepository = $subscriberAttributeValueRepository; - $this->subscriberHistoryRepository = $subscriberHistoryRepository; - $this->userMessageBounceRepository = $userMessageBounceRepository; - $this->userMessageForwardRepository = $userMessageForwardRepository; - $this->userMessageViewRepository = $userMessageViewRepository; - $this->subscriptionRepository = $subscriptionRepository; + $this->userMessageRepo = $userMessageRepo; + $this->subscriberAttrValueRepo = $subscriberAttrValueRepo; + $this->subscriberHistoryRepo = $subscriberHistoryRepo; + $this->userMessageBounceRepo = $userMessageBounceRepo; + $this->userMessageForwardRepo = $userMessageForwardRepo; + $this->userMessageViewRepo = $userMessageViewRepo; + $this->subscriptionRepo = $subscriptionRepo; } public function deleteLeavingBlacklist(Subscriber $subscriber): void { - $linkTrackUmlClick = $this->linkTrackUmlClickRepository->findBy(['userId' => $subscriber->getId()]); - foreach ($linkTrackUmlClick as $click) { - $this->entityManager->remove($click); - } - - $subscriptions = $this->subscriptionRepository->findBy(['subscriber' => $subscriber]); - foreach ($subscriptions as $subscription) { - $this->entityManager->remove($subscription); - } - - $userMessages = $this->userMessageRepository->findBy(['user' => $subscriber]); - foreach ($userMessages as $message) { - $this->entityManager->remove($message); - } - - $subscriberAttributes = $this->subscriberAttributeValueRepository->findBy(['subscriber' => $subscriber]); - foreach ($subscriberAttributes as $attribute) { - $this->entityManager->remove($attribute); - } - - $subscriberHistory = $this->subscriberHistoryRepository->findBy(['subscriber' => $subscriber]); - foreach ($subscriberHistory as $history) { - $this->entityManager->remove($history); - } + $this->removeEntities($this->linkTrackUmlClickRepo->findBy(['userId' => $subscriber->getId()])); + $this->removeEntities($this->subscriptionRepo->findBy(['subscriber' => $subscriber])); + $this->removeEntities($this->userMessageRepo->findBy(['user' => $subscriber])); + $this->removeEntities($this->subscriberAttrValueRepo->findBy(['subscriber' => $subscriber])); + $this->removeEntities($this->subscriberHistoryRepo->findBy(['subscriber' => $subscriber])); + $this->removeEntities($this->userMessageBounceRepo->findBy(['userId' => $subscriber->getId()])); + $this->removeEntities($this->userMessageForwardRepo->findBy(['userId' => $subscriber->getId()])); + $this->removeEntities($this->userMessageViewRepo->findBy(['userId' => $subscriber->getId()])); - $userMessageBounces = $this->userMessageBounceRepository->findBy(['userId' => $subscriber->getId()]); - foreach ($userMessageBounces as $bounce) { - $this->entityManager->remove($bounce); - } - - $userMessageForwards = $this->userMessageForwardRepository->findBy(['userId' => $subscriber->getId()]); - foreach ($userMessageForwards as $forward) { - $this->entityManager->remove($forward); - } + $this->entityManager->remove($subscriber); + } - $userMessageViews = $this->userMessageViewRepository->findBy(['userId' => $subscriber->getId()]); - foreach ($userMessageViews as $view) { - $this->entityManager->remove($view); + /** + * Remove a collection of entities + * + * @param array $entities + */ + private function removeEntities(array $entities): void + { + foreach ($entities as $entity) { + $this->entityManager->remove($entity); } - - $this->entityManager->remove($subscriber); } } diff --git a/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php b/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php index ec91345d..e6d42236 100644 --- a/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php +++ b/tests/Integration/Domain/Subscription/Service/SubscriberDeletionServiceTest.php @@ -55,12 +55,12 @@ public function testDeleteSubscriberWithRelatedDataDoesNotThrowDoctrineError(): $this->entityManager->persist($admin); $msg = new Message( - new MessageFormat(true, MessageFormat::FORMAT_TEXT), - new MessageSchedule(1, null, 3, null, null), - new MessageMetadata('done'), - new MessageContent('Owned by Admin 1!'), - new MessageOptions(), - $admin + format: new MessageFormat(true, MessageFormat::FORMAT_TEXT), + schedule: new MessageSchedule(1, null, 3, null, null), + metadata: new MessageMetadata('done'), + content: new MessageContent('Owned by Admin 1!'), + options: new MessageOptions(), + owner: $admin ); $this->entityManager->persist($msg); @@ -119,27 +119,31 @@ public function testDeleteSubscriberWithRelatedDataDoesNotThrowDoctrineError(): $this->fail('Exception was thrown: ' . $e->getMessage()); } - // Verify the subscriber was deleted $deletedSubscriber = $this->entityManager->getRepository(Subscriber::class)->find($subscriberId); $this->assertNull($deletedSubscriber, 'Subscriber should be deleted'); - // Verify related entities were deleted - $subscriptions = $this->entityManager->getRepository(Subscription::class)->findBy(['subscriber' => $subscriber]); + $subscriptionRepo = $this->entityManager->getRepository(Subscription::class); + $subscriptions = $subscriptionRepo->findBy(['subscriber' => $subscriber]); $this->assertEmpty($subscriptions, 'Subscriptions should be deleted'); - $linkTrackUmlClicks = $this->entityManager->getRepository(LinkTrackUmlClick::class)->findBy(['userId' => $subscriberId]); + $linkTrackRepo = $this->entityManager->getRepository(LinkTrackUmlClick::class); + $linkTrackUmlClicks = $linkTrackRepo->findBy(['userId' => $subscriberId]); $this->assertEmpty($linkTrackUmlClicks, 'LinkTrackUmlClicks should be deleted'); - $userMessages = $this->entityManager->getRepository(UserMessage::class)->findBy(['user' => $subscriber]); + $userMessageRepo = $this->entityManager->getRepository(UserMessage::class); + $userMessages = $userMessageRepo->findBy(['user' => $subscriber]); $this->assertEmpty($userMessages, 'UserMessages should be deleted'); - $userMessageBounces = $this->entityManager->getRepository(UserMessageBounce::class)->findBy(['userId' => $subscriberId]); + $bounceRepo = $this->entityManager->getRepository(UserMessageBounce::class); + $userMessageBounces = $bounceRepo->findBy(['userId' => $subscriberId]); $this->assertEmpty($userMessageBounces, 'UserMessageBounces should be deleted'); - $userMessageForwards = $this->entityManager->getRepository(UserMessageForward::class)->findBy(['userId' => $subscriberId]); + $forwardRepo = $this->entityManager->getRepository(UserMessageForward::class); + $userMessageForwards = $forwardRepo->findBy(['userId' => $subscriberId]); $this->assertEmpty($userMessageForwards, 'UserMessageForwards should be deleted'); - $userMessageViews = $this->entityManager->getRepository(UserMessageView::class)->findBy(['userId' => $subscriberId]); + $viewRepo = $this->entityManager->getRepository(UserMessageView::class); + $userMessageViews = $viewRepo->findBy(['userId' => $subscriberId]); $this->assertEmpty($userMessageViews, 'UserMessageViews should be deleted'); } } diff --git a/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php b/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php index 284f8b37..1ba1d7d0 100644 --- a/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php +++ b/tests/Unit/Domain/Subscription/Service/SubscriberDeletionServiceTest.php @@ -4,7 +4,6 @@ namespace PhpList\Core\Tests\Unit\Domain\Subscription\Service; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use PhpList\Core\Domain\Analytics\Repository\LinkTrackUmlClickRepository; use PhpList\Core\Domain\Analytics\Repository\UserMessageViewRepository; @@ -23,6 +22,7 @@ use PhpList\Core\Domain\Subscription\Repository\SubscriberAttributeValueRepository; use PhpList\Core\Domain\Subscription\Repository\SubscriberHistoryRepository; use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository; +use PhpList\Core\Domain\Subscription\Repository\SubscriptionRepository; use PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -32,12 +32,12 @@ class SubscriberDeletionServiceTest extends TestCase private LinkTrackUmlClickRepository&MockObject $linkTrackUmlClickRepository; private EntityManagerInterface&MockObject $entityManager; private UserMessageRepository&MockObject $userMessageRepository; - private SubscriberRepository&MockObject $subscriberRepository; private SubscriberAttributeValueRepository&MockObject $subscriberAttributeValueRepository; private SubscriberHistoryRepository&MockObject $subscriberHistoryRepository; private UserMessageBounceRepository&MockObject $userMessageBounceRepository; private UserMessageForwardRepository&MockObject $userMessageForwardRepository; private UserMessageViewRepository&MockObject $userMessageViewRepository; + private SubscriptionRepository&MockObject $subscriptionRepository; private SubscriberDeletionService $service; protected function setUp(): void @@ -45,23 +45,23 @@ protected function setUp(): void $this->linkTrackUmlClickRepository = $this->createMock(LinkTrackUmlClickRepository::class); $this->entityManager = $this->createMock(EntityManagerInterface::class); $this->userMessageRepository = $this->createMock(UserMessageRepository::class); - $this->subscriberRepository = $this->createMock(SubscriberRepository::class); $this->subscriberAttributeValueRepository = $this->createMock(SubscriberAttributeValueRepository::class); $this->subscriberHistoryRepository = $this->createMock(SubscriberHistoryRepository::class); $this->userMessageBounceRepository = $this->createMock(UserMessageBounceRepository::class); $this->userMessageForwardRepository = $this->createMock(UserMessageForwardRepository::class); $this->userMessageViewRepository = $this->createMock(UserMessageViewRepository::class); + $this->subscriptionRepository = $this->createMock(SubscriptionRepository::class); $this->service = new SubscriberDeletionService( $this->linkTrackUmlClickRepository, $this->entityManager, $this->userMessageRepository, - $this->subscriberRepository, $this->subscriberAttributeValueRepository, $this->subscriberHistoryRepository, $this->userMessageBounceRepository, $this->userMessageForwardRepository, - $this->userMessageViewRepository + $this->userMessageViewRepository, + $this->subscriptionRepository ); } @@ -72,88 +72,56 @@ public function testDeleteLeavingBlacklistRemovesAllRelatedData(): void $subscriber->method('getId')->willReturn($subscriberId); $subscription = $this->createMock(Subscription::class); - $subscriptions = new ArrayCollection([$subscription]); - $subscriber->method('getSubscriptions')->willReturn($subscriptions); + $this->subscriptionRepository + ->method('findBy') + ->with(['subscriber' => $subscriber]) + ->willReturn([$subscription]); $linkTrackUmlClick = $this->createMock(LinkTrackUmlClick::class); $this->linkTrackUmlClickRepository ->method('findBy') - ->with(['userid' => $subscriberId]) + ->with(['userId' => $subscriberId]) ->willReturn([$linkTrackUmlClick]); - $this->linkTrackUmlClickRepository - ->expects($this->once()) - ->method('remove') - ->with($linkTrackUmlClick); $this->entityManager - ->expects($this->once()) - ->method('remove') - ->with($subscription); + ->expects($this->atLeast(1)) + ->method('remove'); $userMessage = $this->createMock(UserMessage::class); $this->userMessageRepository ->method('findBy') ->with(['user' => $subscriber]) ->willReturn([$userMessage]); - $this->userMessageRepository - ->expects($this->once()) - ->method('remove') - ->with($userMessage); $subscriberAttribute = $this->createMock(SubscriberAttributeValue::class); $this->subscriberAttributeValueRepository ->method('findBy') ->with(['subscriber' => $subscriber]) ->willReturn([$subscriberAttribute]); - $this->subscriberAttributeValueRepository - ->expects($this->once()) - ->method('remove') - ->with($subscriberAttribute); $subscriberHistory = $this->createMock(SubscriberHistory::class); $this->subscriberHistoryRepository ->method('findBy') ->with(['subscriber' => $subscriber]) ->willReturn([$subscriberHistory]); - $this->subscriberHistoryRepository - ->expects($this->once()) - ->method('remove') - ->with($subscriberHistory); $userMessageBounce = $this->createMock(UserMessageBounce::class); $this->userMessageBounceRepository ->method('findBy') ->with(['userId' => $subscriberId]) ->willReturn([$userMessageBounce]); - $this->userMessageBounceRepository - ->expects($this->once()) - ->method('remove') - ->with($userMessageBounce); $userMessageForward = $this->createMock(UserMessageForward::class); $this->userMessageForwardRepository ->method('findBy') ->with(['userId' => $subscriberId]) ->willReturn([$userMessageForward]); - $this->userMessageForwardRepository - ->expects($this->once()) - ->method('remove') - ->with($userMessageForward); $userMessageView = $this->createMock(UserMessageView::class); $this->userMessageViewRepository ->method('findBy') - ->with(['userid' => $subscriberId]) + ->with(['userId' => $subscriberId]) ->willReturn([$userMessageView]); - $this->userMessageViewRepository - ->expects($this->once()) - ->method('remove') - ->with($userMessageView); - - $this->subscriberRepository - ->expects($this->once()) - ->method('remove') - ->with($subscriber); $this->service->deleteLeavingBlacklist($subscriber); } @@ -164,16 +132,15 @@ public function testDeleteLeavingBlacklistHandlesEmptyRelatedData(): void $subscriberId = 123; $subscriber->method('getId')->willReturn($subscriberId); - $emptySubscriptions = new ArrayCollection(); - $subscriber->method('getSubscriptions')->willReturn($emptySubscriptions); + $this->subscriptionRepository + ->method('findBy') + ->with(['subscriber' => $subscriber]) + ->willReturn([]); $this->linkTrackUmlClickRepository ->method('findBy') - ->with(['userid' => $subscriberId]) + ->with(['userId' => $subscriberId]) ->willReturn([]); - $this->linkTrackUmlClickRepository - ->expects($this->never()) - ->method('remove'); $this->userMessageRepository ->method('findBy') @@ -217,13 +184,13 @@ public function testDeleteLeavingBlacklistHandlesEmptyRelatedData(): void $this->userMessageViewRepository ->method('findBy') - ->with(['userid' => $subscriberId]) + ->with(['userId' => $subscriberId]) ->willReturn([]); $this->userMessageViewRepository ->expects($this->never()) ->method('remove'); - $this->subscriberRepository + $this->entityManager ->expects($this->once()) ->method('remove') ->with($subscriber); diff --git a/tests/Unit/Domain/Subscription/Service/SubscriberManagerTest.php b/tests/Unit/Domain/Subscription/Service/SubscriberManagerTest.php index d5564434..7d246b7d 100644 --- a/tests/Unit/Domain/Subscription/Service/SubscriberManagerTest.php +++ b/tests/Unit/Domain/Subscription/Service/SubscriberManagerTest.php @@ -11,30 +11,60 @@ use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository; use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberManager; use PHPUnit\Framework\MockObject\MockObject; +use PhpList\Core\Domain\Subscription\Service\SubscriberDeletionService; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; class SubscriberManagerTest extends TestCase { - private SubscriberRepository&MockObject $subscriberRepository; - private MessageBusInterface&MockObject $messageBus; + private SubscriberRepository|MockObject $subscriberRepository; + private EntityManagerInterface|MockObject $entityManager; + private MessageBusInterface|MockObject $messageBus; + private SubscriberDeletionService|MockObject $subscriberDeletionService; private SubscriberManager $subscriberManager; protected function setUp(): void { $this->subscriberRepository = $this->createMock(SubscriberRepository::class); - $entityManager = $this->createMock(EntityManagerInterface::class); + $this->entityManager = $this->createMock(EntityManagerInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); + $this->subscriberDeletionService = $this->createMock(SubscriberDeletionService::class); $this->subscriberManager = new SubscriberManager( $this->subscriberRepository, - $entityManager, - $this->messageBus + $this->entityManager, + $this->messageBus, + $this->subscriberDeletionService ); } public function testCreateSubscriberPersistsAndReturnsProperlyInitializedEntity(): void + { + $this->subscriberRepository + ->expects($this->once()) + ->method('save') + ->with($this->callback(function (Subscriber $sub): bool { + return $sub->getEmail() === 'foo@bar.com' + && $sub->isConfirmed() === true + && $sub->isBlacklisted() === false + && $sub->hasHtmlEmail() === true + && $sub->isDisabled() === false; + })); + + $dto = new CreateSubscriberDto(email: 'foo@bar.com', requestConfirmation: false, htmlEmail: true); + + $result = $this->subscriberManager->createSubscriber($dto); + + $this->assertInstanceOf(Subscriber::class, $result); + $this->assertSame('foo@bar.com', $result->getEmail()); + $this->assertTrue($result->isConfirmed()); + $this->assertFalse($result->isBlacklisted()); + $this->assertTrue($result->hasHtmlEmail()); + $this->assertFalse($result->isDisabled()); + } + + public function testCreateSubscriberPersistsAndSendsEmail(): void { $this->subscriberRepository ->expects($this->once()) @@ -51,7 +81,9 @@ public function testCreateSubscriberPersistsAndReturnsProperlyInitializedEntity( $this->messageBus ->expects($this->once()) ->method('dispatch') - ->willReturn(new Envelope(new SubscriberConfirmationMessage('foo@bar.com', 'test-unique-id-456'))); + ->willReturnCallback(function ($message) { + return new Envelope($message); + }); $dto = new CreateSubscriberDto(email: 'foo@bar.com', requestConfirmation: true, htmlEmail: true); @@ -85,7 +117,9 @@ public function testCreateSubscriberWithConfirmationSendsConfirmationEmail(): vo $this->assertTrue($message->hasHtmlEmail()); return true; })) - ->willReturn(new Envelope(new SubscriberConfirmationMessage('foo@bar.com', 'test-unique-id-456'))); + ->willReturnCallback(function ($message) { + return new Envelope($message); + }); $dto = new CreateSubscriberDto(email: 'test@example.com', requestConfirmation: true, htmlEmail: true); $this->subscriberManager->createSubscriber($dto);