bus = $this->createMock(MessageBusInterface::class); $this->unsubscribeManager = $this->createMock(UnsubscribeManager::class); $this->em = $this->createMock(EntityManagerInterface::class); $this->urlGenerator = $this->createMock(UrlGeneratorInterface::class); $this->projectDir = sys_get_temp_dir().'/mailer_test_'.uniqid(); mkdir($this->projectDir.'/public', 0o777, true); mkdir($this->projectDir.'/config/cert', 0o777, true); } protected function tearDown(): void { @unlink($this->projectDir.'/public/key.asc'); @unlink($this->projectDir.'/config/cert/certificate.pem'); @unlink($this->projectDir.'/config/cert/private-key.pem'); @rmdir($this->projectDir.'/config/cert'); @rmdir($this->projectDir.'/config'); @rmdir($this->projectDir.'/public'); @rmdir($this->projectDir); } private function createService(): MailerService { return new MailerService( $this->bus, $this->projectDir, 'passphrase', 'contact@test.com', $this->urlGenerator, $this->unsubscribeManager, $this->em, ); } public function testSendEmailSkipsUnsubscribedRecipient(): void { $this->unsubscribeManager->method('isUnsubscribed')->willReturn(true); $this->bus->expects(self::never())->method('dispatch'); $this->createService()->sendEmail('user@example.com', 'Subject', '
Body
'); } public function testSendEmailDoesNotSkipWhitelistedAddress(): void { $this->unsubscribeManager->method('isUnsubscribed')->willReturn(true); $this->urlGenerator->method('generate')->willReturn('https://example.com/track/abc'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $this->createService()->sendEmail('contact@test.com', 'Subject', 'Body
'); } public function testSendEmailDispatchesForNonUnsubscribedUser(): void { $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false); $this->unsubscribeManager->method('generateToken')->willReturn('token123'); $this->urlGenerator->method('generate')->willReturn('https://example.com/url'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $this->createService()->sendEmail('user@example.com', 'Test', 'Content
'); } public function testSendEmailWithoutUnsubscribeHeaders(): void { $this->urlGenerator->method('generate')->willReturn('https://example.com/url'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $this->createService()->sendEmail('user@example.com', 'Test', 'Content
', withUnsubscribe: false); } public function testSendEmailWithReplyTo(): void { $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false); $this->unsubscribeManager->method('generateToken')->willReturn('token'); $this->urlGenerator->method('generate')->willReturn('https://example.com/url'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $this->createService()->sendEmail('user@example.com', 'Test', 'Content
', replyTo: 'reply@example.com'); } public function testSendEmailWithAttachments(): void { $tmpFile = $this->projectDir.'/public/test.txt'; file_put_contents($tmpFile, 'test content'); $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false); $this->unsubscribeManager->method('generateToken')->willReturn('token'); $this->urlGenerator->method('generate')->willReturn('https://example.com/url'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $this->createService()->sendEmail('user@example.com', 'Test', 'Content
', attachments: [ ['path' => $tmpFile, 'name' => 'test.txt'], ]); @unlink($tmpFile); } public function testSendAttachesPublicKey(): void { file_put_contents($this->projectDir.'/public/key.asc', 'fake-pgp-key'); $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false); $this->unsubscribeManager->method('generateToken')->willReturn('token'); $this->urlGenerator->method('generate')->willReturn('https://example.com/url'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $this->createService()->sendEmail('user@example.com', 'Test', 'Content
'); } public function testSendSignsWithSmime(): void { if (!\extension_loaded('openssl')) { self::markTestSkipped('OpenSSL extension required'); } $privKey = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); $csr = openssl_csr_new(['commonName' => 'test'], $privKey); $cert = openssl_csr_sign($csr, null, $privKey, 1); openssl_x509_export($cert, $certPem); openssl_pkey_export($privKey, $keyPem, 'testpass'); file_put_contents($this->projectDir.'/config/cert/certificate.pem', $certPem); file_put_contents($this->projectDir.'/config/cert/private-key.pem', $keyPem); $service = new MailerService( $this->bus, $this->projectDir, 'testpass', 'contact@test.com', $this->urlGenerator, $this->unsubscribeManager, $this->em, ); $this->unsubscribeManager->method('isUnsubscribed')->willReturn(false); $this->unsubscribeManager->method('generateToken')->willReturn('token'); $this->urlGenerator->method('generate')->willReturn('https://example.com/url'); $this->em->expects(self::once())->method('persist'); $this->em->expects(self::once())->method('flush'); $this->bus->expects(self::once())->method('dispatch')->willReturn(new Envelope(new \stdClass())); $service->sendEmail('user@example.com', 'Test', 'Signed
'); } }