From 1e81dcfe9654d7a972e81e3c5d1c4605a09a58b3 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Fri, 27 Dec 2024 16:17:58 +0100 Subject: [PATCH 1/3] [ECP-9177] Implement webhook clean-up cronjob --- .../AdyenNotificationRepositoryInterface.php | 39 +++++++++++ Cron/CleanupNotifications.php | 66 +++++++++++++++++++ .../NotificationsProviderInterface.php | 25 +++++++ .../ProcessedOldNotificationsProvider.php | 63 ++++++++++++++++++ Helper/Config.php | 21 ++++++ Model/AdyenNotificationRepository.php | 62 +++++++++++++++++ .../system/adyen_testing_performance.xml | 9 +++ etc/config.xml | 2 + etc/crontab.xml | 3 + etc/di.xml | 16 +++++ 10 files changed, 306 insertions(+) create mode 100644 Api/Repository/AdyenNotificationRepositoryInterface.php create mode 100644 Cron/CleanupNotifications.php create mode 100644 Cron/Providers/NotificationsProviderInterface.php create mode 100644 Cron/Providers/ProcessedOldNotificationsProvider.php create mode 100644 Model/AdyenNotificationRepository.php diff --git a/Api/Repository/AdyenNotificationRepositoryInterface.php b/Api/Repository/AdyenNotificationRepositoryInterface.php new file mode 100644 index 0000000000..67453ec80b --- /dev/null +++ b/Api/Repository/AdyenNotificationRepositoryInterface.php @@ -0,0 +1,39 @@ + + */ + +namespace Adyen\Payment\Api\Repository; + +use Adyen\Payment\Api\Data\NotificationInterface; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\Api\SearchResultsInterface; +use Magento\Framework\Exception\LocalizedException; + +interface AdyenNotificationRepositoryInterface +{ + /** + * Retrieve Adyen Notification entities which match a specified criteria. + * + * @param SearchCriteriaInterface $searchCriteria + * @return SearchResultsInterface + * + * @throws LocalizedException + */ + public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface; + + /** + * Deletes a specified Adyen notification. + * + * @param NotificationInterface $entity The notification ID. + * @return bool + */ + public function delete(NotificationInterface $entity): bool; +} diff --git a/Cron/CleanupNotifications.php b/Cron/CleanupNotifications.php new file mode 100644 index 0000000000..fc3bf26dfc --- /dev/null +++ b/Cron/CleanupNotifications.php @@ -0,0 +1,66 @@ + + */ + +namespace Adyen\Payment\Cron; + +use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; +use Adyen\Payment\Cron\Providers\NotificationsProviderInterface; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; + +class CleanupNotifications +{ + /** + * @param NotificationsProviderInterface[] $providers + */ + public function __construct( + private readonly array $providers, + private readonly AdyenLogger $adyenLogger, + private readonly Config $configHelper, + private readonly StoreManagerInterface $storeManager, + private readonly AdyenNotificationRepositoryInterface $adyenNotificationRepository + ) { } + + /** + * @return void + * @throws NoSuchEntityException + */ + public function execute(): void + { + $storeId = $this->storeManager->getStore()->getId(); + $isWebhookCleanupEnabled = $this->configHelper->getIsWebhookCleanupEnabled($storeId); + + if ($isWebhookCleanupEnabled) { + $numberOfItemsRemoved = 0; + + foreach ($this->providers as $provider) { + foreach ($provider->provide() as $notificationToCleanup) { + $isSuccessfullyDeleted = $this->adyenNotificationRepository->delete($notificationToCleanup); + + if ($isSuccessfullyDeleted) { + $numberOfItemsRemoved++; + } + } + } + + $successMessage = sprintf( + __('%s webhook notifications have been cleaned-up by the CleanupNotifications job.'), + $numberOfItemsRemoved + ); + $this->adyenLogger->addAdyenDebug($successMessage); + } else { + $message = __('Webhook notification clean-up feature is disabled. The job has been skipped!'); + $this->adyenLogger->addAdyenDebug($message); + } + } +} diff --git a/Cron/Providers/NotificationsProviderInterface.php b/Cron/Providers/NotificationsProviderInterface.php new file mode 100644 index 0000000000..34f9b06256 --- /dev/null +++ b/Cron/Providers/NotificationsProviderInterface.php @@ -0,0 +1,25 @@ + + */ + +namespace Adyen\Payment\Cron\Providers; + +interface NotificationsProviderInterface +{ + /** + * @return array + */ + public function provide(): array; + + /** + * @return string + */ + public function getProviderName(): string; +} diff --git a/Cron/Providers/ProcessedOldNotificationsProvider.php b/Cron/Providers/ProcessedOldNotificationsProvider.php new file mode 100644 index 0000000000..60196b7c12 --- /dev/null +++ b/Cron/Providers/ProcessedOldNotificationsProvider.php @@ -0,0 +1,63 @@ + + */ + +namespace Adyen\Payment\Cron\Providers; + +use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Model\StoreManagerInterface; + +class ProcessedOldNotificationsProvider implements NotificationsProviderInterface +{ + public function __construct( + private readonly AdyenNotificationRepositoryInterface $adyenNotificationRepository, + private readonly SearchCriteriaBuilder $searchCriteriaBuilder, + private readonly Config $configHelper, + private readonly StoreManagerInterface $storeManager, + private readonly AdyenLogger $adyenLogger + ) { } + + public function provide(): array + { + $storeId = $this->storeManager->getStore()->getId(); + $numberOfDays = $this->configHelper->getRequiredDaysForOldWebhooks($storeId); + + $dateFrom = date('Y-m-d H:i:s', time() - $numberOfDays * 24 * 60 * 60); + + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter('done', 1) + ->addFilter('processing', 0) + ->addFilter('created_at', $dateFrom, 'lteq') + ->create(); + + try { + $items = $this->adyenNotificationRepository->getList($searchCriteria); + return $items->getItems(); + } catch (LocalizedException $e) { + $errorMessage = sprintf( + __('An error occurred while providing notifications older than %s days!'), + $numberOfDays + ); + + $this->adyenLogger->error($errorMessage); + + return []; + } + } + + public function getProviderName(): string + { + return "Adyen processed old webhook notifications"; + } +} diff --git a/Helper/Config.php b/Helper/Config.php index 8657bfe26a..483960e026 100644 --- a/Helper/Config.php +++ b/Helper/Config.php @@ -57,6 +57,8 @@ class Config const XML_RECURRING_CONFIGURATION = 'recurring_configuration'; const XML_ALLOW_MULTISTORE_TOKENS = 'allow_multistore_tokens'; const XML_THREEDS_FLOW = 'threeds_flow'; + const XML_CLEANUP_OLD_WEBHOOKS = 'cleanup_old_webhooks'; + const XML_REQUIRED_DAYS_OLD_WEBHOOKS = 'required_days_old_webhooks'; protected ScopeConfigInterface $scopeConfig; private EncryptorInterface $encryptor; @@ -592,6 +594,25 @@ public function getThreeDSFlow(int $storeId = null): string ); } + public function getIsWebhookCleanupEnabled(int $storeId = null): bool + { + return $this->getConfigData( + self::XML_CLEANUP_OLD_WEBHOOKS, + self::XML_ADYEN_ABSTRACT_PREFIX, + $storeId, + true + ); + } + + public function getRequiredDaysForOldWebhooks(int $storeId = null): int + { + return (int) $this->getConfigData( + self::XML_REQUIRED_DAYS_OLD_WEBHOOKS, + self::XML_ADYEN_ABSTRACT_PREFIX, + $storeId + ); + } + public function getConfigData(string $field, string $xmlPrefix, ?int $storeId, bool $flag = false): mixed { $path = implode("/", [self::XML_PAYMENT_PREFIX, $xmlPrefix, $field]); diff --git a/Model/AdyenNotificationRepository.php b/Model/AdyenNotificationRepository.php new file mode 100644 index 0000000000..277d12be60 --- /dev/null +++ b/Model/AdyenNotificationRepository.php @@ -0,0 +1,62 @@ + + */ + +namespace Adyen\Payment\Model; + +use Adyen\Payment\Api\Data\NotificationInterface; +use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; +use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory; +use Magento\Framework\Api\Search\SearchResultFactory; +use Magento\Framework\Api\SearchCriteria\CollectionProcessor; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\Api\SearchResultsInterface; +use Magento\Framework\ObjectManagerInterface; + +class AdyenNotificationRepository implements AdyenNotificationRepositoryInterface +{ + /** + * @param SearchResultFactory $searchResultsFactory + * @param CollectionFactory $collectionFactory + * @param CollectionProcessor $collectionProcessor + * @param ObjectManagerInterface $objectManager + * @param string $resourceModel + */ + public function __construct( + private readonly SearchResultFactory $searchResultsFactory, + private readonly CollectionFactory $collectionFactory, + private readonly CollectionProcessor $collectionProcessor, + private readonly ObjectManagerInterface $objectManager, + private readonly string $resourceModel + ) { } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return SearchResultsInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface + { + $searchResult = $this->searchResultsFactory->create(); + $collection = $this->collectionFactory->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + $searchResult->setItems($collection->getItems()); + $searchResult->setTotalCount($collection->getSize()); + + return $searchResult; + } + + public function delete(NotificationInterface $entity): bool + { + $resource = $this->objectManager->get($this->resourceModel); + $resource->delete($entity); + + return true; + } +} diff --git a/etc/adminhtml/system/adyen_testing_performance.xml b/etc/adminhtml/system/adyen_testing_performance.xml index c384428841..358be8ffcf 100644 --- a/etc/adminhtml/system/adyen_testing_performance.xml +++ b/etc/adminhtml/system/adyen_testing_performance.xml @@ -55,5 +55,14 @@ ]]> + + + Magento\Config\Model\Config\Source\Yesno + payment/adyen_abstract/cleanup_old_webhooks + + Webhooks older than certain days will be removed from the database by a cronjob if this feature is enabled. + The default value is 90 days and this can be configured by overriding `payment/adyen_abstract/required_days_old_webhooks` configuration path. + + diff --git a/etc/config.xml b/etc/config.xml index b432bd83ff..76e66ba2b4 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -34,6 +34,8 @@ canceled manual 1 + 0 + 90 0 diff --git a/etc/crontab.xml b/etc/crontab.xml index d638f45ad1..cf093da854 100755 --- a/etc/crontab.xml +++ b/etc/crontab.xml @@ -24,5 +24,8 @@ 0 0 * * * + + 0 0 * * * + diff --git a/etc/di.xml b/etc/di.xml index aefb9b552a..7a3438e34c 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -1693,6 +1693,8 @@ + @@ -1717,6 +1719,20 @@ + + + Adyen\Payment\Model\ResourceModel\Notification + + + + + + + Adyen\Payment\Cron\Providers\ProcessedOldNotificationsProvider + + + + From b62a1e8c0c8d2ab9dc8a8fd682f74a88fdfb155d Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 7 Jan 2025 14:17:10 +0100 Subject: [PATCH 2/3] [ECP-9177] Write unit tests --- Test/Unit/Cron/CleanupNotificationsTest.php | 101 ++++++++++++ .../ProcessedOldNotificationsProviderTest.php | 147 ++++++++++++++++++ Test/Unit/Helper/ConfigTest.php | 41 +++++ .../Model/AdyenNotificationRepositoryTest.php | 102 ++++++++++++ 4 files changed, 391 insertions(+) create mode 100644 Test/Unit/Cron/CleanupNotificationsTest.php create mode 100644 Test/Unit/Cron/Providers/ProcessedOldNotificationsProviderTest.php create mode 100644 Test/Unit/Model/AdyenNotificationRepositoryTest.php diff --git a/Test/Unit/Cron/CleanupNotificationsTest.php b/Test/Unit/Cron/CleanupNotificationsTest.php new file mode 100644 index 0000000000..cc2b044205 --- /dev/null +++ b/Test/Unit/Cron/CleanupNotificationsTest.php @@ -0,0 +1,101 @@ + + */ + +namespace Adyen\Payment\Test\Cron; + +use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; +use Adyen\Payment\Cron\CleanupNotifications; +use Adyen\Payment\Cron\Providers\NotificationsProviderInterface; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\Notification; +use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; + +class CleanupNotificationsTest extends AbstractAdyenTestCase +{ + protected ?CleanupNotifications $cleanupNotifications; + protected AdyenLogger|MockObject $adyenLoggerMock; + protected Config|MockObject $configHelperMock; + protected StoreManagerInterface|MockObject $storeManagerMock; + protected AdyenNotificationRepositoryInterface|MockObject $adyenNotificationRepositoryMock; + protected NotificationsProviderInterface|MockObject $notificationsProvider; + protected array $providers; + + const STORE_ID = PHP_INT_MAX; + + protected function setUp(): void + { + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); + $this->configHelperMock = $this->createMock(Config::class); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->adyenNotificationRepositoryMock = + $this->createMock(AdyenNotificationRepositoryInterface::class); + $this->notificationsProvider = $this->createMock(NotificationsProviderInterface::class); + + $this->providers[] = $this->notificationsProvider; + + $storeMock = $this->createMock(StoreInterface::class); + $storeMock->method('getId')->willReturn(self::STORE_ID); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); + + $this->cleanupNotifications = new CleanupNotifications( + $this->providers, + $this->adyenLoggerMock, + $this->configHelperMock, + $this->storeManagerMock, + $this->adyenNotificationRepositoryMock + ); + } + + protected function tearDown(): void + { + $this->cleanupNotifications = null; + } + + public function testExecuteConfigEnabled() + { + $this->configHelperMock->expects($this->once()) + ->method('getIsWebhookCleanupEnabled') + ->with(self::STORE_ID) + ->willReturn(true); + + $notificationMock = $this->createMock(Notification::class); + $providerResult[] = $notificationMock; + + $this->notificationsProvider->method('provide')->willReturn($providerResult); + + $this->adyenNotificationRepositoryMock->expects($this->once()) + ->method('delete') + ->with($notificationMock) + ->willReturn(true); + + $this->adyenLoggerMock->expects($this->once())->method('addAdyenDebug'); + + $this->cleanupNotifications->execute(); + } + + public function testExecuteConfigDisabled() + { + $this->configHelperMock->expects($this->once()) + ->method('getIsWebhookCleanupEnabled') + ->with(self::STORE_ID) + ->willReturn(false); + + $this->notificationsProvider->expects($this->never())->method('provide'); + $this->adyenNotificationRepositoryMock->expects($this->never())->method('delete'); + $this->adyenLoggerMock->expects($this->once())->method('addAdyenDebug'); + + $this->cleanupNotifications->execute(); + } +} diff --git a/Test/Unit/Cron/Providers/ProcessedOldNotificationsProviderTest.php b/Test/Unit/Cron/Providers/ProcessedOldNotificationsProviderTest.php new file mode 100644 index 0000000000..dd77a1af14 --- /dev/null +++ b/Test/Unit/Cron/Providers/ProcessedOldNotificationsProviderTest.php @@ -0,0 +1,147 @@ + + */ + +namespace Adyen\Payment\Test\Cron\Providers; + +use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; +use Adyen\Payment\Cron\Providers\ProcessedOldNotificationsProvider; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchResultsInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; + +class ProcessedOldNotificationsProviderTest extends AbstractAdyenTestCase +{ + protected ?ProcessedOldNotificationsProvider $notificationsProvider; + protected AdyenNotificationRepositoryInterface|MockObject $adyenNotificationRepositoryMock; + protected SearchCriteriaBuilder|MockObject $searchCriteriaBuilderMock; + protected Config|MockObject $configHelperMock; + protected StoreManagerInterface|MockObject $storeManagerMock; + protected AdyenLogger|MockObject $adyenLoggerMock; + + const STORE_ID = PHP_INT_MAX; + + protected function setUp(): void + { + $this->adyenNotificationRepositoryMock = + $this->createMock(AdyenNotificationRepositoryInterface::class); + $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); + $this->configHelperMock = $this->createMock(Config::class); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); + + $storeMock = $this->createMock(StoreInterface::class); + $storeMock->method('getId')->willReturn(self::STORE_ID); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); + + $this->notificationsProvider = new ProcessedOldNotificationsProvider( + $this->adyenNotificationRepositoryMock, + $this->searchCriteriaBuilderMock, + $this->configHelperMock, + $this->storeManagerMock, + $this->adyenLoggerMock + ); + } + + protected function tearDown(): void + { + $this->notificationsProvider = null; + } + + public function testProvideSuccess() + { + $expiryDays = 90; + + $this->configHelperMock->expects($this->once()) + ->method('getRequiredDaysForOldWebhooks') + ->with(self::STORE_ID) + ->willReturn($expiryDays); + + $dateMock = date('Y-m-d H:i:s', time() - $expiryDays * 24 * 60 * 60); + + $this->searchCriteriaBuilderMock->expects($this->exactly(3)) + ->method('addFilter') + ->withConsecutive( + ['done', 1, 'eq'], + ['processing', 0, 'eq'], + ['created_at', $dateMock, 'lteq'] + ) + ->willReturnSelf(); + + $searchCriteriaMock = $this->createMock(SearchCriteria::class); + + $this->searchCriteriaBuilderMock->expects($this->once()) + ->method('create') + ->willReturn($searchCriteriaMock); + + $searchResultsMock = $this->createMock(SearchResultsInterface::class); + $searchResultsMock->expects($this->once()) + ->method('getItems') + ->willReturn([]); + + $this->adyenNotificationRepositoryMock->expects($this->once()) + ->method('getList') + ->with($searchCriteriaMock) + ->willReturn($searchResultsMock); + + $this->notificationsProvider->provide(); + } + + public function testProvideFailure() + { + $expiryDays = 90; + + $this->configHelperMock->expects($this->once()) + ->method('getRequiredDaysForOldWebhooks') + ->with(self::STORE_ID) + ->willReturn($expiryDays); + + $dateMock = date('Y-m-d H:i:s', time() - $expiryDays * 24 * 60 * 60); + + $this->searchCriteriaBuilderMock->expects($this->exactly(3)) + ->method('addFilter') + ->withConsecutive( + ['done', 1, 'eq'], + ['processing', 0, 'eq'], + ['created_at', $dateMock, 'lteq'] + ) + ->willReturnSelf(); + + $searchCriteriaMock = $this->createMock(SearchCriteria::class); + + $this->searchCriteriaBuilderMock->expects($this->once()) + ->method('create') + ->willReturn($searchCriteriaMock); + + $this->adyenNotificationRepositoryMock->expects($this->once()) + ->method('getList') + ->willThrowException(new LocalizedException(__('mock error message'))); + + $this->adyenLoggerMock->expects($this->once())->method('error'); + + $result = $this->notificationsProvider->provide(); + $this->assertEmpty($result); + } + + public function testGetProviderName() + { + $this->assertEquals( + 'Adyen processed old webhook notifications', + $this->notificationsProvider->getProviderName() + ); + } +} diff --git a/Test/Unit/Helper/ConfigTest.php b/Test/Unit/Helper/ConfigTest.php index c67a06cbce..abbbdb7806 100644 --- a/Test/Unit/Helper/ConfigTest.php +++ b/Test/Unit/Helper/ConfigTest.php @@ -169,4 +169,45 @@ public function testGetIsCvcRequiredForRecurringCardPayments() $result = $this->configHelper->getIsCvcRequiredForRecurringCardPayments($storeId); $this->assertEquals($expectedResult, $result); } + + public function testGetIsWebhookCleanupEnabled() + { + $storeId = PHP_INT_MAX; + + $path = sprintf( + "%s/%s/%s", + Config::XML_PAYMENT_PREFIX, + Config::XML_ADYEN_ABSTRACT_PREFIX, + Config::XML_CLEANUP_OLD_WEBHOOKS + ); + + $this->scopeConfigMock->expects($this->once()) + ->method('isSetFlag') + ->with($this->equalTo($path), $this->equalTo(ScopeInterface::SCOPE_STORE), $this->equalTo($storeId)) + ->willReturn(true); + + $result = $this->configHelper->getIsWebhookCleanupEnabled($storeId); + $this->assertTrue($result); + } + + public function testGetRequiredDaysForOldWebhooks() + { + $storeId = PHP_INT_MAX; + + $path = sprintf( + "%s/%s/%s", + Config::XML_PAYMENT_PREFIX, + Config::XML_ADYEN_ABSTRACT_PREFIX, + Config::XML_REQUIRED_DAYS_OLD_WEBHOOKS + ); + + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with($this->equalTo($path), $this->equalTo(ScopeInterface::SCOPE_STORE), $this->equalTo($storeId)) + ->willReturn("90"); + + $result = $this->configHelper->getRequiredDaysForOldWebhooks($storeId); + $this->assertIsInt($result); + $this->assertEquals(90, $result); + } } diff --git a/Test/Unit/Model/AdyenNotificationRepositoryTest.php b/Test/Unit/Model/AdyenNotificationRepositoryTest.php new file mode 100644 index 0000000000..82dba02a40 --- /dev/null +++ b/Test/Unit/Model/AdyenNotificationRepositoryTest.php @@ -0,0 +1,102 @@ + + */ + +namespace Adyen\Payment\Test\Helper\Unit\Model; + +use Adyen\Payment\Model\AdyenNotificationRepository; +use Adyen\Payment\Model\Notification as NotificationEntity; +use Adyen\Payment\Model\ResourceModel\Notification; +use Adyen\Payment\Model\ResourceModel\Notification\Collection; +use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory; +use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; +use Magento\Framework\Api\Search\SearchResultFactory; +use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\Api\SearchCriteria\CollectionProcessor; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\Api\SearchResultsInterface; +use Magento\Framework\ObjectManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; + +class AdyenNotificationRepositoryTest extends AbstractAdyenTestCase +{ + protected ?AdyenNotificationRepository $adyenNotificationRepository; + protected SearchResultFactory|MockObject $searchResultFactoryMock; + protected CollectionFactory|MockObject $collectionFactoryMock; + protected CollectionProcessor|MockObject $collectionProcessorMock; + protected ObjectManagerInterface|MockObject $objectManagerMock; + + const RESOURCE_MODEL = 'Adyen\Payment\Model\ResourceModel\Notification'; + + protected function setUp(): void + { + $this->searchResultFactoryMock = $this->createMock(SearchResultFactory::class); + $this->collectionFactoryMock = $this->createGeneratedMock( + CollectionFactory::class, + ['create'] + ); + $this->collectionProcessorMock = $this->createMock(CollectionProcessor::class); + $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class); + + $this->adyenNotificationRepository = new AdyenNotificationRepository( + $this->searchResultFactoryMock, + $this->collectionFactoryMock, + $this->collectionProcessorMock, + $this->objectManagerMock, + self::RESOURCE_MODEL + ); + } + + protected function tearDown(): void + { + $this->adyenNotificationRepository = null; + } + + public function testGetList() + { + $searchResult = $this->createMock(SearchResultInterface::class); + $searchResult->expects($this->once())->method('setItems'); + $searchResult->expects($this->once())->method('setTotalCount'); + + $this->searchResultFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($searchResult); + + $collection = $this->createMock(Collection::class); + $this->collectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($collection); + + $searchCriteria = $this->createMock(SearchCriteriaInterface::class); + $this->collectionProcessorMock->expects($this->once()) + ->method('process') + ->with($searchCriteria, $collection); + + + $result = $this->adyenNotificationRepository->getList($searchCriteria); + $this->assertInstanceOf(SearchResultsInterface::class, $result); + } + + public function testDelete() + { + $entityMock = $this->createMock(NotificationEntity::class); + + $resourceModelMock = $this->createMock(Notification::class); + $resourceModelMock->expects($this->once())->method('delete')->with($entityMock); + + $this->objectManagerMock->expects($this->once()) + ->method('get') + ->willReturn($resourceModelMock); + + $result = $this->adyenNotificationRepository->delete($entityMock); + + $this->assertTrue($result); + } +} From eff01fdb0467556274fa9ec143860cc6dfa47a74 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Thu, 9 Jan 2025 13:07:18 +0100 Subject: [PATCH 3/3] [ECP-9177] Log message with ID after removing the entity --- Cron/CleanupNotifications.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cron/CleanupNotifications.php b/Cron/CleanupNotifications.php index fc3bf26dfc..909f876365 100644 --- a/Cron/CleanupNotifications.php +++ b/Cron/CleanupNotifications.php @@ -15,6 +15,7 @@ use Adyen\Payment\Cron\Providers\NotificationsProviderInterface; use Adyen\Payment\Helper\Config; use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\Notification; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; @@ -44,10 +45,15 @@ public function execute(): void $numberOfItemsRemoved = 0; foreach ($this->providers as $provider) { + /** @var Notification $notificationToCleanup */ foreach ($provider->provide() as $notificationToCleanup) { $isSuccessfullyDeleted = $this->adyenNotificationRepository->delete($notificationToCleanup); if ($isSuccessfullyDeleted) { + $message = __('%1: Notification with entityId %2 has been deleted.', + $provider->getProviderName(), $notificationToCleanup->getEntityId()); + $this->adyenLogger->addAdyenNotification($message); + $numberOfItemsRemoved++; } }