diff --git a/composer.json b/composer.json index 1505942..09c8098 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "require": { "php": "^8.1", "doctrine/persistence": "^2.2.2 || ^3.0.0", - "nucleos/user-bundle": "^2.3 || ^3.0", + "nucleos/user-bundle": "^2.3 || ^3.4.0", "symfony/config": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", "symfony/event-dispatcher": "^6.4 || ^7.0", @@ -61,10 +61,12 @@ }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", + "dama/doctrine-test-bundle": "^8.0", "doctrine/doctrine-bundle": "^2.5", "doctrine/orm": "^2.7", "ergebnis/composer-normalize": "^2.0.1", "symfony/browser-kit": "^6.4 || ^7.0", + "symfony/css-selector": "^6.4 || ^7.0", "symfony/doctrine-bridge": "^6.4 || ^7.0", "symfony/yaml": "^6.4 || ^7.0" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f3a638c..8c50fe7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,12 +3,19 @@ + + + + ./tests/ + + + ./src/ diff --git a/src/EventListener/EmailConfirmationListener.php b/src/EventListener/EmailConfirmationListener.php index 7b7b038..2e8c5d8 100644 --- a/src/EventListener/EmailConfirmationListener.php +++ b/src/EventListener/EmailConfirmationListener.php @@ -42,9 +42,6 @@ public function __construct( $this->requestStack = $requestStack; } - /** - * @return array - */ public static function getSubscribedEvents(): array { return [ diff --git a/tests/App/AppKernel.php b/tests/App/AppKernel.php index 5260288..9d36aed 100644 --- a/tests/App/AppKernel.php +++ b/tests/App/AppKernel.php @@ -13,6 +13,7 @@ namespace Nucleos\ProfileBundle\Tests\App; +use DAMA\DoctrineTestBundle\DAMADoctrineTestBundle; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Nucleos\ProfileBundle\NucleosProfileBundle; use Nucleos\UserBundle\NucleosUserBundle; @@ -29,9 +30,9 @@ final class AppKernel extends Kernel { use MicroKernelTrait; - public function __construct() + public function __construct(string $environment = 'test', bool $debug = false) { - parent::__construct('test', false); + parent::__construct($environment, $debug); } public function registerBundles(): iterable @@ -46,6 +47,8 @@ public function registerBundles(): iterable yield new NucleosUserBundle(); + yield new DAMADoctrineTestBundle(); + yield new NucleosProfileBundle(); } diff --git a/tests/App/Entity/TestGroup.php b/tests/App/Entity/TestGroup.php index 0555cda..ac2e870 100644 --- a/tests/App/Entity/TestGroup.php +++ b/tests/App/Entity/TestGroup.php @@ -13,6 +13,16 @@ namespace Nucleos\ProfileBundle\Tests\App\Entity; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; use Nucleos\UserBundle\Model\Group; -class TestGroup extends Group {} +#[ORM\Entity] +#[ORM\Table(name: 'user__group')] +class TestGroup extends Group +{ + #[ORM\Id] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\GeneratedValue] + protected ?int $id = null; +} diff --git a/tests/App/Entity/TestUser.php b/tests/App/Entity/TestUser.php index 978baa9..8f27cc7 100644 --- a/tests/App/Entity/TestUser.php +++ b/tests/App/Entity/TestUser.php @@ -13,9 +13,44 @@ namespace Nucleos\ProfileBundle\Tests\App\Entity; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Nucleos\UserBundle\Model\GroupInterface; use Nucleos\UserBundle\Model\User; /** - * @phpstan-extends User<\Nucleos\UserBundle\Model\GroupInterface> + * @phpstan-extends User */ -class TestUser extends User {} +#[ORM\Entity] +#[ORM\Table(name: 'user__user')] +class TestUser extends User +{ + #[ORM\Id] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\GeneratedValue] + protected int $id; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: TestGroup::class)] + #[ORM\JoinTable(name: 'user__user_group')] + protected Collection $groups; + + private static int $index = 1; + + public function __construct() + { + parent::__construct(); + + $this->id = self::$index++; + $this->groups = new ArrayCollection(); + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/tests/App/config/config.php b/tests/App/config/config.php index 9e9df57..abf39f9 100644 --- a/tests/App/config/config.php +++ b/tests/App/config/config.php @@ -25,6 +25,12 @@ $containerConfigurator->extension('framework', ['session' => ['storage_factory_id' => 'session.storage.factory.mock_file', 'handler_id' => null]]); + $containerConfigurator->extension('framework', [ + 'mailer' => [ + 'dsn' => 'null://null', + ], + ]); + $containerConfigurator->extension('twig', ['strict_variables' => true]); $containerConfigurator->extension('twig', ['exception_controller' => null]); @@ -77,5 +83,9 @@ $containerConfigurator->extension('nucleos_user', ['group' => ['group_class' => TestGroup::class]]); - $containerConfigurator->extension('nucleos_user', ['loggedin' => ['route' => 'home']]); + $containerConfigurator->extension('nucleos_user', ['loggedin' => ['route' => 'nucleos_user_update_security']]); + + $containerConfigurator->extension('nucleos_profile', ['registration' => ['confirmation' => ['enabled' => true, 'from_email' => 'no-reply@example.com']]]); + + $containerConfigurator->extension('dama_doctrine_test', ['enable_static_connection' => true, 'enable_static_meta_data_cache' => true, 'enable_static_query_cache' => true]); }; diff --git a/tests/Functional/Action/EditProfileWebTest.php b/tests/Functional/Action/EditProfileWebTest.php new file mode 100644 index 0000000..5ad49dd --- /dev/null +++ b/tests/Functional/Action/EditProfileWebTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nucleos\ProfileBundle\Tests\Functional\Action; + +use Nucleos\ProfileBundle\Action\EditProfileAction; +use Nucleos\ProfileBundle\Tests\Functional\DoctrineSetupTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +#[CoversClass(EditProfileAction::class)] +class EditProfileWebTest extends WebTestCase +{ + use DoctrineSetupTrait; + + #[Test] + public function execute(): void + { + $client = self::createClient(); + + $this->persist( + $user = self::createUser(), + ); + + $client->loginUser($user); + $client->request('GET', '/profile/edit'); + + self::assertResponseIsSuccessful(); + + $client->submitForm('profile_form[save]', [ + 'profile_form[locale]' => 'de_DE', + ]); + + self::assertResponseRedirects('/profile/'); + $client->followRedirect(); + + self::assertSame('de_DE', $this->getUser($user->getId())?->getLocale()); + } +} diff --git a/tests/Functional/Action/RegistrationWebTest.php b/tests/Functional/Action/RegistrationWebTest.php new file mode 100644 index 0000000..05dcdd2 --- /dev/null +++ b/tests/Functional/Action/RegistrationWebTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nucleos\ProfileBundle\Tests\Functional\Action; + +use Nucleos\ProfileBundle\Action\CheckRegistrationMailAction; +use Nucleos\ProfileBundle\Action\ConfirmRegistrationAction; +use Nucleos\ProfileBundle\Action\RegistrationAction; +use Nucleos\ProfileBundle\Action\RegistrationConfirmedAction; +use Nucleos\ProfileBundle\Tests\App\Entity\TestUser; +use Nucleos\ProfileBundle\Tests\Functional\DoctrineSetupTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +#[CoversClass(RegistrationAction::class)] +#[CoversClass(ConfirmRegistrationAction::class)] +#[CoversClass(CheckRegistrationMailAction::class)] +#[CoversClass(RegistrationConfirmedAction::class)] +class RegistrationWebTest extends WebTestCase +{ + use DoctrineSetupTrait; + + #[Test] + public function execute(): void + { + $client = self::createClient(); + + $this->performRegister($client); + + $user = $this->getEntityManager()->getRepository(TestUser::class)->findOneBy([ + 'username' => 'new_username', + ]); + + self::assertNotNull($user); + self::assertFalse($user->isEnabled()); + self::assertNotNull($user->getConfirmationToken()); + + $this->performConfirm($client, $user); + } + + #[Test] + public function executeWithLoggedInUser(): void + { + $client = self::createClient(); + + $this->persist( + $user = self::createUser(), + ); + + $client->loginUser($user); + $client->request('GET', '/register/'); + + self::assertResponseRedirects('/profile/'); + } + + private function performRegister(KernelBrowser $client): void + { + $client->request('GET', '/register/'); + + self::assertResponseIsSuccessful(); + + $client->submitForm('registration_form[save]', [ + 'registration_form[username]' => 'new_username', + 'registration_form[email]' => 'new@example.com', + 'registration_form[plainPassword][first]' => 'super_secret_password', + 'registration_form[plainPassword][second]' => 'super_secret_password', + ]); + + self::assertResponseRedirects('/register/check-email'); + } + + private function performConfirm(KernelBrowser $client, TestUser $user): void + { + $client->request('GET', sprintf('/register/confirm/%s', $user->getConfirmationToken())); + + self::assertResponseRedirects('/register/confirmed'); + + $user = $this->getEntityManager()->find(TestUser::class, $user->getId()); + + self::assertNotNull($user); + self::assertTrue($user->isEnabled()); + self::assertNull($user->getConfirmationToken()); + } +} diff --git a/tests/Functional/Action/ShowProfileWebTest.php b/tests/Functional/Action/ShowProfileWebTest.php new file mode 100644 index 0000000..2a970d4 --- /dev/null +++ b/tests/Functional/Action/ShowProfileWebTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nucleos\ProfileBundle\Tests\Functional\Action; + +use Nucleos\ProfileBundle\Action\ShowProfileAction; +use Nucleos\ProfileBundle\Tests\Functional\DoctrineSetupTrait; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +#[CoversClass(ShowProfileAction::class)] +class ShowProfileWebTest extends WebTestCase +{ + use DoctrineSetupTrait; + + #[Test] + public function execute(): void + { + $client = self::createClient(); + + $this->persist( + $user = self::createUser(), + ); + + $client->loginUser($user); + $client->request('GET', '/profile/'); + + self::assertResponseIsSuccessful(); + + self::assertSelectorTextContains('.nucleos_profile_user_show', $user->getUsername()); + } +} diff --git a/tests/Functional/DoctrineSetupTrait.php b/tests/Functional/DoctrineSetupTrait.php new file mode 100644 index 0000000..f421b5e --- /dev/null +++ b/tests/Functional/DoctrineSetupTrait.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nucleos\ProfileBundle\Tests\Functional; + +use Doctrine\ORM\EntityManagerInterface; +use Nucleos\ProfileBundle\Tests\App\Entity\TestUser; +use Symfony\Bundle\SecurityBundle\Security; + +trait DoctrineSetupTrait +{ + /** + * @param string[] $roles + */ + public static function createUser( + string $username = null, + array $roles = [] + ): TestUser { + $entity = new TestUser(); + $entity->setPlainPassword('password'); + + $username ??= ('my-user'.$entity->getId()); + + $entity->setUsername($username); + $entity->setEmail(sprintf('%s@example.com', $username)); + $entity->setRoles($roles); + + return $entity; + } + + protected function persist(object ...$objects): void + { + $manager = $this->getEntityManager(); + + foreach ($objects as $object) { + $manager->persist($object); + } + + $manager->flush(); + } + + protected function getSecurity(): Security + { + $manager = self::getContainer()->get(Security::class); + + \assert($manager instanceof Security); + + return $manager; + } + + private function getUser(int $id): ?TestUser + { + return $this->getEntityManager()->find(TestUser::class, $id); + } + + private function getEntityManager(): EntityManagerInterface + { + $manager = self::getContainer()->get('doctrine.orm.entity_manager'); + + \assert($manager instanceof EntityManagerInterface); + + return $manager; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7be6547..b618c01 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -11,6 +11,10 @@ * file that was distributed with this source code. */ +use Nucleos\ProfileBundle\Tests\App\AppKernel; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; + if (!($loader = @include __DIR__.'/../vendor/autoload.php')) { echo <<<'EOT' You need to install the project dependencies using Composer: @@ -23,3 +27,32 @@ exit(1); } + +function bootstrap(): void +{ + $kernels = [ + AppKernel::class, + ]; + + foreach ($kernels as $kernel) { + $kernel = new $kernel('test', false); + $kernel->boot(); + + $application = new Application($kernel); + $application->setAutoExit(false); + + $application->run(new ArrayInput([ + 'command' => 'doctrine:database:create', + '--quiet' => '1', + ])); + + $application->run(new ArrayInput([ + 'command' => 'doctrine:schema:create', + '--quiet' => '1', + ])); + + $kernel->shutdown(); + } +} + +bootstrap();