From ebdc5151eef6fe9c7943399e7ba69ee82e18b58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Tue, 14 Oct 2025 23:34:17 +0200 Subject: [PATCH] fix: use state provider --- src/Controller/DomainRefreshController.php | 91 ------------------- src/Entity/Domain.php | 3 - src/Service/RDAPService.php | 3 +- src/State/AutoRegisterDomainProvider.php | 61 ++++++++----- .../Controller/RegistrationControllerTest.php | 78 ---------------- tests/Controller/WatchlistControllerTest.php | 3 +- tests/Service/RDAPServiceTest.php | 44 ++++++++- .../AutoRegisterDomainProviderTest.php} | 6 +- 8 files changed, 89 insertions(+), 200 deletions(-) delete mode 100644 src/Controller/DomainRefreshController.php rename tests/{Controller/DomainRefreshControllerTest.php => State/AutoRegisterDomainProviderTest.php} (75%) diff --git a/src/Controller/DomainRefreshController.php b/src/Controller/DomainRefreshController.php deleted file mode 100644 index 97859b4..0000000 --- a/src/Controller/DomainRefreshController.php +++ /dev/null @@ -1,91 +0,0 @@ -getUser()->getUserIdentifier(); - - $this->logger->info('User wants to update a domain name', [ - 'username' => $userId, - 'ldhName' => $idnDomain, - ]); - - /** @var ?Domain $domain */ - $domain = $this->domainRepository->findOneBy(['ldhName' => $idnDomain]); - // If the domain name exists in the database, recently updated and not important, we return the stored Domain - if (null !== $domain - && !$domain->getDeleted() - && !$domain->isToBeUpdated(true, true) - && !$this->kernel->isDebug() - && true !== filter_var($request->get('forced', false), FILTER_VALIDATE_BOOLEAN) - ) { - $this->logger->debug('It is not necessary to update the domain name', [ - 'ldhName' => $idnDomain, - 'updatedAt' => $domain->getUpdatedAt()->format(\DateTimeInterface::ATOM), - ]); - - return $domain; - } - - if (false === $this->kernel->isDebug() && true === $this->getParameter('limited_features')) { - $limiter = $this->rdapRequestsLimiter->create($userId); - $limit = $limiter->consume(); - - if (!$limit->isAccepted()) { - throw new TooManyRequestsHttpException($limit->getRetryAfter()->getTimestamp() - time()); - } - } - - $updatedAt = null === $domain ? new \DateTimeImmutable('now') : $domain->getUpdatedAt(); - $domain = $this->RDAPService->registerDomain($idnDomain); - - $randomizer = new Randomizer(); - $watchLists = $randomizer->shuffleArray($domain->getWatchLists()->toArray()); - - /** @var WatchList $watchList */ - foreach ($watchLists as $watchList) { - $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); - } - - return $domain; - } -} diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index 35963bf..a6c5d0b 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -6,7 +6,6 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use App\Config\EventAction; -use App\Controller\DomainRefreshController; use App\Repository\DomainRepository; use App\Service\RDAPService; use App\State\AutoRegisterDomainProvider; @@ -43,7 +42,6 @@ use Symfony\Component\Serializer\Attribute\SerializedName; */ new Get( uriTemplate: '/domains/{ldhName}', // Do not delete this line, otherwise Symfony interprets the TLD of the domain name as a return type - controller: DomainRefreshController::class, normalizationContext: [ 'groups' => [ 'domain:item', @@ -55,7 +53,6 @@ use Symfony\Component\Serializer\Attribute\SerializedName; 'ds:list', ], ], - read: false ), ], provider: AutoRegisterDomainProvider::class diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index c1cbffa..2cfb068 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -62,7 +62,8 @@ class RDAPService 'Private', ]; - public function __construct(private HttpClientInterface $client, + public function __construct( + private readonly HttpClientInterface $client, private readonly EntityRepository $entityRepository, private readonly DomainRepository $domainRepository, private readonly DomainEventRepository $domainEventRepository, diff --git a/src/State/AutoRegisterDomainProvider.php b/src/State/AutoRegisterDomainProvider.php index 87f8051..07d5bcb 100644 --- a/src/State/AutoRegisterDomainProvider.php +++ b/src/State/AutoRegisterDomainProvider.php @@ -5,39 +5,62 @@ namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Entity\Domain; -use App\Exception\DomainNotFoundException; +use App\Entity\WatchList; +use App\Message\SendDomainEventNotif; +use App\Repository\DomainRepository; use App\Service\RDAPService; -use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; +use Random\Randomizer; use Symfony\Bundle\SecurityBundle\Security; -use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; readonly class AutoRegisterDomainProvider implements ProviderInterface { public function __construct( - #[Autowire(service: 'api_platform.doctrine.orm.state.item_provider')] - private ProviderInterface $itemProvider, private RDAPService $RDAPService, - private EntityManagerInterface $entityManager, private KernelInterface $kernel, private ParameterBagInterface $parameterBag, - private RateLimiterFactory $rdapRequestsLimiter, private Security $security, + private RateLimiterFactory $rdapRequestsLimiter, + private Security $security, + private LoggerInterface $logger, + private DomainRepository $domainRepository, + private MessageBusInterface $bus, ) { } public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { - $domain = $this->itemProvider->provide($operation, $uriVariables, $context); + $userId = $this->security->getUser()->getUserIdentifier(); + $idnDomain = RDAPService::convertToIdn($uriVariables['ldhName']); + + $this->logger->info('User wants to update a domain name', [ + 'username' => $userId, + 'ldhName' => $idnDomain, + ]); + + /** @var ?Domain $domain */ + $domain = $this->domainRepository->findOneBy(['ldhName' => $idnDomain]); + // If the domain name exists in the database, recently updated and not important, we return the stored Domain + if (null !== $domain + && !$domain->getDeleted() + && !$domain->isToBeUpdated(true, true) + && !$this->kernel->isDebug() + && true !== filter_var($context['request']->get('forced', false), FILTER_VALIDATE_BOOLEAN) + ) { + $this->logger->debug('It is not necessary to update the domain name', [ + 'ldhName' => $idnDomain, + 'updatedAt' => $domain->getUpdatedAt()->format(\DateTimeInterface::ATOM), + ]); - if (!is_null($domain)) { return $domain; } if (false === $this->kernel->isDebug() && true === $this->parameterBag->get('limited_features')) { - $limiter = $this->rdapRequestsLimiter->create($this->security->getUser()->getUserIdentifier()); + $limiter = $this->rdapRequestsLimiter->create($userId); $limit = $limiter->consume(); if (!$limit->isAccepted()) { @@ -45,19 +68,15 @@ readonly class AutoRegisterDomainProvider implements ProviderInterface } } - $ldhName = RDAPService::convertToIdn($uriVariables['ldhName']); + $updatedAt = null === $domain ? new \DateTimeImmutable('now') : $domain->getUpdatedAt(); + $domain = $this->RDAPService->registerDomain($idnDomain); - try { - $domain = $this->RDAPService->registerDomain($ldhName); - } catch (DomainNotFoundException) { - $domain = (new Domain()) - ->setLdhName($ldhName) - ->setTld($this->RDAPService->getTld($ldhName)) - ->setDelegationSigned(false) - ->setDeleted(true); + $randomizer = new Randomizer(); + $watchLists = $randomizer->shuffleArray($domain->getWatchLists()->toArray()); - $this->entityManager->persist($domain); - $this->entityManager->flush(); + /** @var WatchList $watchList */ + foreach ($watchLists as $watchList) { + $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); } return $domain; diff --git a/tests/Controller/RegistrationControllerTest.php b/tests/Controller/RegistrationControllerTest.php index ad297b2..e69de29 100644 --- a/tests/Controller/RegistrationControllerTest.php +++ b/tests/Controller/RegistrationControllerTest.php @@ -1,78 +0,0 @@ -get(EntityManagerInterface::class); - } - - public function testRegister(): void - { - $testUser = UserFactory::createOne(); - RegistrationControllerTest::$entityManager->remove($testUser); - RegistrationControllerTest::$entityManager->flush(); - - $client = $this->createClient(); - $client->request('POST', '/api/register', [ - 'json' => [ - 'email' => $testUser->getEmail(), - 'password' => $testUser->getPlainPassword(), - ], - ]); - $this->assertResponseIsSuccessful(); - $this->assertResponseStatusCodeSame(201); - } - - public function testRegisterEmptyEmail(): void - { - $client = $this->createClient(); - $client->request('POST', '/api/register', [ - 'json' => [ - 'email' => '', - 'password' => 'MySuperPassword123', - ], - ]); - $this->assertResponseStatusCodeSame(400); - } - - public function testRegisterEmptyPassword(): void - { - $client = $this->createClient(); - $client->request('POST', '/api/register', [ - 'json' => [ - 'email' => 'test@domainwatchdog.eu', - 'password' => '', - ], - ]); - $this->assertResponseStatusCodeSame(400); - } - - public function testRegisterWeakPassword(): void - { - $client = $this->createClient(); - $client->request('POST', '/api/register', [ - 'json' => [ - 'email' => 'test@domainwatchdog.eu', - 'password' => '123', - ], - ]); - $this->assertResponseStatusCodeSame(400); - } -} diff --git a/tests/Controller/WatchlistControllerTest.php b/tests/Controller/WatchlistControllerTest.php index f2d6603..5a81435 100644 --- a/tests/Controller/WatchlistControllerTest.php +++ b/tests/Controller/WatchlistControllerTest.php @@ -16,6 +16,7 @@ final class WatchlistControllerTest extends ApiTestCase use Factories; use AuthenticatedUserTrait; + #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] public function testGetWatchlistCollection(): void { $client = $this->createUserAndWatchlist(); @@ -77,7 +78,7 @@ final class WatchlistControllerTest extends ApiTestCase { $client = self::createClientWithCredentials(self::getToken(UserFactory::createOne())); $client->request('POST', '/api/watchlists', ['json' => [ - 'domains' => ['/api/domains/example.com'], + 'domains' => ['/api/domains/iana.org'], 'name' => 'My Watchlist', 'triggers' => [ ['action' => 'email', 'event' => 'last changed'], diff --git a/tests/Service/RDAPServiceTest.php b/tests/Service/RDAPServiceTest.php index 3b29a32..a30adb3 100644 --- a/tests/Service/RDAPServiceTest.php +++ b/tests/Service/RDAPServiceTest.php @@ -16,6 +16,9 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\HttpClientInterface; class RDAPServiceTest extends KernelTestCase { @@ -76,9 +79,46 @@ class RDAPServiceTest extends KernelTestCase } #[Depends('testUpdateRdapServers')] - public function testDomainNotFound() + public function testDomainDeleted() { + self::$RDAPService->registerDomain('example.com'); + + self::ensureKernelShutdown(); + self::bootKernel(); + + static::getContainer()->set(HttpClientInterface::class, new MockHttpClient( + new MockResponse('', ['http_code' => 404]) + )); + + $rdapService = static::getContainer()->get(RDAPService::class); + $this->expectException(DomainNotFoundException::class); - self::$RDAPService->registerDomain('dd5c3e77-c824-4792-95fc-ddde03a08881.com'); + $rdapService->registerDomain('example.com'); + } + + #[Depends('testDomainDeleted')] + public function testDomainUpdateStatus(): void + { + $domain = self::$RDAPService->registerDomain('example.com'); + $domain->setStatus(['pending delete']); + self::$entityManager->flush(); + self::$RDAPService->registerDomain('example.com'); + + $this->expectNotToPerformAssertions(); + } + + #[Depends('testUpdateRdapServers')] + public function testHttpClientException() + { + self::ensureKernelShutdown(); + self::bootKernel(); + static::getContainer()->set(HttpClientInterface::class, new MockHttpClient( + fn () => throw new TransportException() + )); + + $rdapService = static::getContainer()->get(RDAPService::class); + + $this->expectException(TransportException::class); + $rdapService->registerDomain('example.com'); } } diff --git a/tests/Controller/DomainRefreshControllerTest.php b/tests/State/AutoRegisterDomainProviderTest.php similarity index 75% rename from tests/Controller/DomainRefreshControllerTest.php rename to tests/State/AutoRegisterDomainProviderTest.php index 5dc4fec..b230b5e 100644 --- a/tests/Controller/DomainRefreshControllerTest.php +++ b/tests/State/AutoRegisterDomainProviderTest.php @@ -1,6 +1,6 @@ request('GET', '/api/domains/example.com'); $this->assertResponseIsSuccessful();