feat(notification): enhance email logging and refactor event handling to use scalar identifiers
This commit is contained in:
132
tests/Unit/Notification/NotificationMailServiceLoggingTest.php
Normal file
132
tests/Unit/Notification/NotificationMailServiceLoggingTest.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Notification;
|
||||
|
||||
use App\Domains\Integration\Services\MailService;
|
||||
use App\Domains\Notification\Services\NotificationMailService;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Ticket\Services\TicketPdfService;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Mockery;
|
||||
use ReflectionMethod;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationMailServiceLoggingTest extends TestCase
|
||||
{
|
||||
private NotificationMailService $service;
|
||||
|
||||
private MailService $mailService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->mailService = Mockery::mock(MailService::class);
|
||||
$this->service = new NotificationMailService(
|
||||
$this->mailService,
|
||||
Mockery::mock(TicketPdfService::class),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_logs_and_swallows_a_missing_purchase(): void
|
||||
{
|
||||
$this->createEmptyPurchasesTable();
|
||||
|
||||
Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
|
||||
Log::shouldReceive('warning')->once()->with(
|
||||
'Notification email skipped.',
|
||||
[
|
||||
'purchase_id' => 123,
|
||||
'reason' => 'purchase_not_found',
|
||||
'missing_model' => Purchase::class,
|
||||
'email_type' => 'purchase_paid',
|
||||
],
|
||||
);
|
||||
|
||||
$this->service->sendPurchasePaid(123);
|
||||
}
|
||||
|
||||
public function test_missing_purchase_log_includes_the_requested_ticket_ids(): void
|
||||
{
|
||||
$this->createEmptyPurchasesTable();
|
||||
|
||||
Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
|
||||
Log::shouldReceive('warning')->once()->with(
|
||||
'Notification email skipped.',
|
||||
[
|
||||
'purchase_id' => 123,
|
||||
'requested_ticket_count' => 2,
|
||||
'requested_ticket_ids' => [10, 11],
|
||||
'reason' => 'purchase_not_found',
|
||||
'missing_model' => Purchase::class,
|
||||
'email_type' => 'tickets_available',
|
||||
],
|
||||
);
|
||||
|
||||
$this->service->sendTicketsAvailable(123, [10, 11]);
|
||||
}
|
||||
|
||||
public function test_it_logs_successful_delivery_with_the_mailer(): void
|
||||
{
|
||||
$this->mailService->shouldReceive('mailerName')->once()->andReturn('smtp');
|
||||
Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
|
||||
Log::shouldReceive('info')->once()->with(
|
||||
'Notification email sent.',
|
||||
[
|
||||
'user_id' => 10,
|
||||
'tenant_code' => 'tenant-test',
|
||||
'email_type' => 'welcome',
|
||||
'mailer' => 'smtp',
|
||||
],
|
||||
);
|
||||
|
||||
$this->sendLogged(
|
||||
'welcome',
|
||||
['user_id' => 10],
|
||||
fn (): array => ['tenant_code' => 'tenant-test'],
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_logs_and_rethrows_delivery_failures(): void
|
||||
{
|
||||
$exception = new RuntimeException('SMTP unavailable.');
|
||||
Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf();
|
||||
Log::shouldReceive('error')->once()->with(
|
||||
'Notification email delivery failed.',
|
||||
Mockery::on(fn (array $context): bool => $context['purchase_id'] === 123
|
||||
&& $context['email_type'] === 'purchase_paid'
|
||||
&& $context['exception'] === $exception),
|
||||
);
|
||||
|
||||
$this->expectExceptionObject($exception);
|
||||
|
||||
$this->sendLogged('purchase_paid', ['purchase_id' => 123], function () use ($exception): array {
|
||||
throw $exception;
|
||||
});
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $context */
|
||||
private function sendLogged(string $emailType, array $context, callable $send): void
|
||||
{
|
||||
(new ReflectionMethod($this->service, 'sendLogged'))->invoke(
|
||||
$this->service,
|
||||
$emailType,
|
||||
$context,
|
||||
$send,
|
||||
);
|
||||
}
|
||||
|
||||
private function createEmptyPurchasesTable(): void
|
||||
{
|
||||
config([
|
||||
'database.default' => 'sqlite',
|
||||
'database.connections.sqlite.database' => ':memory:',
|
||||
]);
|
||||
Schema::connection('sqlite')->create('compras', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,12 @@ namespace Tests\Unit\Notification;
|
||||
use App\Domains\Notification\Events\PasswordResetRequested;
|
||||
use App\Domains\Notification\Listeners\SendPasswordResetEmail;
|
||||
use App\Domains\Notification\Services\NotificationMailService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SendPasswordResetEmailTest extends TestCase
|
||||
{
|
||||
public function test_it_logs_and_rethrows_mail_failures(): void
|
||||
public function test_it_delegates_mail_failures_to_the_notification_service(): void
|
||||
{
|
||||
$exception = new RuntimeException('SMTP unavailable.');
|
||||
$mailService = \Mockery::mock(NotificationMailService::class);
|
||||
@@ -21,16 +20,6 @@ class SendPasswordResetEmailTest extends TestCase
|
||||
->andThrow($exception);
|
||||
$this->app->instance(NotificationMailService::class, $mailService);
|
||||
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with(
|
||||
'Failed to send password reset email.',
|
||||
\Mockery::on(fn (array $context): bool => $context['attempt_id'] === 10
|
||||
&& $context['tenant_code'] === 'tenant-test'
|
||||
&& $context['channel'] === PasswordResetRequested::CHANNEL_STOREFRONT
|
||||
&& $context['exception'] === $exception),
|
||||
);
|
||||
|
||||
$this->expectExceptionObject($exception);
|
||||
|
||||
(new SendPasswordResetEmail)->handle(
|
||||
|
||||
Reference in New Issue
Block a user