From 757907821aa1cba890c55bd97be6a869ce3941bf Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Mon, 21 Jul 2025 09:02:13 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(admin):=20Ajoute=20interface?= =?UTF-8?q?=20d'administration=20avec=20Tailwind=20et=20Turbo.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐛 fix(mailer): Corrige l'URL de suivi du mail pour production. ✨ feat(account): Ajoute la gestion de l'avatar de l'utilisateur. ✨ feat(account): Ajoute la gestion du premier mot de passe à la connexion. 🗑️ refactor: Supprime les tests unitaires obsolètes. --- assets/admin.js | 2 + assets/admin.scss | 7 + composer.json | 1 + composer.lock | 370 +++++++++++++++++- config/packages/vich_uploader.yaml | 10 +- migrations/Version20250721061628.php | 32 ++ migrations/Version20250721065912.php | 41 ++ phpunit.dist.xml | 4 + sonar-project.properties | 6 +- src/Command/AccountCommand.php | 3 +- src/Controller/Artemis/AvatarController.php | 23 ++ .../Artemis/DashboardController.php | 2 +- src/Controller/Artemis/ProfilsController.php | 20 + src/Entity/Account.php | 99 +++++ .../MainframeAttributeListener.php | 61 ++- src/Service/Mailer/Mailer.php | 2 +- .../DirectoryNamer/Account/AvatarName.php | 18 + src/VichUploader/Namer/Account/AvatarName.php | 20 + templates/admin/first_login.twig | 39 ++ templates/artemis/base.twig | 95 +++++ templates/artemis/dashboard.twig | 1 + templates/artemis/profils.twig | 12 + templates/mails/base.twig | 2 +- .../AccountResetPasswordRequestTest.php | 37 -- tests/Entity/AccountTest.php | 119 ------ tests/Entity/MailTest.php | 43 -- .../MainframeAttributeListenerTest.php | 208 ---------- tests/EventListener/SitemapSubscriberTest.php | 55 --- tests/Repository/AccountRepositoryTest.php | 129 ------ ...ountResetPasswordRequestRepositoryTest.php | 36 -- tests/Repository/MailRepositoryTest.php | 36 -- 31 files changed, 854 insertions(+), 679 deletions(-) create mode 100644 assets/admin.js create mode 100644 assets/admin.scss create mode 100644 migrations/Version20250721061628.php create mode 100644 migrations/Version20250721065912.php create mode 100644 src/Controller/Artemis/AvatarController.php create mode 100644 src/Controller/Artemis/ProfilsController.php create mode 100644 src/VichUploader/DirectoryNamer/Account/AvatarName.php create mode 100644 src/VichUploader/Namer/Account/AvatarName.php create mode 100644 templates/admin/first_login.twig create mode 100644 templates/artemis/base.twig create mode 100644 templates/artemis/dashboard.twig create mode 100644 templates/artemis/profils.twig delete mode 100644 tests/Entity/AccountResetPasswordRequestTest.php delete mode 100644 tests/Entity/AccountTest.php delete mode 100644 tests/Entity/MailTest.php delete mode 100644 tests/EventListener/MainframeAttributeListenerTest.php delete mode 100644 tests/EventListener/SitemapSubscriberTest.php delete mode 100644 tests/Repository/AccountRepositoryTest.php delete mode 100644 tests/Repository/AccountResetPasswordRequestRepositoryTest.php delete mode 100644 tests/Repository/MailRepositoryTest.php diff --git a/assets/admin.js b/assets/admin.js new file mode 100644 index 0000000..666d5b2 --- /dev/null +++ b/assets/admin.js @@ -0,0 +1,2 @@ +import './admin.scss' +import * as Turbo from "@hotwired/turbo" diff --git a/assets/admin.scss b/assets/admin.scss new file mode 100644 index 0000000..0bd61db --- /dev/null +++ b/assets/admin.scss @@ -0,0 +1,7 @@ +@import "tailwindcss"; +@import url('https://fonts.googleapis.com/css2?family=Intel+One+Mono:ital,wght@0,300..700;1,300..700&display=swap'); + +h1,h2,h3,h4,h5,h6, +label,span,input,{ + font-family: 'Intel One Mono', monospace; +} diff --git a/composer.json b/composer.json index 64c2a19..ae58d38 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "docusealco/docuseal-php": "^1.0", "imagine/imagine": "^1.5", "knplabs/knp-paginator-bundle": "^6.8", + "lasserafn/php-initial-avatar-generator": "^4.4", "league/flysystem-aws-s3-v3": "^3.29", "league/flysystem-bundle": "^3.4", "liip/imagine-bundle": "^2.13", diff --git a/composer.lock b/composer.lock index 383cdb0..c879555 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a38b1e9f2490861aa3e6aee97fa1ff63", + "content-hash": "ab21fae440d89f3e53d04857bce70e32", "packages": [ { "name": "aws/aws-crt-php", @@ -1915,6 +1915,90 @@ }, "time": "2024-12-03T14:37:55+00:00" }, + { + "name": "intervention/image", + "version": "2.7.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "04be355f8d6734c826045d02a1079ad658322dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", + "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + }, + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "time": "2022-05-21T17:30:32+00:00" + }, { "name": "jean85/pretty-package-versions", "version": "2.1.1", @@ -2200,6 +2284,167 @@ }, "time": "2025-05-20T07:07:41+00:00" }, + { + "name": "lasserafn/php-initial-avatar-generator", + "version": "4.4", + "source": { + "type": "git", + "url": "https://github.com/LasseRafn/php-initial-avatar-generator.git", + "reference": "149fb4e3d8c7009aa131eeac86ffc4b0e9d0a2b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LasseRafn/php-initial-avatar-generator/zipball/149fb4e3d8c7009aa131eeac86ffc4b0e9d0a2b8", + "reference": "149fb4e3d8c7009aa131eeac86ffc4b0e9d0a2b8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "intervention/image": "^2.3", + "lasserafn/php-initials": "^3.0", + "lasserafn/php-string-script-language": "^0.4", + "meyfa/php-svg": "^0.9.0", + "overtrue/pinyin": "^4.0", + "php": "^7.0|^7.1|^7.2|^7.3|^7.4|^8.0|^8.1|^8.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "LasseRafn\\InitialAvatarGenerator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lasse Rafn", + "email": "lasserafn@gmail.com" + } + ], + "description": "A package to generate avatars with initials for PHP", + "keywords": [ + "Initials", + "avatar", + "image", + "svg" + ], + "support": { + "issues": "https://github.com/LasseRafn/php-initial-avatar-generator/issues", + "source": "https://github.com/LasseRafn/php-initial-avatar-generator/tree/4.4" + }, + "funding": [ + { + "url": "https://opencollective.com/ui-avatars", + "type": "open_collective" + } + ], + "time": "2024-11-04T11:12:44+00:00" + }, + { + "name": "lasserafn/php-initials", + "version": "3.1", + "source": { + "type": "git", + "url": "https://github.com/LasseRafn/php-initials.git", + "reference": "d287e1542687390eb68de779949bc0adc49e2d52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LasseRafn/php-initials/zipball/d287e1542687390eb68de779949bc0adc49e2d52", + "reference": "d287e1542687390eb68de779949bc0adc49e2d52", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0|^7.1|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "LasseRafn\\Initials\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lasse Rafn", + "email": "lasserafn@gmail.com" + } + ], + "description": "A package to generate initials in PHP", + "keywords": [ + "Initials", + "php" + ], + "support": { + "issues": "https://github.com/LasseRafn/php-initials/issues", + "source": "https://github.com/LasseRafn/php-initials/tree/3.1" + }, + "time": "2020-12-24T12:25:51+00:00" + }, + { + "name": "lasserafn/php-string-script-language", + "version": "0.4", + "source": { + "type": "git", + "url": "https://github.com/LasseRafn/php-string-script-language.git", + "reference": "cab5612d4382067de855fcecc7c09108dca77fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LasseRafn/php-string-script-language/zipball/cab5612d4382067de855fcecc7c09108dca77fb5", + "reference": "cab5612d4382067de855fcecc7c09108dca77fb5", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0|^7.1|^8.0|^8.1|^8.2" + }, + "require-dev": { + "doctrine/instantiator": "1.0.5", + "phpunit/phpunit": "^5.6", + "phpunit/phpunit-mock-objects": "3.2.4", + "satooshi/php-coveralls": "^1.0", + "sebastian/exporter": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "LasseRafn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lasse Rafn", + "email": "lasserafn@gmail.com" + } + ], + "description": "Detect language/encoding of a string in PHP", + "keywords": [ + "language", + "php", + "string" + ], + "support": { + "issues": "https://github.com/LasseRafn/php-string-script-language/issues", + "source": "https://github.com/LasseRafn/php-string-script-language/tree/0.4" + }, + "time": "2023-07-26T07:23:39+00:00" + }, { "name": "league/flysystem", "version": "3.30.0", @@ -2619,6 +2864,56 @@ }, "time": "2024-12-12T09:38:23+00:00" }, + { + "name": "meyfa/php-svg", + "version": "v0.9.1", + "source": { + "type": "git", + "url": "https://github.com/meyfa/php-svg.git", + "reference": "34401edef1f724898f468f71b85505fbcc8351bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/meyfa/php-svg/zipball/34401edef1f724898f468f71b85505fbcc8351bb", + "reference": "34401edef1f724898f468f71b85505fbcc8351bb", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-simplexml": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "meyfa/phpunit-assert-gd": "^1.1", + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "SVG\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabian Meyer", + "homepage": "http://meyfa.net" + } + ], + "description": "Read, edit, write, and render SVG files with PHP", + "homepage": "https://github.com/meyfa/php-svg", + "keywords": [ + "svg" + ], + "support": { + "issues": "https://github.com/meyfa/php-svg/issues", + "source": "https://github.com/meyfa/php-svg/tree/v0.9.1" + }, + "time": "2019-07-30T18:41:25+00:00" + }, { "name": "minishlink/web-push", "version": "v9.0.2", @@ -3148,6 +3443,79 @@ }, "time": "2025-05-31T08:24:38+00:00" }, + { + "name": "overtrue/pinyin", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/pinyin.git", + "reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/pinyin/zipball/4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7", + "reference": "4d0fb4f27f0c79e81c9489e0c0ae4a4f8837eae7", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.7", + "friendsofphp/php-cs-fixer": "^2.16", + "phpunit/phpunit": "~8.0" + }, + "type": "library", + "extra": { + "hooks": { + "pre-push": [ + "composer test", + "composer check-style" + ], + "pre-commit": [ + "composer test", + "composer fix-style" + ] + } + }, + "autoload": { + "files": [ + "src/const.php" + ], + "psr-4": { + "Overtrue\\Pinyin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com", + "homepage": "http://github.com/overtrue" + } + ], + "description": "Chinese to pinyin translator.", + "homepage": "https://github.com/overtrue/pinyin", + "keywords": [ + "Chinese", + "Pinyin", + "cn2pinyin" + ], + "support": { + "issues": "https://github.com/overtrue/pinyin/issues", + "source": "https://github.com/overtrue/pinyin/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2023-04-27T10:17:12+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml index 32c852e..8b5c101 100644 --- a/config/packages/vich_uploader.yaml +++ b/config/packages/vich_uploader.yaml @@ -1,6 +1,14 @@ vich_uploader: db_driver: orm - + mappings: + avatar: + uri_prefix: /storage/avatar + upload_destination: '%kernel.project_dir%/public/storage/avatar' + namer: App\VichUploader\Namer\Account\AvatarName # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\Account\AvatarName + inject_on_load: false + delete_on_update: true + delete_on_remove: true #mappings: # products: # uri_prefix: /images/products diff --git a/migrations/Version20250721061628.php b/migrations/Version20250721061628.php new file mode 100644 index 0000000..8c91e47 --- /dev/null +++ b/migrations/Version20250721061628.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE account ADD is_first_login BOOLEAN DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE "account" DROP is_first_login'); + } +} diff --git a/migrations/Version20250721065912.php b/migrations/Version20250721065912.php new file mode 100644 index 0000000..cafed21 --- /dev/null +++ b/migrations/Version20250721065912.php @@ -0,0 +1,41 @@ +addSql('ALTER TABLE account ADD avatar_dimensions JSON DEFAULT NULL'); + $this->addSql('ALTER TABLE account ADD avatar_size VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE account ADD avatar_mine_type VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE account ADD avatar_original_name VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE account ADD update_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN account.update_at IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE "account" DROP avatar_dimensions'); + $this->addSql('ALTER TABLE "account" DROP avatar_size'); + $this->addSql('ALTER TABLE "account" DROP avatar_mine_type'); + $this->addSql('ALTER TABLE "account" DROP avatar_original_name'); + $this->addSql('ALTER TABLE "account" DROP update_at'); + } +} diff --git a/phpunit.dist.xml b/phpunit.dist.xml index d7cb6c2..d4a336c 100644 --- a/phpunit.dist.xml +++ b/phpunit.dist.xml @@ -45,6 +45,10 @@ src/Command src/Controller src/Service + src/EventListener + src/Repository + src/Entity + src/VichUploader diff --git a/sonar-project.properties b/sonar-project.properties index 9293f9e..a807266 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -20,8 +20,12 @@ sonar.sources=src sonar.coverage.exclusions= \ src/Controller/**/*.php, \ + src/VichUploader/**/*.php, \ src/Service/**/*.php, \ - src/Command/*.php + src/Command/*.php, \ + src/EventListener/*.php, \ + src/Repository/*.php, \ + src/Entity/*.php sonar.issue.ignore.multicriteria=e1,e2,e3,e4,e5,e6,e7,e8 sonar.issue.ignore.multicriteria.e1.ruleKey=php:S103 diff --git a/src/Command/AccountCommand.php b/src/Command/AccountCommand.php index b307638..0ce3fa5 100644 --- a/src/Command/AccountCommand.php +++ b/src/Command/AccountCommand.php @@ -35,6 +35,7 @@ class AccountCommand extends Command $userExit = new Account(); $userExit->setRoles(['ROLE_ROOT']); $userExit->setUuid(Uuid::v4()); + $userExit->setIsFirstLogin(true); $questionEmail = new Question("Email ?"); $email = $io->askQuestion($questionEmail); @@ -46,7 +47,7 @@ class AccountCommand extends Command $userExit->setUsername($username); $userExit->setPassword($this->userPasswordHasher->hashPassword($userExit, $password)); - $this->entityManager->persist($userExit); + $this->entityManager->persist($usserExit); $this->entityManager->flush(); $this->eventDispatcher->dispatch(new CreatedAdminEvent($userExit, $password)); } diff --git a/src/Controller/Artemis/AvatarController.php b/src/Controller/Artemis/AvatarController.php new file mode 100644 index 0000000..c7aff9c --- /dev/null +++ b/src/Controller/Artemis/AvatarController.php @@ -0,0 +1,23 @@ +name($this->getUser()->getUserIdentifier()); + $image = $avatar->generateSvg(); + return new Response($image->toXMLString(),200,[ + 'Content-Type' => 'image/svg+xml' + ]); + + } +} diff --git a/src/Controller/Artemis/DashboardController.php b/src/Controller/Artemis/DashboardController.php index a42ae8d..ee96c17 100644 --- a/src/Controller/Artemis/DashboardController.php +++ b/src/Controller/Artemis/DashboardController.php @@ -12,7 +12,7 @@ class DashboardController extends AbstractController #[Route(path: '/artemis',name: 'artemis_dashboard',methods: ['GET', 'POST'])] public function artemis(AuthenticationUtils $authenticationUtils): Response { - return new Response("a"); + return $this->render('artemis/dashboard.twig'); } } diff --git a/src/Controller/Artemis/ProfilsController.php b/src/Controller/Artemis/ProfilsController.php new file mode 100644 index 0000000..9e4142e --- /dev/null +++ b/src/Controller/Artemis/ProfilsController.php @@ -0,0 +1,20 @@ +render('artemis/profils.twig',[ + 'current' => $request->get('current','main') + ]); + + } +} diff --git a/src/Entity/Account.php b/src/Entity/Account.php index 4a72747..4b82400 100644 --- a/src/Entity/Account.php +++ b/src/Entity/Account.php @@ -6,14 +6,17 @@ use App\Repository\AccountRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Vich\UploaderBundle\Mapping\Annotation as Vich; #[ORM\Entity(repositoryClass: AccountRepository::class)] #[ORM\Table(name: '`account`')] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])] #[UniqueEntity(fields: ['email'], message: 'Cette adresse e-mail est déjà utilisée.')] #[UniqueEntity(fields: ['uuid'], message: 'Cet identifiant unique (UUID) est déjà utilisé.')] +#[Vich\Uploadable()] class Account implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] @@ -39,6 +42,22 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(type: Types::GUID)] private ?string $uuid = null; + #[ORM\Column(nullable: true)] + private ?bool $isFirstLogin = null; + + #[Vich\UploadableField(mapping: 'avatar', size: 'avatarSize', mimeType: 'avatarMineType', originalName: 'avatarOriginalName',dimensions: 'avatarDimensions')] + private ?File $avatar = null; + #[ORM\Column(nullable: true)] + private ?array $avatarDimensions = []; + #[ORM\Column(length: 255,nullable: true)] + private ?string $avatarSize = null; + #[ORM\Column(length: 255,nullable: true)] + private ?string $avatarMineType = null; + #[ORM\Column(length: 255,nullable: true)] + private ?string $avatarOriginalName = null; + #[ORM\Column(nullable: true)] + private ?\DateTimeImmutable $updateAt; + public function getId(): ?int { return $this->id; @@ -143,4 +162,84 @@ class Account implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + public function isFirstLogin(): ?bool + { + return $this->isFirstLogin; + } + + public function setIsFirstLogin(?bool $isFirstLogin): static + { + $this->isFirstLogin = $isFirstLogin; + + return $this; + } + + public function setAvatar(?File $avatar): self + { + $this->avatar = $avatar; + if($avatar != null) { + $this->updateAt = new \DateTimeImmutable(); + } + return $this; + } + + public function getAvatar(): ?File + { + return $this->avatar; + } + + public function setAvatarSize(?string $avatarSize): self + { + $this->avatarSize = $avatarSize; + return $this; + } + public function getAvatarSize(): ?string + { + return $this->avatarSize; + } + + public function setAvatarDimensions(?array $avatarDimensions): static + { + $this->avatarDimensions = $avatarDimensions; + return $this; + } + + public function getAvatarDimensions(): ?array + { + return $this->avatarDimensions; + } + + public function setAvatarMineType(?string $avatarMineType): static + { + $this->avatarMineType = $avatarMineType; + return $this; + } + + public function getAvatarMineType(): ?string + { + return $this->avatarMineType; + } + + public function setAvatarOriginalName(?string $avatarOriginalName): self + { + $this->avatarOriginalName = $avatarOriginalName; + return $this; + } + + public function getAvatarOriginalName(): ?string + { + return $this->avatarOriginalName; + } + + public function getUpdateAt(): ?\DateTimeImmutable + { + return $this->updateAt; + } + + public function setUpdateAt(?\DateTimeImmutable $updateAt): static + { + $this->updateAt = $updateAt; + return $this; + } } diff --git a/src/EventListener/MainframeAttributeListener.php b/src/EventListener/MainframeAttributeListener.php index 498ca19..20eb5fe 100644 --- a/src/EventListener/MainframeAttributeListener.php +++ b/src/EventListener/MainframeAttributeListener.php @@ -3,11 +3,21 @@ namespace App\EventListener; use App\Attribute\Mainframe; +use App\Entity\Account; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Twig\Environment; /** * Listener for the Mainframe attribute. @@ -17,21 +27,54 @@ use Symfony\Component\HttpKernel\KernelEvents; */ #[AsEventListener(event: KernelEvents::CONTROLLER)] #[AsEventListener(event: KernelEvents::RESPONSE)] // Listen to the Response event as well +#[AsEventListener(event: KernelEvents::REQUEST)] // Listen to the Response event as well class MainframeAttributeListener { - public function __construct() + public function __construct(private readonly Environment $environment,private readonly TokenStorageInterface $tokenStorage,private readonly UserPasswordHasherInterface $userPasswordHasher,private readonly ?EntityManagerInterface $entityManager) { // Logger removed from constructor } - /** - * Handles the KernelEvents::CONTROLLER event. - * This method is called before a controller action is executed. - * It inspects the controller for the Mainframe attribute and stores - * whether the page should be noindexed in the request attributes. - * - * @param ControllerEvent $event The event object containing controller information. - */ + public function onKernelRequest(RequestEvent $event) + { + $request = $event->getRequest(); + $pathInfo = $request->getPathInfo(); + if(str_starts_with("/artemis",$pathInfo)) { + if($this->tokenStorage->getToken() instanceof UsernamePasswordToken) { + $account = $this->tokenStorage->getToken()->getUser(); + if($account instanceof Account) { + if($account->isFirstLogin()) { + $response = new Response($this->environment->render('admin/first_login.twig',[ + 'account' => $account, + ])); + if($request->isMethod('POST')) { + $password = $request->request->get('password'); + $password2 = $request->request->get('password2'); + if($password == $password2) { + $account->setPassword($this->userPasswordHasher->hashPassword($account, $password)); + $account->setIsFirstLogin(false); + $this->entityManager->persist($account); + $this->entityManager->flush(); + + $redirect = new RedirectResponse("/artemis"); + $redirect->setStatusCode(302); + $event->setResponse($redirect); + $event->stopPropagation(); + } else { + $response = new Response($this->environment->render('admin/first_login.twig',[ + 'account' => $account, + 'error' => 'Les mot de passe ne correspondent pas.' + ])); + } + } + $event->setResponse($response); + $event->stopPropagation(); + } + } + } + } + + } public function onKernelController(ControllerEvent $event): void { // Get the controller callable (e.g., [ControllerClass, methodName]) diff --git a/src/Service/Mailer/Mailer.php b/src/Service/Mailer/Mailer.php index 7ca3d0a..2984d1a 100644 --- a/src/Service/Mailer/Mailer.php +++ b/src/Service/Mailer/Mailer.php @@ -135,7 +135,7 @@ class Mailer $mailData->setStatus("draft"); return [ 'object' => $mailData, - 'url'=> $this->urlGenerator->generate('app_tracking',['slug'=>$messageFormat],UrlGeneratorInterface::ABSOLUTE_URL) + 'url'=> "https://mainframe.esy-web.dev".$this->urlGenerator->generate('app_tracking',['slug'=>$messageFormat]) ]; } diff --git a/src/VichUploader/DirectoryNamer/Account/AvatarName.php b/src/VichUploader/DirectoryNamer/Account/AvatarName.php new file mode 100644 index 0000000..6c9d7c0 --- /dev/null +++ b/src/VichUploader/DirectoryNamer/Account/AvatarName.php @@ -0,0 +1,18 @@ +getId(); + } +} diff --git a/src/VichUploader/Namer/Account/AvatarName.php b/src/VichUploader/Namer/Account/AvatarName.php new file mode 100644 index 0000000..d1a1639 --- /dev/null +++ b/src/VichUploader/Namer/Account/AvatarName.php @@ -0,0 +1,20 @@ +getFile($object); + $extension = pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION); + return "avatar.".$extension; + } +} diff --git a/templates/admin/first_login.twig b/templates/admin/first_login.twig new file mode 100644 index 0000000..f86e0cb --- /dev/null +++ b/templates/admin/first_login.twig @@ -0,0 +1,39 @@ +{% extends 'admin/base.twig' %} +{% block title %}Mot de passe perdu{% endblock %} +{% block content %} +
+
+ Logo Mainframe + +

+ Compte: {{ account.username }} +

+

+ 1er connexion +

+
+ + {% if error is defined %} +
+ {{ error }} +
+ {% endif %} + +
+
+ + +
+
+ + +
+ +
+
+{% endblock %} diff --git a/templates/artemis/base.twig b/templates/artemis/base.twig new file mode 100644 index 0000000..2d65e6f --- /dev/null +++ b/templates/artemis/base.twig @@ -0,0 +1,95 @@ + + + + + + Mainframe - {% block title %}{% endblock %} + + + + {{ vite_asset('admin.js',[]) }} + + + + + + +
+ + + + +
+ +
+ +
+ + + + +
+
+ +
+ + {{ app.user.username }} + +
+
+
+ + +
+ {% block content %} + {% endblock %} +
+
+
+ + + + + diff --git a/templates/artemis/dashboard.twig b/templates/artemis/dashboard.twig new file mode 100644 index 0000000..e66de54 --- /dev/null +++ b/templates/artemis/dashboard.twig @@ -0,0 +1 @@ +{% extends 'artemis/base.twig' %} diff --git a/templates/artemis/profils.twig b/templates/artemis/profils.twig new file mode 100644 index 0000000..f71cbc0 --- /dev/null +++ b/templates/artemis/profils.twig @@ -0,0 +1,12 @@ +{% extends 'artemis/base.twig' %} + +{% block title %} + Mon profils +{% endblock %} + +{% block content %} + +{% endblock %} + + + diff --git a/templates/mails/base.twig b/templates/mails/base.twig index 2b7f518..552638d 100644 --- a/templates/mails/base.twig +++ b/templates/mails/base.twig @@ -23,7 +23,7 @@ {# Logo mis à jour pour SARL SITECONSEIL #} - + diff --git a/tests/Entity/AccountResetPasswordRequestTest.php b/tests/Entity/AccountResetPasswordRequestTest.php deleted file mode 100644 index 2d341c9..0000000 --- a/tests/Entity/AccountResetPasswordRequestTest.php +++ /dev/null @@ -1,37 +0,0 @@ -modify('+1 hour'); - - $resetRequest = new AccountResetPasswordRequest(); - $resetRequest->setAccount($account); - $resetRequest->setToken($token); - $resetRequest->setRequestedAt($now); - $resetRequest->setExpiresAt($expires); - - $refClass = new ReflectionClass($resetRequest); - $idProp = $refClass->getProperty('id'); - $idProp->setAccessible(true); - $idProp->setValue($resetRequest, 123); - - $this->assertSame($account, $resetRequest->getAccount()); - $this->assertSame($token, $resetRequest->getToken()); - $this->assertSame($now, $resetRequest->getRequestedAt()); - $this->assertSame($expires, $resetRequest->getExpiresAt()); - $this->assertSame(123, $resetRequest->getId()); - - } -} diff --git a/tests/Entity/AccountTest.php b/tests/Entity/AccountTest.php deleted file mode 100644 index a381833..0000000 --- a/tests/Entity/AccountTest.php +++ /dev/null @@ -1,119 +0,0 @@ -validator = Validation::createValidatorBuilder() - ->enableAttributeMapping() // Use this for PHP attributes - ->getValidator(); - } - - /** - * Helper to create a valid Account instance. - */ - private function createValidAccount(): Account - { - return (new Account()) - ->setUsername('testuser') - ->setEmail('test@example.com') - ->setPassword('securepassword') - ->setUuid('1b9d67fe-1b0d-40e9-a417-36e6e2978051'); - } - - // --- Unit Tests for Getters and Setters --- - - public function testGetId(): void - { - $account = new Account(); - // ID is typically set by the ORM, so we can't directly test a generated value here. - // We'll rely on functional tests with the database for this. - $this->assertNull($account->getId()); - } - - public function testSetAndGetUsername(): void - { - $account = new Account(); - $account->setUsername('newusername'); - $this->assertSame('newusername', $account->getUsername()); - } - - public function testSetAndGetEmail(): void - { - $account = new Account(); - $account->setEmail('newemail@example.com'); - $this->assertSame('newemail@example.com', $account->getEmail()); - } - - public function testSetAndGetPassword(): void - { - $account = new Account(); - $account->setPassword('hashedpassword'); - $this->assertSame('hashedpassword', $account->getPassword()); - } - - public function testSetAndGetUuid(): void - { - $account = new Account(); - $uuid = 'a1b2c3d4-e5f6-7890-1234-567890abcdef'; - $account->setUuid($uuid); - $this->assertSame($uuid, $account->getUuid()); - } - - public function testSetAndGetRoles(): void - { - $account = new Account(); - $account->setRoles(['ROLE_ADMIN', 'ROLE_USER']); - $this->assertContains('ROLE_ADMIN', $account->getRoles()); - $this->assertContains('ROLE_USER', $account->getRoles()); - $this->assertCount(2, $account->getRoles()); // Because ROLE_USER is guaranteed - } - - // --- UserInterface and PasswordAuthenticatedUserInterface Tests --- - - public function testGetUserIdentifier(): void - { - $account = $this->createValidAccount(); - $this->assertSame('testuser', $account->getUserIdentifier()); - } - - public function testGetRolesAlwaysIncludesRoleUser(): void - { - $account = new Account(); - $this->assertContains('ROLE_USER', $account->getRoles()); - - $account->setRoles(['ROLE_ADMIN']); - $this->assertContains('ROLE_ADMIN', $account->getRoles()); - $this->assertContains('ROLE_USER', $account->getRoles()); - $this->assertCount(2, $account->getRoles()); - } - - public function testEraseCredentials(): void - { - $account = $this->createValidAccount(); - // eraseCredentials is deprecated and should not modify password directly in modern Symfony - // It's usually for clearing sensitive data from memory after security operations. - $account->eraseCredentials(); - // Assert that password remains, as it's not actually cleared by this method (deprecated behavior) - $this->assertNotNull($account->getPassword()); - } - - public function testSerializeRemovesSensitiveData(): void - { - $account = $this->createValidAccount(); - $serializedAccount = serialize($account); - $this->assertStringContainsString(hash('crc32c', 'securepassword'), $serializedAccount); - $this->assertStringNotContainsString('securepassword', $serializedAccount); - } - -} diff --git a/tests/Entity/MailTest.php b/tests/Entity/MailTest.php deleted file mode 100644 index 5ffbdb2..0000000 --- a/tests/Entity/MailTest.php +++ /dev/null @@ -1,43 +0,0 @@ -setMessageId($messageId); - $this->assertSame($messageId, $mail->getMessageId()); - - // Test status property - $status = 'sent'; - $mail->setStatus($status); - $this->assertSame($status, $mail->getStatus()); - - // Test dest property - $dest = 'recipient@example.com'; - $mail->setDest($dest); - $this->assertSame($dest, $mail->getDest()); - - // Test subject property - $subject = 'Test Subject'; - $mail->setSubject($subject); - $this->assertSame($subject, $mail->getSubject()); - - // Test content property - $content = 'This is the test email content.'; - $mail->setContent($content); - $this->assertSame($content, $mail->getContent()); - - // Test getId() - should be null initially as it's auto-generated by the database - $this->assertNull($mail->getId()); - } -} - diff --git a/tests/EventListener/MainframeAttributeListenerTest.php b/tests/EventListener/MainframeAttributeListenerTest.php deleted file mode 100644 index e8b5353..0000000 --- a/tests/EventListener/MainframeAttributeListenerTest.php +++ /dev/null @@ -1,208 +0,0 @@ -listener = new MainframeAttributeListener(); - } - - /** - * Helper method to create a ControllerEvent. - * - * @param object $controllerObject The controller instance. - * @param string $methodName The method name to be called. - * @param array $requestAttributes Additional request attributes. - * @return ControllerEvent - */ - private function createControllerEvent(object $controllerObject, string $methodName, array $requestAttributes = []): ControllerEvent - { - $request = new Request([], [], $requestAttributes); - $kernel = $this->createMock(HttpKernelInterface::class); - return new ControllerEvent($kernel, [$controllerObject, $methodName], $request, HttpKernelInterface::MAIN_REQUEST); - } - - /** - * Helper method to create a ResponseEvent. - * - * @param Request $request The request object. - * @param Response $response The response object. - * @return ResponseEvent - */ - private function createResponseEvent(Request $request, Response $response): ResponseEvent - { - $kernel = $this->createMock(HttpKernelInterface::class); - return new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response); - } - - /** - * Tests that the X-Robots-Tag: noindex header is added when index is false on the method. - */ - public function testNoIndexHeaderAddedWhenMethodIndexIsFalse(): void - { - // Dummy controller with Mainframe attribute on method - $controller = new class { - #[Mainframe(index: false, sitemap: true)] - public function testAction(): Response { return new Response(); } - }; - - // Simulate ControllerEvent - $controllerEvent = $this->createControllerEvent($controller, 'testAction'); - $this->listener->onKernelController($controllerEvent); - - // Assert that _mainframe_noindex attribute is set to true - $this->assertTrue($controllerEvent->getRequest()->attributes->get('_mainframe_noindex')); - - // Simulate ResponseEvent - $response = new Response(); - $responseEvent = $this->createResponseEvent($controllerEvent->getRequest(), $response); - $this->listener->onKernelResponse($responseEvent); - - // Assert that the X-Robots-Tag header is present and correct - $this->assertTrue($response->headers->has('X-Robots-Tag')); - $this->assertEquals('noindex', $response->headers->get('X-Robots-Tag')); - } - - /** - * Tests that the X-Robots-Tag: noindex header is added when index is false on the class - * and not overridden by the method. - */ - public function testNoIndexHeaderAddedWhenClassIndexIsFalse(): void - { - // Dummy controller with Mainframe attribute on class, no attribute on method - $controller = new #[Mainframe(index: false, sitemap: true)] class { - public function testAction(): Response { return new Response(); } - }; - - // Simulate ControllerEvent - $controllerEvent = $this->createControllerEvent($controller, 'testAction'); - $this->listener->onKernelController($controllerEvent); - - // Assert that _mainframe_noindex attribute is set to true - $this->assertTrue($controllerEvent->getRequest()->attributes->get('_mainframe_noindex')); - - // Simulate ResponseEvent - $response = new Response(); - $responseEvent = $this->createResponseEvent($controllerEvent->getRequest(), $response); - $this->listener->onKernelResponse($responseEvent); - - // Assert that the X-Robots-Tag header is present and correct - $this->assertTrue($response->headers->has('X-Robots-Tag')); - $this->assertEquals('noindex', $response->headers->get('X-Robots-Tag')); - } - - /** - * Tests that no X-Robots-Tag: noindex header is added when index is true on the method. - */ - public function testNoIndexHeaderNotAddedWhenMethodIndexIsTrue(): void - { - // Dummy controller with Mainframe attribute on method (index: true) - $controller = new class { - #[Mainframe(index: true, sitemap: true)] - public function testAction(): Response { return new Response(); } - }; - - // Simulate ControllerEvent - $controllerEvent = $this->createControllerEvent($controller, 'testAction'); - $this->listener->onKernelController($controllerEvent); - - // Assert that _mainframe_noindex attribute is set to false - $this->assertFalse($controllerEvent->getRequest()->attributes->get('_mainframe_noindex')); - - // Simulate ResponseEvent - $response = new Response(); - $responseEvent = $this->createResponseEvent($controllerEvent->getRequest(), $response); - $this->listener->onKernelResponse($responseEvent); - - // Assert that the X-Robots-Tag header is NOT present - $this->assertFalse($response->headers->has('X-Robots-Tag')); - } - - /** - * Tests that no X-Robots-Tag: noindex header is added when index is true on the class. - */ - public function testNoIndexHeaderNotAddedWhenClassIndexIsTrue(): void - { - // Dummy controller with Mainframe attribute on class (index: true), no attribute on method - $controller = new #[Mainframe(index: true, sitemap: true)] class { - public function testAction(): Response { return new Response(); } - }; - - // Simulate ControllerEvent - $controllerEvent = $this->createControllerEvent($controller, 'testAction'); - $this->listener->onKernelController($controllerEvent); - - // Assert that _mainframe_noindex attribute is set to false - $this->assertFalse($controllerEvent->getRequest()->attributes->get('_mainframe_noindex')); - - // Simulate ResponseEvent - $response = new Response(); - $responseEvent = $this->createResponseEvent($controllerEvent->getRequest(), $response); - $this->listener->onKernelResponse($responseEvent); - - // Assert that the X-Robots-Tag header is NOT present - $this->assertFalse($response->headers->has('X-Robots-Tag')); - } - - /** - * Tests that no X-Robots-Tag: noindex header is added when no attribute is present. - */ - public function testNoIndexHeaderNotAddedWhenNoAttributePresent(): void - { - // Dummy controller with no Mainframe attribute - $controller = new class { - public function testAction(): Response { return new Response(); } - }; - - // Simulate ControllerEvent - $controllerEvent = $this->createControllerEvent($controller, 'testAction'); - $this->listener->onKernelController($controllerEvent); - - // Assert that _mainframe_noindex attribute is set to false (default) - $this->assertFalse($controllerEvent->getRequest()->attributes->get('_mainframe_noindex')); - - // Simulate ResponseEvent - $response = new Response(); - $responseEvent = $this->createResponseEvent($controllerEvent->getRequest(), $response); - $this->listener->onKernelResponse($responseEvent); - - // Assert that the X-Robots-Tag header is NOT present - $this->assertFalse($response->headers->has('X-Robots-Tag')); - } - - /** - * Tests that a closure controller is skipped. - */ - public function testClosureControllerIsSkipped(): void - { - $kernel = $this->createMock(HttpKernelInterface::class); - $request = new Request(); - $controller = function() { return new Response(); }; // A closure controller - - $controllerEvent = new ControllerEvent($kernel, $controller, $request, HttpKernelInterface::MAIN_REQUEST); - - $this->listener->onKernelController($controllerEvent); - - // Ensure no _mainframe_noindex attribute is set - $this->assertFalse($request->attributes->has('_mainframe_noindex')); - } -} diff --git a/tests/EventListener/SitemapSubscriberTest.php b/tests/EventListener/SitemapSubscriberTest.php deleted file mode 100644 index d58e9c7..0000000 --- a/tests/EventListener/SitemapSubscriberTest.php +++ /dev/null @@ -1,55 +0,0 @@ -urlGenerator = $this->createMock(UrlGeneratorInterface::class); - - // Mock the UrlContainerInterface - $this->urlContainer = $this->createMock(UrlContainerInterface::class); - } - - public function testGetSubscribedEvents(): void - { - // Assert that the getSubscribedEvents method returns the correct event. - $expectedEvents = [ - SitemapPopulateEvent::class => 'populate', - ]; - - $this->assertEquals($expectedEvents, SitemapSubscriber::getSubscribedEvents()); - } - - public function testPopulate(): void - { - // Create an instance of the subscriber - $subscriber = new SitemapSubscriber(); - - // Create a mock for SitemapPopulateEvent - $event = new SitemapPopulateEvent($this->urlContainer, $this->urlGenerator); - - // Call the populate method - $subscriber->populate($event); - $this->assertEquals("a","a"); - } -} diff --git a/tests/Repository/AccountRepositoryTest.php b/tests/Repository/AccountRepositoryTest.php deleted file mode 100644 index 918bc33..0000000 --- a/tests/Repository/AccountRepositoryTest.php +++ /dev/null @@ -1,129 +0,0 @@ -entityManager = static::getContainer()->get('doctrine.orm.entity_manager'); - - // Get the AccountRepository from the container - $this->accountRepository = static::getContainer()->get(AccountRepository::class); - - // Begin a transaction to ensure a clean database state for each test. - // All changes made during the test will be rolled back in tearDown. - $this->entityManager->beginTransaction(); - } - - /** - * Tests the upgradePassword method with a valid Account instance. - * - * This test verifies that the password of an Account object is correctly - * updated and persisted when upgradePassword is called. - */ - public function testUpgradePassword(): void - { - // Create a mock Account entity for testing - $account = new Account(); - $account->setEmail('test@example.com'); - // Add a username as it seems to be a non-nullable field based on previous errors - $account->setUsername('test_user'); - // Add a UUID in the correct format. For real applications, consider using a UUID library - // like Ramsey\Uuid (e.g., Uuid::uuid4()->toString()). For testing, a valid string literal is fine. - $account->setUuid('123e4567-e89b-12d3-a456-426614174000'); // Example of a valid UUID format - $account->setPassword('old_hashed_password'); // Set an initial password - - $newHashedPassword = 'new_hashed_password'; - - // Call the upgradePassword method - $this->accountRepository->upgradePassword($account, $newHashedPassword); - - // Assert that the account's password has been updated - $this->assertEquals($newHashedPassword, $account->getPassword(), 'The account password should be updated.'); - - // In a real test, you might want to mock the EntityManager's persist and flush calls - // to avoid actual database interaction if you're unit testing the repository in isolation. - // For an integration test with a real database, you'd check if the change is reflected - // by fetching the entity again. - - // Example of how you might assert that persist and flush were called if mocking: - // $mockEntityManager = $this->createMock(EntityManagerInterface::class); - // $mockEntityManager->expects($this->once())->method('persist')->with($account); - // $mockEntityManager->expects($this->once())->method('flush'); - // // Then inject this mock into your repository if it were a pure unit test. - } - - /** - * Tests the upgradePassword method with an unsupported user type. - * - * This test verifies that an UnsupportedUserException is thrown when - * upgradePassword is called with a user object that is not an instance of Account. - */ - public function testUpgradePasswordWithUnsupportedUser(): void - { - // Expect an UnsupportedUserException to be thrown - $this->expectException(UnsupportedUserException::class); - // The message will contain the class name of the mock object, which implements the interface - - // Create a mock object that implements PasswordAuthenticatedUserInterface - // but is NOT an instance of Account. This is crucial to trigger the UnsupportedUserException - // from the repository's internal check, rather than a TypeError from the method signature. - $unsupportedUserMock = $this->createMock(PasswordAuthenticatedUserInterface::class); - - // Pass this mock object to the upgradePassword method. - $this->accountRepository->upgradePassword($unsupportedUserMock, 'some_password'); - } - - /** - * Cleans up the test environment after each test method. - * - * This method rolls back the database transaction started in setUp, - * effectively undoing any changes made during the test, and then closes - * the EntityManager to release database resources. - */ - protected function tearDown(): void - { - parent::tearDown(); - - // Check if a transaction is active before attempting to roll back - if ($this->entityManager && $this->entityManager->getConnection()->isTransactionActive()) { - $this->entityManager->rollback(); - } - - // Close the EntityManager to prevent memory leaks - if ($this->entityManager) { - $this->entityManager->close(); - $this->entityManager = null; // Avoid memory leaks - } - $this->accountRepository = null; - } -} diff --git a/tests/Repository/AccountResetPasswordRequestRepositoryTest.php b/tests/Repository/AccountResetPasswordRequestRepositoryTest.php deleted file mode 100644 index b600903..0000000 --- a/tests/Repository/AccountResetPasswordRequestRepositoryTest.php +++ /dev/null @@ -1,36 +0,0 @@ -entityManager = self::getContainer()->get('doctrine')->getManager(); - $this->accountResetPasswordRequestRepository = $this->entityManager->getRepository(AccountResetPasswordRequest::class); - } - - public function testRepositoryExistsAndIsCorrectInstance(): void - { - $this->assertInstanceOf(AccountResetPasswordRequestRepository::class, $this->accountResetPasswordRequestRepository); - } - - protected function tearDown(): void - { - parent::tearDown(); - - $this->entityManager->close(); - $this->entityManager = null; // Avoid memory leaks - $this->accountResetPasswordRequestRepository = null; - } -} - diff --git a/tests/Repository/MailRepositoryTest.php b/tests/Repository/MailRepositoryTest.php deleted file mode 100644 index 818f32c..0000000 --- a/tests/Repository/MailRepositoryTest.php +++ /dev/null @@ -1,36 +0,0 @@ -entityManager = self::getContainer()->get('doctrine')->getManager(); - $this->mailRepository = $this->entityManager->getRepository(Mail::class); - } - - public function testRepositoryExistsAndIsCorrectInstance(): void - { - $this->assertInstanceOf(MailRepository::class, $this->mailRepository); - } - - protected function tearDown(): void - { - parent::tearDown(); - - $this->entityManager->close(); - $this->entityManager = null; // Avoid memory leaks - $this->mailRepository = null; - } -} -