mailer = $this->createMock(MailerInterface::class); $this->logger = $this->createMock(LoggerInterface::class); $this->urlGenerator = $this->createMock(UrlGeneratorInterface::class); $this->loggerService = $this->createMock(LoggerService::class); $this->service = new EmailService( $this->mailer, $this->logger, $this->urlGenerator, $this->loggerService ); } /** * Helper to set private ID property on entities. */ private function setEntityId(object $entity, int $id): void { $reflection = new \ReflectionClass($entity); if ($reflection->hasProperty('id')) { $property = $reflection->getProperty('id'); // $property->setAccessible(true); // Uncomment for PHP < 8.1 $property->setValue($entity, $id); } } // ========================================== // TEST: sendPasswordSetupEmail // ========================================== public function testSendPasswordSetupEmailSuccess(): void { // 1. Setup Data $user = new User(); $this->setEntityId($user, 10); $user->setEmail('new@user.com'); // Token format: "o{OrgId}@{RandomHex}" // We use "o50@abcdef" to test that Org ID 50 is correctly extracted $token = 'o50@abcdef123456'; // 2. Expect URL Generation $this->urlGenerator->expects($this->once()) ->method('generate') ->with( 'password_setup', ['id' => 10, 'token' => $token], UrlGeneratorInterface::ABSOLUTE_URL ) ->willReturn('https://sudalys.fr/setup/10/token'); // 3. Expect Mailer Send $this->mailer->expects($this->once()) ->method('send') ->with($this->callback(function (TemplatedEmail $email) use ($user, $token) { // Verify Email Construction $context = $email->getContext(); return $email->getTo()[0]->getAddress() === 'new@user.com' && $email->getSubject() === 'Définissez votre mot de passe' && $email->getHtmlTemplate() === 'emails/password_setup.html.twig' && $context['user'] === $user && $context['token'] === $token && $context['linkUrl'] === 'https://sudalys.fr/setup/10/token'; })); // 4. Expect Business Log (Success) // Ensure the Org ID '50' was extracted from the token 'o50@...' $this->loggerService->expects($this->once()) ->method('logEmailSent') ->with(10, 50, 'Password setup email sent.'); // 5. Run $this->service->sendPasswordSetupEmail($user, $token); } public function testSendPasswordSetupEmailWithoutOrgIdInToken(): void { $user = new User(); $this->setEntityId($user, 10); $user->setEmail('user@test.com'); // Token WITHOUT 'o' prefix -> Org ID should be null $token = 'abcdef123456'; $this->urlGenerator->method('generate')->willReturn('https://link.com'); // Verify log receives null for Org ID $this->loggerService->expects($this->once()) ->method('logEmailSent') ->with(10, null, 'Password setup email sent.'); $this->service->sendPasswordSetupEmail($user, $token); } public function testSendPasswordSetupEmailHandlesException(): void { $user = new User(); $this->setEntityId($user, 10); $user->setEmail('fail@test.com'); $token = 'token'; $this->urlGenerator->method('generate')->willReturn('http://link'); // Simulate Mailer Failure $this->mailer->expects($this->once()) ->method('send') ->willThrowException(new TransportException('SMTP Error')); // Expect System Error Log $this->logger->expects($this->once()) ->method('error') ->with($this->stringContains('Failed to send password setup email: SMTP Error')); // Ensure business log is NOT called (or called depending on where failure happens, // in your code business log is AFTER mailer, so it should NOT be called) $this->loggerService->expects($this->never())->method('logEmailSent'); // No exception should bubble up (caught in catch block) $this->service->sendPasswordSetupEmail($user, $token); } // ========================================== // TEST: sendExistingUserNotificationEmail // ========================================== public function testSendExistingUserNotificationEmailSuccess(): void { // 1. Setup Data $user = new User(); $this->setEntityId($user, 20); $user->setEmail('existing@user.com'); $org = new Organizations(); $this->setEntityId($org, 99); $org->setName('My Organization'); $token = 'some-token'; // 2. Expect URL Generation $this->urlGenerator->expects($this->once()) ->method('generate') ->with( 'user_accept', ['id' => 20, 'token' => $token], UrlGeneratorInterface::ABSOLUTE_URL ) ->willReturn('https://sudalys.fr/accept/20'); // 3. Expect Mailer Send $this->mailer->expects($this->once()) ->method('send') ->with($this->callback(function (TemplatedEmail $email) use ($org) { return $email->getTo()[0]->getAddress() === 'existing@user.com' && $email->getSubject() === "Invitation à rejoindre l'organisation My Organization" && $email->getContext()['expirationDays'] === 15; })); // 4. Expect Business Log $this->loggerService->expects($this->once()) ->method('logEmailSent') ->with(20, 99, 'Existing user notification email sent.'); // 5. Run $this->service->sendExistingUserNotificationEmail($user, $org, $token); } public function testSendExistingUserNotificationEmailHandlesException(): void { $user = new User(); $this->setEntityId($user, 20); $user->setEmail('fail@user.com'); $org = new Organizations(); $this->setEntityId($org, 99); $this->urlGenerator->method('generate')->willReturn('link'); // In this specific method, your code logs success BEFORE sending email? // Looking at source: // $this->loggerService->logEmailSent(...); // $this->mailer->send($email); // So we expect logEmailSent to be called even if mailer fails $this->loggerService->expects($this->once())->method('logEmailSent'); $this->mailer->method('send') ->willThrowException(new TransportException('Connection refused')); // Expect System Error Log $this->logger->expects($this->once()) ->method('error') ->with($this->stringContains('Failed to send existing user notification email')); $this->service->sendExistingUserNotificationEmail($user, $org, 'token'); } }