forked from fa/breadcrumb-the-shire
RateLimiterServiceTest (17): hit/isBlocked/reset/registerFailure, fail-open, sliding window, scope normalization. PermissionServiceTest (24): userHas, getUserPermissions, cache hit/miss/refresh, clearUserCache, CRUD, system permission protection. RoleServiceTest (9): createFromAdmin, updateFromAdmin, deleteByUuid, Admin role protection, duplicate code rejection. TenantServiceTest (8): CRUD, department-dependent deletion blocking. DepartmentServiceTest (14): listPaged scope filtering, groupActiveByTenantIds, createFromAdmin, deleteByUuid, syncTenants. MailServiceTest (8): send logging, MailerException handling, template metadata. UserLifecycleServiceTest (8): advisory locking, deactivation, deletion, snapshot failure skip, privileged user exclusion, manual trigger type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
166 lines
6.3 KiB
PHP
166 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Tests\Service\Mail;
|
|
|
|
use MintyPHP\Repository\Mail\MailLogRepositoryInterface;
|
|
use MintyPHP\Service\Mail\MailService;
|
|
use MintyPHP\Service\Settings\SettingsSmtpGateway;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class MailServiceTest extends TestCase
|
|
{
|
|
private function createSmtpGatewayMock(): SettingsSmtpGateway
|
|
{
|
|
$gateway = $this->createMock(SettingsSmtpGateway::class);
|
|
$gateway->method('getSmtpHost')->willReturn('localhost');
|
|
$gateway->method('getSmtpPort')->willReturn(587);
|
|
$gateway->method('getSmtpUser')->willReturn('');
|
|
$gateway->method('getSmtpPassword')->willReturn('');
|
|
$gateway->method('getSmtpSecure')->willReturn('tls');
|
|
$gateway->method('getSmtpFrom')->willReturn('test@example.com');
|
|
$gateway->method('getSmtpFromName')->willReturn('Test');
|
|
|
|
return $gateway;
|
|
}
|
|
|
|
private function newService(
|
|
?MailLogRepositoryInterface $mailLogRepository = null,
|
|
?SettingsSmtpGateway $settingsSmtpGateway = null,
|
|
): MailService {
|
|
return new MailService(
|
|
$mailLogRepository ?? $this->createMock(MailLogRepositoryInterface::class),
|
|
$settingsSmtpGateway ?? $this->createSmtpGatewayMock(),
|
|
);
|
|
}
|
|
|
|
public function testSendLogsBeforeSending(): void
|
|
{
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->expects($this->once())
|
|
->method('create')
|
|
->with($this->callback(function (array $data): bool {
|
|
return $data['to_email'] === 'user@example.com'
|
|
&& $data['subject'] === 'Hello'
|
|
&& $data['status'] === 'queued';
|
|
}))
|
|
->willReturn(1);
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$service->send('user@example.com', 'Hello', '<p>Hi</p>', 'Hi');
|
|
}
|
|
|
|
public function testSendHandlesMailerException(): void
|
|
{
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->method('create')->willReturn(1);
|
|
$mailLogRepository->expects($this->once())->method('markFailed');
|
|
$mailLogRepository->expects($this->never())->method('markSent');
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$result = $service->send('user@example.com', 'Hello', '<p>Hi</p>', 'Hi');
|
|
|
|
$this->assertFalse($result['ok']);
|
|
$this->assertSame('send_failed', $result['error']);
|
|
}
|
|
|
|
public function testSendHandlesNullLogId(): void
|
|
{
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->method('create')->willReturn(null);
|
|
$mailLogRepository->expects($this->never())->method('markFailed');
|
|
$mailLogRepository->expects($this->never())->method('markSent');
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$result = $service->send('user@example.com', 'Hello', '<p>Hi</p>', 'Hi');
|
|
|
|
$this->assertFalse($result['ok']);
|
|
$this->assertSame('send_failed', $result['error']);
|
|
}
|
|
|
|
public function testSendWithEmptyLogIdStillReturnsError(): void
|
|
{
|
|
// create() returns null (e.g. DB insert failed) — error path still returns a result.
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->method('create')->willReturn(null);
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$result = $service->send('user@example.com', 'Subject', '<p>Body</p>', 'Body');
|
|
|
|
$this->assertArrayHasKey('ok', $result);
|
|
$this->assertArrayHasKey('error', $result);
|
|
$this->assertFalse($result['ok']);
|
|
}
|
|
|
|
public function testSendTemplateCallsSend(): void
|
|
{
|
|
// Templates won't exist in test context, so html/text will be empty strings.
|
|
// But the send path is still exercised, creating a log entry.
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->expects($this->once())
|
|
->method('create')
|
|
->with($this->callback(function (array $data): bool {
|
|
return $data['to_email'] === 'user@example.com'
|
|
&& $data['template'] === 'welcome';
|
|
}))
|
|
->willReturn(2);
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$result = $service->sendTemplate('welcome', ['name' => 'Alice'], 'user@example.com', 'Welcome');
|
|
|
|
$this->assertArrayHasKey('ok', $result);
|
|
}
|
|
|
|
public function testSendCallsMarkFailedWithErrorMessage(): void
|
|
{
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->method('create')->willReturn(5);
|
|
$mailLogRepository->expects($this->once())
|
|
->method('markFailed')
|
|
->with(
|
|
$this->identicalTo(5),
|
|
$this->isType('string'),
|
|
);
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$service->send('user@example.com', 'Test', '<p>Body</p>', 'Body');
|
|
}
|
|
|
|
public function testSendPassesMetaTemplateToLogEntry(): void
|
|
{
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->expects($this->once())
|
|
->method('create')
|
|
->with($this->callback(function (array $data): bool {
|
|
return $data['template'] === 'invoice';
|
|
}))
|
|
->willReturn(3);
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$service->send('user@example.com', 'Invoice', '<p>Invoice</p>', 'Invoice', [
|
|
'template' => 'invoice',
|
|
]);
|
|
}
|
|
|
|
public function testSendWithoutMetaTemplateLogsNullTemplate(): void
|
|
{
|
|
$mailLogRepository = $this->createMock(MailLogRepositoryInterface::class);
|
|
$mailLogRepository->expects($this->once())
|
|
->method('create')
|
|
->with($this->callback(function (array $data): bool {
|
|
return $data['template'] === null;
|
|
}))
|
|
->willReturn(4);
|
|
|
|
$service = $this->newService(mailLogRepository: $mailLogRepository);
|
|
|
|
$service->send('user@example.com', 'Ad hoc', '<p>Ad hoc</p>', 'Ad hoc');
|
|
}
|
|
}
|