diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 91513e8..69f7dd8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -42,12 +42,6 @@ parameters: count: 1 path: src/bundle/Controller/UserSettingsController.php - - - message: '#^Method Ibexa\\Contracts\\User\\Invitation\\Persistence\\Gateway\:\:getInvitationByEmail\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: src/contracts/Invitation/Persistence/Gateway.php - - message: '#^Method Ibexa\\Contracts\\User\\Notification\\UserPasswordReset\:\:asEmailMessage\(\) never returns null so it can be removed from the return type\.$#' identifier: return.unusedType diff --git a/src/contracts/Invitation/Persistence/Gateway.php b/src/contracts/Invitation/Persistence/Gateway.php index c60d3ee..e9eb2dd 100644 --- a/src/contracts/Invitation/Persistence/Gateway.php +++ b/src/contracts/Invitation/Persistence/Gateway.php @@ -39,13 +39,16 @@ public function addInvitation( ): array; /** - * @return array + * @phpstan-return TInvitationData */ public function getInvitation(string $hash): array; public function invitationExistsForEmail(string $email): bool; - public function getInvitationByEmail(string $email); + /** + * @phpstan-return TInvitationData + */ + public function getInvitationByEmail(string $email): array; /** * @phpstan-return TInvitationData[] diff --git a/src/lib/Invitation/Persistence/DoctrineGateway.php b/src/lib/Invitation/Persistence/DoctrineGateway.php index cbf76cb..6331e3b 100644 --- a/src/lib/Invitation/Persistence/DoctrineGateway.php +++ b/src/lib/Invitation/Persistence/DoctrineGateway.php @@ -14,6 +14,7 @@ use Ibexa\Contracts\User\Invitation\Persistence\Gateway; use Ibexa\Contracts\User\Invitation\Persistence\InvitationUpdateStruct; use Ibexa\Contracts\User\Invitation\Query\InvitationFilter; +use Ibexa\Core\Base\Exceptions\NotFoundException; /** * @internal @@ -32,6 +33,9 @@ public function __construct( ) { } + /** + * @phpstan-return TInvitationData + */ public function addInvitation( string $email, string $siteAccessName, @@ -74,6 +78,9 @@ public function addInvitation( return $this->getInvitationByEmail($email); } + /** + * @phpstan-return TInvitationData + */ public function getInvitation( string $hash ): array { @@ -86,9 +93,14 @@ public function getInvitation( ); $statement = $query->executeQuery(); + $result = $statement->fetchAssociative(); - /** @var array */ - return $statement->fetchAssociative(); + if ($result === false) { + throw new NotFoundException('invitation', $hash); + } + + /** @phpstan-var TInvitationData */ + return $result; } public function invitationExistsForEmail( @@ -124,9 +136,14 @@ public function getInvitationByEmail(string $email): array ); $statement = $query->executeQuery(); + $result = $statement->fetchAssociative(); + + if ($result === false) { + throw new NotFoundException('invitation', $email); + } /** @phpstan-var TInvitationData */ - return $statement->fetchAssociative(); + return $result; } private function getSelectQuery(): QueryBuilder diff --git a/src/lib/Invitation/Persistence/Handler.php b/src/lib/Invitation/Persistence/Handler.php index f2c4981..876ffb9 100644 --- a/src/lib/Invitation/Persistence/Handler.php +++ b/src/lib/Invitation/Persistence/Handler.php @@ -14,7 +14,6 @@ use Ibexa\Contracts\User\Invitation\Persistence\InvitationUpdateStruct; use Ibexa\Contracts\User\Invitation\Persistence\Mapper; use Ibexa\Contracts\User\Invitation\Query\InvitationFilter; -use Ibexa\Core\Base\Exceptions\NotFoundException; class Handler implements HandlerInterface { @@ -51,10 +50,6 @@ public function getInvitation( ): Invitation { $invitationRow = $this->gateway->getInvitation($hash); - if (empty($invitationRow)) { - throw new NotFoundException('invitation', $hash); - } - return $this->mapper->extractInvitationFromRow($invitationRow); } @@ -63,10 +58,6 @@ public function getInvitationForEmail( ): Invitation { $invitationRow = $this->gateway->getInvitationByEmail($email); - if (empty($invitationRow)) { - throw new NotFoundException('invitation', $email); - } - return $this->mapper->extractInvitationFromRow($invitationRow); } diff --git a/tests/integration/IbexaKernelTestCase.php b/tests/integration/IbexaKernelTestCase.php index d604717..3d8af55 100644 --- a/tests/integration/IbexaKernelTestCase.php +++ b/tests/integration/IbexaKernelTestCase.php @@ -10,6 +10,7 @@ use Ibexa\Contracts\Core\Test\IbexaKernelTestCase as BaseIbexaKernelTestCase; use Ibexa\Contracts\User\Invitation\InvitationService; +use Ibexa\User\Invitation\Persistence\DoctrineGateway; abstract class IbexaKernelTestCase extends BaseIbexaKernelTestCase { @@ -17,4 +18,9 @@ protected static function getInvitationService(): InvitationService { return self::getServiceByClassName(InvitationService::class); } + + protected static function getInvitationGateway(): DoctrineGateway + { + return self::getServiceByClassName(DoctrineGateway::class); + } } diff --git a/tests/integration/IbexaTestKernel.php b/tests/integration/IbexaTestKernel.php index d328e52..6f5c19f 100644 --- a/tests/integration/IbexaTestKernel.php +++ b/tests/integration/IbexaTestKernel.php @@ -14,6 +14,7 @@ use Ibexa\ContentForms\Form\ActionDispatcher\UserDispatcher; use Ibexa\Contracts\Core\Test\IbexaTestKernel as BaseIbexaTestKernel; use Ibexa\Contracts\User\Invitation\InvitationService; +use Ibexa\User\Invitation\Persistence\DoctrineGateway; use LogicException; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -51,6 +52,7 @@ protected static function getExposedServicesByClass(): iterable yield InvitationService::class; yield FormFactoryInterface::class; + yield DoctrineGateway::class; } #[\Override] diff --git a/tests/integration/Invitation/InvitationServiceTest.php b/tests/integration/Invitation/InvitationServiceTest.php index fb33f60..7e142b8 100644 --- a/tests/integration/Invitation/InvitationServiceTest.php +++ b/tests/integration/Invitation/InvitationServiceTest.php @@ -16,6 +16,7 @@ use Ibexa\Contracts\User\Invitation\InvitationCreateStruct; use Ibexa\Contracts\User\Invitation\InvitationService; use Ibexa\Contracts\User\Invitation\Query\InvitationFilter; +use Ibexa\Core\Base\Exceptions\NotFoundException; use Ibexa\Tests\Integration\User\IbexaKernelTestCase; use Ibexa\User\Invitation\Persistence\Handler; use Symfony\Bridge\PhpUnit\ClockMock; @@ -168,4 +169,48 @@ public function testRefreshInvitation(): void ClockMock::withClockMock(false); } + + public function testGetInvitationByEmailReturnsRowForExistingInvitation(): void + { + $invitation = $this->invitationService->createInvitation( + new InvitationCreateStruct( + 'invitation-service@ibexa.co', + 'admin', + ) + ); + + $invitationByEmail = $this->invitationService->getInvitationByEmail($invitation->getEmail()); + + self::assertSame($invitation->getEmail(), $invitationByEmail->getEmail()); + self::assertSame($invitation->getHash(), $invitationByEmail->getHash()); + } + + public function testGetInvitationByEmailThrowErrorWhenInvitationDoesNotExist(): void + { + $this->expectException(NotFoundException::class); + + $this->invitationService->getInvitationByEmail('gateway-missing@ibexa.co'); + } + + public function testGetInvitationReturnsRowForExistingHash(): void + { + $invitation = $this->invitationService->createInvitation( + new InvitationCreateStruct( + 'invitation-service-by-hash@ibexa.co', + 'admin', + ) + ); + + $invitationByHash = $this->invitationService->getInvitation($invitation->getHash()); + + self::assertSame($invitation->getEmail(), $invitationByHash->getEmail()); + self::assertSame($invitation->getHash(), $invitationByHash->getHash()); + } + + public function testGetInvitationThrowExceptionWhenHashDoesNotExist(): void + { + $this->expectException(NotFoundException::class); + + $this->invitationService->getInvitation('invitation-service-missing-hash'); + } } diff --git a/tests/integration/Invitation/Persistence/DoctrineGatewayTest.php b/tests/integration/Invitation/Persistence/DoctrineGatewayTest.php new file mode 100644 index 0000000..215b5b0 --- /dev/null +++ b/tests/integration/Invitation/Persistence/DoctrineGatewayTest.php @@ -0,0 +1,65 @@ +gateway = self::getInvitationGateway(); + } + + public function testGetInvitationByEmailReturnsRowForExistingInvitation(): void + { + $invitation = $this->gateway->addInvitation( + 'gateway-existing@ibexa.co', + 'admin', + 'gateway-existing-hash' + ); + + $invitationRow = $this->gateway->getInvitationByEmail($invitation['email']); + + self::assertSame($invitation['email'], $invitationRow['email']); + self::assertSame($invitation['hash'], $invitationRow['hash']); + } + + public function testGetInvitationByEmailThrowExceptionWhenInvitationDoesNotExist(): void + { + $this->expectException(NotFoundException::class); + + $this->gateway->getInvitationByEmail('gateway-missing@ibexa.co'); + } + + public function testGetInvitationReturnsRowForExistingHash(): void + { + $invitation = $this->gateway->addInvitation( + 'gateway-existing-by-hash@ibexa.co', + 'admin', + 'gateway-existing-by-hash-hash' + ); + + $invitationRow = $this->gateway->getInvitation($invitation['hash']); + + self::assertSame($invitation['email'], $invitationRow['email']); + self::assertSame($invitation['hash'], $invitationRow['hash']); + } + + public function testGetInvitationThrowExceptionWhenHashDoesNotExist(): void + { + $this->expectException(NotFoundException::class); + + $this->gateway->getInvitation('gateway-missing-hash'); + } +}