feat(mail) : migrate Mail integration into module (back)

LST-67 (2.5) backend. Behaviour-preserving move of the IMAP mail integration
into src/Module/Mail/. All /api/mail/* routes, securities (ROLE_CLIENT still
excluded via MailAccessChecker) and the async sync are unchanged.

- 4 entities + 4 repositories (Domain interfaces + Doctrine impls, bound).
  TaskMailLink.task now references TaskInterface (contract) instead of the
  concrete PM Task. Link/unlink/list-mails controllers load tasks via
  TaskRepositoryInterface; MailCreateTaskController keeps the concrete Task
  (instantiation) — documented Mail->PM coupling.
- Domain (MailProviderInterface, exception), Application (5 DTOs, MailSyncService,
  MailSyncRequested message + handler), Infrastructure (ImapMailProvider +
  MimeHeaderDecoder, MailAccessChecker, 2 console commands, 12 controllers,
  ApiPlatform state + MailSettings resource). TokenEncryptor stays shared.
- doctrine mapping Mail; messenger routing repointed; services.yaml repo +
  provider bindings; MailModule registered (id mail, mail.access/configure).
- #[Auditable] + Timestampable on MailConfiguration only (additive migration);
  IMAP data entities keep their own sync timestamps.

163 tests green, mapping valid, no route regression, cs-fixer clean.
This commit is contained in:
Matthieu
2026-06-20 19:44:19 +02:00
parent 57ccd9a740
commit 25d3a693f9
55 changed files with 453 additions and 209 deletions
+2
View File
@@ -10,6 +10,7 @@ declare(strict_types=1);
use App\Module\Absence\AbsenceModule;
use App\Module\Core\CoreModule;
use App\Module\Directory\DirectoryModule;
use App\Module\Mail\MailModule;
use App\Module\ProjectManagement\ProjectManagementModule;
use App\Module\TimeTracking\TimeTrackingModule;
@@ -19,4 +20,5 @@ return [
ProjectManagementModule::class,
AbsenceModule::class,
DirectoryModule::class,
MailModule::class,
];
+5
View File
@@ -58,6 +58,11 @@ doctrine:
is_bundle: false
dir: '%kernel.project_dir%/src/Module/Directory/Domain/Entity'
prefix: 'App\Module\Directory\Domain\Entity'
Mail:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Module/Mail/Domain/Entity'
prefix: 'App\Module\Mail\Domain\Entity'
controller_resolver:
auto_mapping: false
+1 -1
View File
@@ -23,7 +23,7 @@ framework:
# messenger:consume à maintenir. La sync de fond reste assurée par le cron OS
# (app:mail:sync, synchrone, indépendant du bus). Repasser à `async` + worker si
# la boîte grossit au point que la sync à la demande approche le timeout PHP.
'App\Message\MailSyncRequested': sync
'App\Module\Mail\Application\Message\MailSyncRequested': sync
when@test:
framework:
+10
View File
@@ -103,4 +103,14 @@ services:
App\Module\Directory\Domain\Repository\ProspectRepositoryInterface: '@App\Module\Directory\Infrastructure\Doctrine\DoctrineProspectRepository'
App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineMailConfigurationRepository'
App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineMailFolderRepository'
App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineMailMessageRepository'
App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineTaskMailLinkRepository'
App\Module\Mail\Domain\Provider\MailProviderInterface: '@App\Module\Mail\Infrastructure\Imap\ImapMailProvider'
App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier'
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Mail module: add Timestampable + Blamable tracking on mail_configuration.
*
* Purely additive — adds nullable audit columns to an existing table:
* created_at / updated_at (Timestampable)
* created_by / updated_by -> "user"(id) ON DELETE SET NULL (Blamable)
* No DROP/ALTER on existing data. Columns are lowercase snake_case.
* Hand-written to mirror the schema dump and guarantee zero destructive
* instruction. down() drops the new columns and their FKs/indexes.
*/
final class Version20260620200000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Mail: add Timestampable/Blamable columns on mail_configuration (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE mail_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD CONSTRAINT FK_BFC0A7DBDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE mail_configuration ADD CONSTRAINT FK_BFC0A7DB16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_BFC0A7DBDE12AB56 ON mail_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_BFC0A7DB16FE72E1 ON mail_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN mail_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE mail_configuration DROP CONSTRAINT FK_BFC0A7DBDE12AB56');
$this->addSql('ALTER TABLE mail_configuration DROP CONSTRAINT FK_BFC0A7DB16FE72E1');
$this->addSql('DROP INDEX IDX_BFC0A7DBDE12AB56');
$this->addSql('DROP INDEX IDX_BFC0A7DB16FE72E1');
$this->addSql('ALTER TABLE mail_configuration DROP created_at');
$this->addSql('ALTER TABLE mail_configuration DROP updated_at');
$this->addSql('ALTER TABLE mail_configuration DROP created_by');
$this->addSql('ALTER TABLE mail_configuration DROP updated_by');
}
}
+1 -1
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\DataFixtures;
use App\Entity\MailConfiguration;
use App\Entity\ZimbraConfiguration;
use App\Enum\ContractType;
use App\Module\Absence\Domain\Entity\AbsenceBalance;
@@ -17,6 +16,7 @@ use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\Directory\Domain\Entity\Prospect;
use App\Module\Directory\Domain\Enum\ProspectStatus;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
final readonly class MailAttachmentDto
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
final readonly class MailFolderDto
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
final readonly class MailMessageDetailDto
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
use DateTimeImmutable;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
use DateTimeImmutable;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Message;
namespace App\Module\Mail\Application\Message;
final readonly class MailSyncRequested
{
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\MessageHandler;
namespace App\Module\Mail\Application\MessageHandler;
use App\Message\MailSyncRequested;
use App\Repository\MailFolderRepository;
use App\Service\MailSyncService;
use App\Module\Mail\Application\Message\MailSyncRequested;
use App\Module\Mail\Application\Service\MailSyncService;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Throwable;
@@ -16,7 +16,7 @@ final readonly class MailSyncRequestedHandler
{
public function __construct(
private MailSyncService $mailSyncService,
private MailFolderRepository $folderRepository,
private MailFolderRepositoryInterface $folderRepository,
private LoggerInterface $logger,
) {}
@@ -2,16 +2,16 @@
declare(strict_types=1);
namespace App\Service;
namespace App\Module\Mail\Application\Service;
use App\Entity\MailFolder;
use App\Entity\MailMessage;
use App\Mail\Dto\MailSyncReport;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailConfigurationRepository;
use App\Repository\MailFolderRepository;
use App\Repository\MailMessageRepository;
use App\Module\Mail\Application\Dto\MailSyncReport;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
@@ -27,9 +27,9 @@ final class MailSyncService
public function __construct(
private readonly MailProviderInterface $provider,
private readonly MailConfigurationRepository $configRepository,
private readonly MailFolderRepository $folderRepository,
private readonly MailMessageRepository $messageRepository,
private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $entityManager,
private readonly LockFactory $lockFactory,
private readonly LoggerInterface $logger,
@@ -2,15 +2,22 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailConfigurationRepository;
use App\Shared\Domain\Attribute\Auditable;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailConfigurationRepository::class)]
#[Auditable]
#[ORM\Entity(repositoryClass: DoctrineMailConfigurationRepository::class)]
#[ORM\Table(name: 'mail_configuration')]
class MailConfiguration
class MailConfiguration implements TimestampableInterface, BlamableInterface
{
use TimestampableBlamableTrait;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Repository\MailFolderRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailFolderRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailFolderRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineMailFolderRepository::class)]
#[ORM\Table(name: 'mail_folder')]
#[ORM\Index(columns: ['parent_path'], name: 'idx_mail_folder_parent_path')]
class MailFolder
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Repository\MailMessageRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailMessageRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailMessageRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineMailMessageRepository::class)]
#[ORM\Table(name: 'mail_message')]
#[ORM\UniqueConstraint(name: 'uq_mail_message_folder_uid', columns: ['folder_id', 'uid'])]
#[ORM\Index(columns: ['sent_at'], name: 'idx_mail_message_sent_at')]
@@ -2,15 +2,15 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskMailLinkRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineTaskMailLinkRepository;
use App\Shared\Domain\Contract\TaskInterface;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TaskMailLinkRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskMailLinkRepository::class)]
#[ORM\Table(name: 'task_mail_link')]
#[ORM\UniqueConstraint(name: 'uq_task_mail_link', columns: ['task_id', 'mail_message_id'])]
class TaskMailLink
@@ -20,9 +20,9 @@ class TaskMailLink
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Task::class)]
#[ORM\ManyToOne(targetEntity: TaskInterface::class)]
#[ORM\JoinColumn(name: 'task_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private Task $task;
private TaskInterface $task;
#[ORM\ManyToOne(targetEntity: MailMessage::class)]
#[ORM\JoinColumn(name: 'mail_message_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
@@ -40,12 +40,12 @@ class TaskMailLink
return $this->id;
}
public function getTask(): Task
public function getTask(): TaskInterface
{
return $this->task;
}
public function setTask(Task $task): static
public function setTask(TaskInterface $task): static
{
$this->task = $task;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Exception;
namespace App\Module\Mail\Domain\Exception;
use RuntimeException;
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Mail;
namespace App\Module\Mail\Domain\Provider;
use App\Mail\Dto\MailFolderDto;
use App\Mail\Dto\MailMessageDetailDto;
use App\Mail\Dto\MailMessageHeaderDto;
use App\Mail\Exception\MailProviderException;
use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Module\Mail\Application\Dto\MailMessageDetailDto;
use App\Module\Mail\Application\Dto\MailMessageHeaderDto;
use App\Module\Mail\Domain\Exception\MailProviderException;
interface MailProviderInterface
{
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailConfiguration;
interface MailConfigurationRepositoryInterface
{
public function findSingleton(): ?MailConfiguration;
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailFolder;
interface MailFolderRepositoryInterface
{
/**
* @return list<MailFolder>
*/
public function findAllOrderedByPath(): array;
public function findByPath(string $path): ?MailFolder;
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
interface MailMessageRepositoryInterface
{
public function findById(int $id): ?MailMessage;
/**
* @return list<MailMessage>
*/
public function findAll(): array;
public function findByMessageId(string $messageId): ?MailMessage;
public function findByFolderAndUid(MailFolder $folder, int $uid): ?MailMessage;
/**
* @return list<MailMessage>
*/
public function findByFolderPaginated(MailFolder $folder, int $limit, int $offset): array;
public function countUnreadByFolder(MailFolder $folder): int;
public function findMaxUidInFolder(MailFolder $folder): int;
/**
* @return list<MailMessage>
*/
public function findLastNByFolder(MailFolder $folder, int $limit): array;
/**
* @return list<int>
*/
public function findAllUidsByFolder(MailFolder $folder): array;
/**
* Pagination cursor : retourne $limit messages apres le cursor (sentAt DESC, id DESC).
* Cursor format : base64url(sentAt_iso8601:id) - null pour la premiere page.
*
* @return array{messages: list<MailMessage>, nextCursor: ?string}
*/
public function findByFolderCursor(MailFolder $folder, int $limit, ?string $cursor): array;
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Shared\Domain\Contract\TaskInterface;
interface TaskMailLinkRepositoryInterface
{
/**
* @return list<TaskMailLink>
*/
public function findByTask(TaskInterface $task): array;
public function findByTaskAndMessage(TaskInterface $task, MailMessage $message): ?TaskMailLink;
/**
* @return list<TaskMailLink>
*/
public function findByMessage(MailMessage $message): array;
}
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\ApiResource;
namespace App\Module\Mail\Infrastructure\ApiPlatform\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Patch;
use App\State\Mail\MailSettingsProcessor;
use App\State\Mail\MailSettingsProvider;
use App\Module\Mail\Infrastructure\ApiPlatform\State\MailSettingsProcessor;
use App\Module\Mail\Infrastructure\ApiPlatform\State\MailSettingsProvider;
use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource(
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\State\Mail;
namespace App\Module\Mail\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\ApiResource\MailSettings;
use App\Entity\MailConfiguration;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Infrastructure\ApiPlatform\Resource\MailSettings;
use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface;
@@ -16,7 +16,7 @@ final readonly class MailSettingsProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private MailConfigurationRepository $configRepository,
private MailConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor,
) {}
@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace App\State\Mail;
namespace App\Module\Mail\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\MailSettings;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Infrastructure\ApiPlatform\Resource\MailSettings;
final readonly class MailSettingsProvider implements ProviderInterface
{
public function __construct(
private MailConfigurationRepository $configRepository,
private MailConfigurationRepositoryInterface $configRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): MailSettings
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Command;
namespace App\Module\Mail\Infrastructure\Console;
use App\Mail\MimeHeaderDecoder;
use App\Repository\MailMessageRepository;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Imap\MimeHeaderDecoder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
@@ -21,7 +21,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class MailRedecodeHeadersCommand extends Command
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $entityManager,
) {
parent::__construct();
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Command;
namespace App\Module\Mail\Infrastructure\Console;
use App\Repository\MailConfigurationRepository;
use App\Repository\MailFolderRepository;
use App\Service\MailSyncService;
use App\Module\Mail\Application\Service\MailSyncService;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -22,8 +22,8 @@ final class MailSyncCommand extends Command
{
public function __construct(
private readonly MailSyncService $mailSyncService,
private readonly MailConfigurationRepository $configRepository,
private readonly MailFolderRepository $folderRepository,
private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
) {
parent::__construct();
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -20,7 +20,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailAttachmentDownloadController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -37,7 +37,7 @@ class MailAttachmentDownloadController extends AbstractController
[$messageDbIdStr, $partNumber] = explode(':', $decoded, 2);
$messageDbId = (int) $messageDbIdStr;
$message = $this->messageRepository->find($messageDbId);
$message = $this->messageRepository->findById($messageDbId);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Entity\TaskMailLink;
use App\Module\Core\Domain\Entity\User;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -28,7 +28,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailCreateTaskController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
private readonly TaskRepositoryInterface $taskRepository,
@@ -38,7 +38,7 @@ class MailCreateTaskController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Repository\MailFolderRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -17,7 +17,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailFoldersListController extends AbstractController
{
public function __construct(
private readonly MailFolderRepository $folderRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\MailMessageRepository;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -24,8 +24,9 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailLinkTaskController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly TaskMailLinkRepository $linkRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -34,7 +35,7 @@ class MailLinkTaskController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -46,7 +47,7 @@ class MailLinkTaskController extends AbstractController
throw new UnprocessableEntityHttpException('taskId is required');
}
$task = $this->em->getRepository(Task::class)->find($taskId);
$task = $this->taskRepository->findById((int) $taskId);
if (null === $task) {
throw new NotFoundHttpException('Task not found');
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -22,7 +22,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageDetailController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly MailAccessChecker $accessChecker,
private readonly CacheItemPoolInterface $cache,
@@ -32,7 +32,7 @@ class MailMessageDetailController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,7 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageFlagController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
@@ -31,7 +31,7 @@ class MailMessageFlagController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,7 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageReadController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
@@ -31,7 +31,7 @@ class MailMessageReadController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Repository\MailFolderRepository;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -20,8 +20,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessagesListController extends AbstractController
{
public function __construct(
private readonly MailFolderRepository $folderRepository,
private readonly MailMessageRepository $messageRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Message\MailSyncRequested;
use App\Security\MailAccessChecker;
use App\Module\Mail\Application\Message\MailSyncRequested;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\MailMessageRepository;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,8 +21,9 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailUnlinkTaskController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly TaskMailLinkRepository $linkRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -31,12 +32,12 @@ class MailUnlinkTaskController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
$task = $this->em->getRepository(Task::class)->find($taskId);
$task = $this->taskRepository->findById($taskId);
if (null === $task) {
throw new NotFoundHttpException('Task not found');
}
@@ -2,13 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -20,8 +19,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class TaskMailsListController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly TaskMailLinkRepository $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -29,7 +28,7 @@ class TaskMailsListController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$task = $this->em->getRepository(Task::class)->find($id);
$task = $this->taskRepository->findById($id);
if (null === $task) {
throw new NotFoundHttpException('Task not found');
}
@@ -2,13 +2,17 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailConfiguration;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class MailConfigurationRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<MailConfiguration>
*/
class DoctrineMailConfigurationRepository extends ServiceEntityRepository implements MailConfigurationRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
@@ -2,13 +2,17 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class MailFolderRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<MailFolder>
*/
class DoctrineMailFolderRepository extends ServiceEntityRepository implements MailFolderRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
@@ -2,22 +2,31 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailFolder;
use App\Entity\MailMessage;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class MailMessageRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<MailMessage>
*/
class DoctrineMailMessageRepository extends ServiceEntityRepository implements MailMessageRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, MailMessage::class);
}
public function findById(int $id): ?MailMessage
{
return $this->find($id);
}
public function findByMessageId(string $messageId): ?MailMessage
{
return $this->findOneBy(['messageId' => $messageId]);
@@ -2,15 +2,19 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailMessage;
use App\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Shared\Domain\Contract\TaskInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class TaskMailLinkRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<TaskMailLink>
*/
class DoctrineTaskMailLinkRepository extends ServiceEntityRepository implements TaskMailLinkRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
@@ -20,7 +24,7 @@ class TaskMailLinkRepository extends ServiceEntityRepository
/**
* @return list<TaskMailLink>
*/
public function findByTask(Task $task): array
public function findByTask(TaskInterface $task): array
{
return $this->createQueryBuilder('l')
->andWhere('l.task = :task')
@@ -31,7 +35,7 @@ class TaskMailLinkRepository extends ServiceEntityRepository
;
}
public function findByTaskAndMessage(Task $task, MailMessage $message): ?TaskMailLink
public function findByTaskAndMessage(TaskInterface $task, MailMessage $message): ?TaskMailLink
{
return $this->findOneBy(['task' => $task, 'mailMessage' => $message]);
}
@@ -2,14 +2,15 @@
declare(strict_types=1);
namespace App\Mail;
namespace App\Module\Mail\Infrastructure\Imap;
use App\Mail\Dto\MailAttachmentDto;
use App\Mail\Dto\MailFolderDto;
use App\Mail\Dto\MailMessageDetailDto;
use App\Mail\Dto\MailMessageHeaderDto;
use App\Mail\Exception\MailProviderException;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Application\Dto\MailAttachmentDto;
use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Module\Mail\Application\Dto\MailMessageDetailDto;
use App\Module\Mail\Application\Dto\MailMessageHeaderDto;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Service\TokenEncryptor;
use DateTimeImmutable;
use Psr\Log\LoggerInterface;
@@ -24,7 +25,7 @@ final class ImapMailProvider implements MailProviderInterface
private ?Client $client = null;
public function __construct(
private readonly MailConfigurationRepository $configRepository,
private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly TokenEncryptor $tokenEncryptor,
private readonly LoggerInterface $logger,
) {}
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail;
namespace App\Module\Mail\Infrastructure\Imap;
use const ICONV_MIME_DECODE_CONTINUE_ON_ERROR;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Security;
namespace App\Module\Mail\Infrastructure\Security;
use App\Shared\Domain\Contract\UserInterface as SharedUserInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail;
use App\Shared\Domain\Module\ModuleInterface;
final class MailModule implements ModuleInterface
{
public static function id(): string
{
return 'mail';
}
public static function label(): string
{
return 'Messagerie';
}
public static function isRequired(): bool
{
return false;
}
/**
* Permissions RBAC fin du Module Mail.
*
* Additif : alimente le catalogue RBAC. La sécurité des opérations API
* reste en ROLE_USER/ROLE_ADMIN (non recâblée ici).
*
* @return list<array{code: string, label: string}>
*/
public static function permissions(): array
{
return [
['code' => 'mail.access', 'label' => 'Accéder à la messagerie'],
['code' => 'mail.configure', 'label' => 'Configurer la messagerie'],
];
}
}
@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace App\Tests\Functional\Controller\Mail;
use App\Entity\MailFolder;
use App\Entity\MailMessage;
use App\Module\Core\Domain\Entity\User;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use DateTimeImmutable;
+6 -6
View File
@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace App\Tests\Unit\Mail;
use App\Entity\MailConfiguration;
use App\Mail\Exception\MailProviderException;
use App\Mail\ImapMailProvider;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Infrastructure\Imap\ImapMailProvider;
use App\Service\TokenEncryptor;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
@@ -22,7 +22,7 @@ class ImapMailProviderTest extends TestCase
$config = new MailConfiguration();
$config->setEnabled(false);
$repo = $this->createMock(MailConfigurationRepository::class);
$repo = $this->createMock(MailConfigurationRepositoryInterface::class);
$repo->method('findSingleton')->willReturn($config);
$provider = new ImapMailProvider($repo, $this->makeEncryptor(), new NullLogger());
@@ -33,7 +33,7 @@ class ImapMailProviderTest extends TestCase
public function testThrowsWhenConfigMissing(): void
{
$repo = $this->createMock(MailConfigurationRepository::class);
$repo = $this->createMock(MailConfigurationRepositoryInterface::class);
$repo->method('findSingleton')->willReturn(null);
$provider = new ImapMailProvider($repo, $this->makeEncryptor(), new NullLogger());
+1 -1
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Tests\Unit\Mail;
use App\Mail\Dto\MailSyncReport;
use App\Module\Mail\Application\Dto\MailSyncReport;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
+1 -1
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Tests\Unit\Mail;
use App\Mail\MimeHeaderDecoder;
use App\Module\Mail\Infrastructure\Imap\MimeHeaderDecoder;
use PHPUnit\Framework\TestCase;
/**
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Tests\Unit\Repository;
use App\Entity\MailConfiguration;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailConfigurationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
@@ -14,14 +14,14 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
*/
class MailConfigurationRepositoryTest extends KernelTestCase
{
private MailConfigurationRepository $repository;
private DoctrineMailConfigurationRepository $repository;
private EntityManagerInterface $em;
protected function setUp(): void
{
self::bootKernel();
$container = static::getContainer();
$this->repository = $container->get(MailConfigurationRepository::class);
$this->repository = $container->get(DoctrineMailConfigurationRepository::class);
$this->em = $container->get('doctrine.orm.entity_manager');
$this->em->getConnection()->executeStatement('TRUNCATE TABLE mail_configuration RESTART IDENTITY CASCADE');
}
+20 -20
View File
@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace App\Tests\Unit\Service;
use App\Entity\MailConfiguration;
use App\Entity\MailFolder;
use App\Mail\Dto\MailFolderDto;
use App\Mail\MailProviderInterface;
use App\Repository\MailConfigurationRepository;
use App\Repository\MailFolderRepository;
use App\Repository\MailMessageRepository;
use App\Service\MailSyncService;
use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Module\Mail\Application\Service\MailSyncService;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
@@ -29,12 +29,12 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration();
$config->setEnabled(false);
$configRepo = $this->createMock(MailConfigurationRepository::class);
$configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config);
$provider = $this->createMock(MailProviderInterface::class);
$folderRepo = $this->createMock(MailFolderRepository::class);
$messageRepo = $this->createMock(MailMessageRepository::class);
$folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class);
$lockFactory = $this->makeLockFactory();
@@ -62,12 +62,12 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration();
$config->setEnabled(true);
$configRepo = $this->createMock(MailConfigurationRepository::class);
$configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config);
$provider = $this->createMock(MailProviderInterface::class);
$folderRepo = $this->createMock(MailFolderRepository::class);
$messageRepo = $this->createMock(MailMessageRepository::class);
$folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class);
$lockFactory = $this->makeLockFactory(false);
@@ -93,7 +93,7 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration();
$config->setEnabled(true);
$configRepo = $this->createMock(MailConfigurationRepository::class);
$configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config);
$folderDto = new MailFolderDto(
@@ -107,11 +107,11 @@ class MailSyncServiceTest extends TestCase
$provider = $this->createMock(MailProviderInterface::class);
$provider->method('listFolders')->willReturn([$folderDto]);
$folderRepo = $this->createMock(MailFolderRepository::class);
$folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$folderRepo->method('findByPath')->willReturn(null);
$folderRepo->method('findAllOrderedByPath')->willReturn([]);
$messageRepo = $this->createMock(MailMessageRepository::class);
$messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class);
$em->expects(self::once())->method('persist');
$em->expects(self::once())->method('flush');
@@ -137,13 +137,13 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration();
$config->setEnabled(true);
$configRepo = $this->createMock(MailConfigurationRepository::class);
$configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config);
$folder = new MailFolder();
$folder->setPath('INBOX');
$messageRepo = $this->createMock(MailMessageRepository::class);
$messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$messageRepo->method('findMaxUidInFolder')->willReturn(10);
$messageRepo->method('findAllUidsByFolder')->willReturn([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$messageRepo->method('findLastNByFolder')->willReturn([]);
@@ -151,7 +151,7 @@ class MailSyncServiceTest extends TestCase
$provider = $this->createMock(MailProviderInterface::class);
$provider->method('listMessages')->willReturn([]);
$folderRepo = $this->createMock(MailFolderRepository::class);
$folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class);
$em->expects(self::never())->method('remove');