From 97ea1ab33a3a2505350defb9aba86f1b2f845194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gangloff?= Date: Tue, 14 Oct 2025 17:40:48 +0200 Subject: [PATCH] test: add some KernelTestCase --- src/Controller/RegistrationController.php | 10 ++- src/Entity/Domain.php | 6 ++ src/Entity/Entity.php | 9 +- src/Entity/User.php | 9 +- src/Factory/UserFactory.php | 2 +- tests/Controller/ConnectorControllerTest.php | 27 ++++++ .../DomainRefreshControllerTest.php | 26 ++++++ .../MeControllerTest.php} | 8 +- .../Controller/RegistrationControllerTest.php | 78 ++++++++++++++++ tests/Controller/WatchlistControllerTest.php | 90 +++++++++++++++++++ 10 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 tests/Controller/ConnectorControllerTest.php create mode 100644 tests/Controller/DomainRefreshControllerTest.php rename tests/{UserTest.php => Controller/MeControllerTest.php} (72%) create mode 100644 tests/Controller/RegistrationControllerTest.php create mode 100644 tests/Controller/WatchlistControllerTest.php diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 399bd80..790cede 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -21,6 +21,7 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; class RegistrationController extends AbstractController { @@ -33,6 +34,7 @@ class RegistrationController extends AbstractController private readonly SerializerInterface $serializer, private readonly LoggerInterface $logger, private readonly KernelInterface $kernel, + private readonly ValidatorInterface $validator, ) { } @@ -64,14 +66,16 @@ class RegistrationController extends AbstractController } $user = $this->serializer->deserialize($request->getContent(), User::class, 'json', ['groups' => 'user:register']); - if (null === $user->getEmail() || null === $user->getPassword()) { - throw new BadRequestHttpException('Bad request'); + $violations = $this->validator->validate($user); + + if ($violations->count() > 0) { + throw new BadRequestHttpException($violations->get(0)); } $user->setPassword( $userPasswordHasher->hashPassword( $user, - $user->getPassword() + $user->getPlainPassword() ) )->setCreatedAt(new \DateTimeImmutable()); diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index 2add1f1..35963bf 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -2,6 +2,7 @@ namespace App\Entity; +use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use App\Config\EventAction; @@ -75,6 +76,11 @@ class Domain */ #[ORM\OneToMany(targetEntity: DomainEvent::class, mappedBy: 'domain', cascade: ['persist'], orphanRemoval: true)] #[Groups(['domain:item', 'domain:list', 'watchlist:list'])] + #[ApiProperty( + openapiContext: [ + 'type' => 'array', + ] + )] private Collection $events; /** diff --git a/src/Entity/Entity.php b/src/Entity/Entity.php index 29fc3cc..847d478 100644 --- a/src/Entity/Entity.php +++ b/src/Entity/Entity.php @@ -2,6 +2,7 @@ namespace App\Entity; +use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use App\Repository\EntityRepository; use Doctrine\Common\Collections\ArrayCollection; @@ -77,7 +78,13 @@ class Entity #[Groups(['entity:item', 'domain:item'])] private Collection $events; - #[ORM\Column] + #[ORM\Column(type: 'json')] + #[ApiProperty( + openapiContext: [ + 'type' => 'array', + 'items' => ['type' => 'array'], + ] + )] #[Groups(['entity:item', 'domain:item'])] private array $jCard = []; diff --git a/src/Entity/User.php b/src/Entity/User.php index 8814da6..f709cf9 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -15,6 +15,8 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Serializer\Attribute\SerializedName; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])] @@ -46,6 +48,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(length: 180)] #[Groups(['user:list', 'user:register'])] + #[Assert\Email] + #[Assert\NotBlank] private ?string $email = null; /** @@ -59,7 +63,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface * @var string|null The hashed password */ #[ORM\Column(nullable: true)] - #[Groups(['user:register'])] private ?string $password = null; /** @@ -80,6 +83,10 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(nullable: true)] private ?\DateTimeImmutable $verifiedAt = null; + #[Assert\PasswordStrength] + #[Assert\NotBlank] + #[SerializedName('password')] + #[Groups(['user:register'])] private ?string $plainPassword = null; public function __construct() diff --git a/src/Factory/UserFactory.php b/src/Factory/UserFactory.php index 9b521a0..b2fe3fd 100644 --- a/src/Factory/UserFactory.php +++ b/src/Factory/UserFactory.php @@ -36,7 +36,7 @@ final class UserFactory extends PersistentObjectFactory protected function defaults(): array|callable { $createdAt = \DateTimeImmutable::createFromMutable(self::faker()->dateTime()); - $plainPassword = self::faker()->password(8, 20); + $plainPassword = self::faker()->password(16, 20); return [ 'createdAt' => $createdAt, diff --git a/tests/Controller/ConnectorControllerTest.php b/tests/Controller/ConnectorControllerTest.php new file mode 100644 index 0000000..ed49f0a --- /dev/null +++ b/tests/Controller/ConnectorControllerTest.php @@ -0,0 +1,27 @@ +request('GET', '/api/connectors'); + + $this->assertResponseIsSuccessful(); + $this->assertMatchesResourceCollectionJsonSchema(Connector::class); + $this->assertCount(0, $response->toArray()['hydra:member']); + } +} diff --git a/tests/Controller/DomainRefreshControllerTest.php b/tests/Controller/DomainRefreshControllerTest.php new file mode 100644 index 0000000..ca7c0d0 --- /dev/null +++ b/tests/Controller/DomainRefreshControllerTest.php @@ -0,0 +1,26 @@ +request('GET', '/api/domains/example.com'); + + $this->assertResponseIsSuccessful(); + $this->assertMatchesResourceItemJsonSchema(Domain::class); + } +} diff --git a/tests/UserTest.php b/tests/Controller/MeControllerTest.php similarity index 72% rename from tests/UserTest.php rename to tests/Controller/MeControllerTest.php index 62f6872..2084a07 100644 --- a/tests/UserTest.php +++ b/tests/Controller/MeControllerTest.php @@ -1,14 +1,15 @@ loginUser($testUser); $client->request('GET', '/api/me'); $this->assertResponseIsSuccessful(); diff --git a/tests/Controller/RegistrationControllerTest.php b/tests/Controller/RegistrationControllerTest.php new file mode 100644 index 0000000..ad297b2 --- /dev/null +++ b/tests/Controller/RegistrationControllerTest.php @@ -0,0 +1,78 @@ +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 new file mode 100644 index 0000000..e44b574 --- /dev/null +++ b/tests/Controller/WatchlistControllerTest.php @@ -0,0 +1,90 @@ +createUserAndWatchlist(); + + $response = $client->request('GET', '/api/watchlists'); + + $this->assertResponseIsSuccessful(); + $this->assertMatchesResourceCollectionJsonSchema(WatchList::class); + + $data = $response->toArray(); + $this->assertArrayHasKey('hydra:member', $data); + $this->assertCount(1, $data['hydra:member']); + } + + #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] + public function testCreateWatchlist(): void + { + $this->createUserAndWatchlist(); + $this->assertResponseIsSuccessful(); + $this->assertResponseStatusCodeSame(201); + $this->assertMatchesResourceItemJsonSchema(WatchList::class); + } + + #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] + public function testGetTrackedDomains() + { + $client = $this->createUserAndWatchlist(); + + $response = $client->request('GET', '/api/tracked'); + $this->assertResponseIsSuccessful(); + + $data = $response->toArray(); + $this->assertArrayHasKey('hydra:member', $data); + $this->assertCount(1, $data['hydra:member']); + } + + #[DependsExternal(RDAPServiceTest::class, 'testUpdateRdapServers')] + public function testGetWatchlistFeeds() + { + $client = $this->createUserAndWatchlist(); + + $response = $client->request('GET', '/api/watchlists'); + $token = $response->toArray()['hydra:member'][0]['token']; + + $client->request('GET', '/api/watchlists/'.$token.'/calendar'); + $this->assertResponseIsSuccessful(); + $this->assertResponseHeaderSame('content-type', 'text/calendar; charset=utf-8'); + + $client->request('GET', '/api/watchlists/'.$token.'/rss/events'); + $this->assertResponseIsSuccessful(); + $this->assertResponseHeaderSame('content-type', 'application/atom+xml; charset=utf-8'); + + $client->request('GET', '/api/watchlists/'.$token.'/rss/status'); + $this->assertResponseIsSuccessful(); + $this->assertResponseHeaderSame('content-type', 'application/atom+xml; charset=utf-8'); + } + + private function createUserAndWatchlist(): Client + { + $client = self::createClientWithCredentials(self::getToken(UserFactory::createOne())); + $client->request('POST', '/api/watchlists', ['json' => [ + 'domains' => ['/api/domains/example.com'], + 'name' => 'My Watchlist', + 'triggers' => [ + ['action' => 'email', 'event' => 'last changed'], + ['action' => 'email', 'event' => 'transfer'], + ['action' => 'email', 'event' => 'expiration'], + ['action' => 'email', 'event' => 'deletion'], + ], + ]]); + + return $client; + } +}