✨ feat(admin): Ajoute interface d'administration avec Tailwind et Turbo.
🐛 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.
This commit is contained in:
2
assets/admin.js
Normal file
2
assets/admin.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import './admin.scss'
|
||||
import * as Turbo from "@hotwired/turbo"
|
||||
7
assets/admin.scss
Normal file
7
assets/admin.scss
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
370
composer.lock
generated
370
composer.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
32
migrations/Version20250721061628.php
Normal file
32
migrations/Version20250721061628.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250721061628 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->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');
|
||||
}
|
||||
}
|
||||
41
migrations/Version20250721065912.php
Normal file
41
migrations/Version20250721065912.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250721065912 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->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');
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,10 @@
|
||||
<directory>src/Command</directory>
|
||||
<directory>src/Controller</directory>
|
||||
<directory>src/Service</directory>
|
||||
<directory>src/EventListener</directory>
|
||||
<directory>src/Repository</directory>
|
||||
<directory>src/Entity</directory>
|
||||
<directory>src/VichUploader</directory>
|
||||
</exclude>
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
23
src/Controller/Artemis/AvatarController.php
Normal file
23
src/Controller/Artemis/AvatarController.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Artemis;
|
||||
|
||||
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class AvatarController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis/avatar',name: 'artemis_avatar',methods: ['GET', 'POST'])]
|
||||
public function artemis(): Response
|
||||
{
|
||||
$avatar = new InitialAvatar();
|
||||
$avatar->name($this->getUser()->getUserIdentifier());
|
||||
$image = $avatar->generateSvg();
|
||||
return new Response($image->toXMLString(),200,[
|
||||
'Content-Type' => 'image/svg+xml'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
20
src/Controller/Artemis/ProfilsController.php
Normal file
20
src/Controller/Artemis/ProfilsController.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Artemis;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class ProfilsController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/artemis/profils',name: 'artemis_profils',methods: ['GET', 'POST'])]
|
||||
public function artemis(Request $request): Response
|
||||
{
|
||||
return $this->render('artemis/profils.twig',[
|
||||
'current' => $request->get('current','main')
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
18
src/VichUploader/DirectoryNamer/Account/AvatarName.php
Normal file
18
src/VichUploader/DirectoryNamer/Account/AvatarName.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\VichUploader\DirectoryNamer\Account;
|
||||
|
||||
use App\Entity\Account;
|
||||
use Vich\UploaderBundle\Mapping\PropertyMapping;
|
||||
use Vich\UploaderBundle\Naming\DirectoryNamerInterface;
|
||||
|
||||
class AvatarName implements DirectoryNamerInterface
|
||||
{
|
||||
|
||||
public function directoryName(object|array $object, PropertyMapping $mapping): string
|
||||
{
|
||||
/** @var Account $account */
|
||||
$account = $object;
|
||||
return $account->getId();
|
||||
}
|
||||
}
|
||||
20
src/VichUploader/Namer/Account/AvatarName.php
Normal file
20
src/VichUploader/Namer/Account/AvatarName.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\VichUploader\Namer\Account;
|
||||
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Vich\UploaderBundle\Mapping\PropertyMapping;
|
||||
use Vich\UploaderBundle\Naming\NamerInterface;
|
||||
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
|
||||
class AvatarName implements NamerInterface
|
||||
{
|
||||
|
||||
public function name(object $object, PropertyMapping $mapping): string
|
||||
{
|
||||
/** @var UploadedFile $file */
|
||||
$file = $mapping->getFile($object);
|
||||
$extension = pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION);
|
||||
return "avatar.".$extension;
|
||||
}
|
||||
}
|
||||
39
templates/admin/first_login.twig
Normal file
39
templates/admin/first_login.twig
Normal file
@@ -0,0 +1,39 @@
|
||||
{% extends 'admin/base.twig' %}
|
||||
{% block title %}Mot de passe perdu{% endblock %}
|
||||
{% block content %}
|
||||
<div class="max-w-md w-full space-y-8 p-10 bg-gray-800 rounded-xl shadow-xl z-10">
|
||||
<div class="text-center">
|
||||
<img class="mx-auto h-12 w-auto" src="{{ asset('assets/logo.png') | imagine_filter('webp') }}" alt="Logo Mainframe">
|
||||
|
||||
<h2 class="mt-6 text-3xl font-extrabold text-white">
|
||||
Compte: {{ account.username }}
|
||||
</h2>
|
||||
<h2 class="mt-6 text-2xl font-extrabold text-white">
|
||||
1er connexion
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{% if error is defined %}
|
||||
<div class="w-full px-4 py-3 rounded-md text-sm font-medium text-red-100 bg-red-600">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form data-turbo="false" method="POST">
|
||||
<div class="mb-5">
|
||||
<label for="password1" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Mot de passe</label>
|
||||
<input type="password" name="password" id="password1" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<label for="password2" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirmer le mot de passe</label>
|
||||
<input type="password" name="password2" id="password2" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-400 dark:bg-indigo-500 dark:hover:bg-indigo-600"
|
||||
>
|
||||
Valider
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
95
templates/artemis/base.twig
Normal file
95
templates/artemis/base.twig
Normal file
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mainframe - {% block title %}{% endblock %}</title>
|
||||
<meta name="robots" content="noindex">
|
||||
|
||||
|
||||
{{ vite_asset('admin.js',[]) }}
|
||||
|
||||
|
||||
<style>
|
||||
/* Custom styles */
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
/* Custom scrollbar for webkit browsers */
|
||||
.sidebar-scroll::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.sidebar-scroll::-webkit-scrollbar-track {
|
||||
background-color: #1f2937;
|
||||
}
|
||||
.sidebar-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: #4b5563;
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
||||
|
||||
<div class="flex h-screen bg-gray-100 dark:bg-gray-900">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar" class="fixed top-0 left-0 z-40 w-64 h-screen bg-white dark:bg-gray-800 shadow-lg transform -translate-x-full md:translate-x-0 transition-transform duration-300 ease-in-out">
|
||||
<div class="flex items-center justify-center h-20 border-b dark:border-gray-700">
|
||||
<h1 class="text-2xl font-bold ml-2">Mainframe</h1>
|
||||
</div>
|
||||
<nav class="mt-4 flex-1 sidebar-scroll overflow-y-auto" style="height: calc(100vh - 80px);">
|
||||
<ul>
|
||||
<!-- Dashboard Link -->
|
||||
<li class="px-4 py-2">
|
||||
<a href="{{ path('artemis_dashboard') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == "artemis_dashboard"%}bg-gray-200 dark:bg-gray-700{% endif%} rounded-lg">
|
||||
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path><path d="M12 2.252A8.014 8.014 0 0117.748 12H12V2.252z"></path></svg>
|
||||
<span class="ml-3">Tableau de bord</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<div class="flex flex-col flex-1 md:ml-64 transition-all duration-300 ease-in-out">
|
||||
<!-- Header -->
|
||||
<header class="flex items-center justify-between h-20 px-6 bg-white dark:bg-gray-800 border-b dark:border-gray-700">
|
||||
<button id="sidebar-toggle" class="text-gray-500 dark:text-gray-400 focus:outline-none focus:text-gray-600 dark:focus:text-gray-300 md:hidden">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
</button>
|
||||
<div class="relative w-full max-w-md md:ml-0 ml-4">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<svg class="w-5 h-5 text-gray-400" viewBox="0 0 24 24" fill="none"><path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
</span>
|
||||
<input type="text" class="w-full py-2 pl-10 pr-4 text-gray-700 bg-white border rounded-md dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring" placeholder="Recherchée">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
|
||||
<div class="relative">
|
||||
<a href="{{ path('artemis_profils') }}" class="relative z-10 block w-8 h-8 overflow-hidden rounded-full shadow focus:outline-none">
|
||||
<img class="object-cover w-full h-full" src="{{ path('artemis_avatar')}}" alt="{{ app.user.username }}">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="flex-1 p-6 overflow-y-auto">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
|
||||
sidebarToggle.addEventListener('click', function () {
|
||||
sidebar.classList.toggle('-translate-x-full');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
templates/artemis/dashboard.twig
Normal file
1
templates/artemis/dashboard.twig
Normal file
@@ -0,0 +1 @@
|
||||
{% extends 'artemis/base.twig' %}
|
||||
12
templates/artemis/profils.twig
Normal file
12
templates/artemis/profils.twig
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends 'artemis/base.twig' %}
|
||||
|
||||
{% block title %}
|
||||
Mon profils
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<mj-section background-color="#ffffff" padding-bottom="0px">
|
||||
<mj-column>
|
||||
{# Logo mis à jour pour SARL SITECONSEIL #}
|
||||
<mj-image src="{{ absolute_url('assets/logo_siteconseil.png') }}" alt="Logo SARL SITECONSEIL" align="center" width="150px" padding-bottom="20px"></mj-image>
|
||||
<mj-image src="https://mainframe.esy-web.dev/assets/logo_siteconseil.png" alt="Logo SARL SITECONSEIL" align="center" width="150px" padding-bottom="20px"></mj-image>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
|
||||
class AccountResetPasswordRequestTest extends TestCase
|
||||
{
|
||||
public function testEntitySettersAndGetters(): void
|
||||
{
|
||||
$account = new Account(); // Ou crée un mock si la classe est complexe
|
||||
$token = 'reset-token-example';
|
||||
$now = new \DateTimeImmutable();
|
||||
$expires = $now->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());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Account;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class AccountTest extends TestCase
|
||||
{
|
||||
private ValidatorInterface $validator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Corrected line: Use enableAttributeMapping() for PHP attributes
|
||||
$this->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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Mail;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MailTest extends TestCase
|
||||
{
|
||||
public function testMailEntity(): void
|
||||
{
|
||||
$mail = new Mail();
|
||||
|
||||
// Test messageId property
|
||||
$messageId = 'test_message_id_123';
|
||||
$mail->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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\App\EventListener;
|
||||
|
||||
use App\Attribute\Mainframe;
|
||||
use App\EventListener\MainframeAttributeListener;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* Test class for the MainframeAttributeListener.
|
||||
* This test class has been updated to reflect the removal of logger calls
|
||||
* from the MainframeAttributeListener.
|
||||
*/
|
||||
class MainframeAttributeListenerTest extends TestCase
|
||||
{
|
||||
private MainframeAttributeListener $listener;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// No logger mock needed as logger has been removed from the listener
|
||||
$this->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'));
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\EventListener;
|
||||
|
||||
use App\EventListener\SitemapSubscriber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Presta\SitemapBundle\Event\SitemapPopulateEvent;
|
||||
use Presta\SitemapBundle\Service\UrlContainerInterface;
|
||||
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
|
||||
|
||||
class SitemapSubscriberTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var UrlGeneratorInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $urlGenerator;
|
||||
|
||||
/**
|
||||
* @var UrlContainerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $urlContainer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Mock the UrlGeneratorInterface
|
||||
$this->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");
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Repository;
|
||||
|
||||
use App\Entity\Account;
|
||||
use App\Repository\AccountRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
|
||||
/**
|
||||
* Test class for AccountRepository.
|
||||
*
|
||||
* This class extends KernelTestCase to allow access to the Symfony container
|
||||
* and test the repository methods with a real database connection (or mock it).
|
||||
*/
|
||||
class AccountRepositoryTest extends KernelTestCase
|
||||
{
|
||||
private ?EntityManagerInterface $entityManager;
|
||||
private ?AccountRepository $accountRepository;
|
||||
|
||||
/**
|
||||
* Sets up the test environment before each test method.
|
||||
*
|
||||
* This method boots the Symfony kernel and retrieves the EntityManager
|
||||
* and AccountRepository from the service container. It also begins a database transaction
|
||||
* to ensure a clean state for each test.
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Boot the Symfony kernel to access services
|
||||
self::bootKernel();
|
||||
|
||||
// Get the EntityManager from the container
|
||||
// Corrected service name from 'doctrine.orm.entity.manager' to 'doctrine.orm.entity_manager'
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Repository;
|
||||
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Repository\AccountResetPasswordRequestRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class AccountResetPasswordRequestRepositoryTest extends KernelTestCase
|
||||
{
|
||||
private ?EntityManagerInterface $entityManager;
|
||||
private ?AccountResetPasswordRequestRepository $accountResetPasswordRequestRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Repository;
|
||||
|
||||
use App\Entity\Mail;
|
||||
use App\Repository\MailRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class MailRepositoryTest extends KernelTestCase
|
||||
{
|
||||
private ?EntityManagerInterface $entityManager;
|
||||
private ?MailRepository $mailRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user