Merge branch 'fixes/TL-1886' into dev

This commit is contained in:
2026-08-26 11:40:21 -03:00
21 changed files with 78 additions and 206 deletions

View File

@@ -96,6 +96,7 @@ class CatalogSchemaTest extends TestCase
$this->assertEqualsCanonicalizing([
'id',
'tenant_code',
'code',
'source_type',
'category_id',
'product_layout',

View File

@@ -142,7 +142,7 @@ class NotificationMailServiceTest extends TestCase
});
}
public function test_it_sends_purchase_and_ticket_emails_to_the_purchase_recipient(): void
public function test_it_sends_one_purchase_confirmation_with_generated_tickets_attached(): void
{
$this->useWebsiteTypeBranding();
$purchase = Purchase::query()->create([
@@ -175,30 +175,23 @@ class NotificationMailServiceTest extends TestCase
$ticket = Ticket::query()->create([
'tenant_code' => $this->tenant->codigo,
'ticket' => fake()->uuid(),
'source_purchase_id' => $purchase->id,
'source_catalog_item_id' => $catalogItem->id,
'user_id' => $this->user->id,
]);
$service = app(NotificationMailService::class);
$service->sendPurchasePaid($purchase->id);
$service->sendTicketsAvailable($purchase->id, [$ticket->id]);
$service->sendPurchaseConfirmed($purchase->id);
Mail::assertSent(Mailable::class, 2);
Mail::assertSent(Mailable::class, function (Mailable $mail) use ($purchase): bool {
$mail->assertTo('checkout@example.com');
return $mail->subject === "Pago confirmado - Compra #{$purchase->id}"
&& str_contains($mail->render(), 'Total pagado')
&& str_contains($mail->render(), 'border-top: 4px solid #112233')
&& ! str_contains($mail->render(), 'border-top: 4px solid #ff7006');
});
Mail::assertSent(Mailable::class, function (Mailable $mail) use ($ticket): bool {
Mail::assertSent(Mailable::class, 1);
Mail::assertSent(Mailable::class, function (Mailable $mail) use ($purchase, $ticket): bool {
$mail->assertTo('checkout@example.com');
$attachment = collect($mail->rawAttachments)->firstWhere('name', "tickets_{$ticket->id}.pdf");
return $mail->subject === 'Tus tickets ya están disponibles'
&& str_contains($mail->render(), 'Entrada general')
&& str_contains($mail->render(), 'El ticket está adjunto')
return $mail->subject === "Compra confirmada - Compra #{$purchase->id}"
&& str_contains($mail->render(), '¡Compra realizada con éxito!')
&& str_contains($mail->render(), 'Total pagado')
&& str_contains($mail->render(), 'Tus tickets ya están disponibles')
&& $attachment !== null
&& $attachment['options'] === ['mime' => 'application/pdf']
&& str_starts_with($attachment['data'], '%PDF-')
@@ -207,6 +200,26 @@ class NotificationMailServiceTest extends TestCase
});
}
public function test_purchase_confirmation_omits_ticket_content_and_attachment_without_tickets(): void
{
$purchase = Purchase::query()->create([
'tenant_codigo' => $this->tenant->codigo,
'user_id' => $this->user->id,
'status' => Purchase::STATUS_PAID,
'payment_method' => 'transfer',
'total' => 25,
]);
app(NotificationMailService::class)->sendPurchaseConfirmed($purchase->id);
Mail::assertSent(Mailable::class, function (Mailable $mail) use ($purchase): bool {
return $mail->subject === "Compra confirmada - Compra #{$purchase->id}"
&& str_contains($mail->render(), '¡Compra realizada con éxito!')
&& ! str_contains($mail->render(), 'Tus tickets ya están disponibles')
&& $mail->rawAttachments === [];
});
}
private function useWebsiteTypeBranding(): void
{
$websiteType = WebsiteType::query()->create([

View File

@@ -2,9 +2,7 @@
namespace Tests\Feature\Notification;
use App\Domains\Notification\Events\TicketsAvailable;
use App\Domains\Notification\Listeners\SendPurchasePaidEmail;
use App\Domains\Notification\Listeners\SendTicketsAvailableEmail;
use App\Domains\Notification\Listeners\SendPurchaseConfirmedEmail;
use App\Domains\Notification\Services\NotificationMailService;
use App\Domains\Purchase\Events\PurchasePaid;
use Mockery;
@@ -12,35 +10,21 @@ use Tests\TestCase;
class QueuedNotificationListenerTest extends TestCase
{
public function test_purchase_paid_email_delegates_with_the_purchase_id(): void
public function test_purchase_confirmed_email_delegates_with_the_purchase_id(): void
{
$mailService = Mockery::mock(NotificationMailService::class);
$mailService->shouldReceive('sendPurchasePaid')
$mailService->shouldReceive('sendPurchaseConfirmed')
->once()
->with(123);
$this->app->instance(NotificationMailService::class, $mailService);
(new SendPurchasePaidEmail)->handle(new PurchasePaid(123));
(new SendPurchaseConfirmedEmail)->handle(new PurchasePaid(123));
}
public function test_tickets_available_email_delegates_with_scalar_identifiers(): void
{
$mailService = Mockery::mock(NotificationMailService::class);
$mailService->shouldReceive('sendTicketsAvailable')
->once()
->with(123, [10, 11]);
$this->app->instance(NotificationMailService::class, $mailService);
(new SendTicketsAvailableEmail)->handle(new TicketsAvailable(123, [10, 11]));
}
public function test_email_events_only_serialize_scalar_identifiers(): void
public function test_purchase_paid_event_only_serializes_the_purchase_id(): void
{
$purchasePaid = unserialize(serialize(new PurchasePaid(123)));
$ticketsAvailable = unserialize(serialize(new TicketsAvailable(123, [10, 11])));
$this->assertSame(123, $purchasePaid->purchaseId);
$this->assertSame(123, $ticketsAvailable->purchaseId);
$this->assertSame([10, 11], $ticketsAvailable->ticketIds);
}
}

View File

@@ -161,6 +161,7 @@ class DesfilePuraTendenciaSeederTest extends TestCase
]);
$this->assertDatabaseHas('featured_groups', [
'tenant_code' => 'desfile_pura_tendencia',
'code' => 'entradas',
'source_type' => 'all',
'product_layout' => 'ticket_selector',
'group_layout' => 'single',

View File

@@ -151,6 +151,7 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
));
$featuredGroup = FeaturedGroup::query()->where('tenant_code', $tenant->codigo)->sole();
$this->assertSame('productos', $featuredGroup->code);
$this->assertSame(FeaturedGroupSource::All, $featuredGroup->source_type);
$this->assertSame(ProductLayout::Row, $featuredGroup->product_layout);
$this->assertSame(GroupLayout::SimpleVertical, $featuredGroup->group_layout);

View File

@@ -69,6 +69,7 @@ class ProductCatalogFromImagesSeederTest extends TestCase
->pluck('id');
$this->assertNotNull($paginatedGroup);
$this->assertSame('productos', $paginatedGroup->code);
$this->assertSame(ProductLayout::ColumnWithImage, $paginatedGroup->product_layout);
$this->assertSame(GroupLayout::Paginated, $paginatedGroup->group_layout);
$this->assertSame(FeaturedGroupSource::All, $paginatedGroup->source_type);
@@ -76,6 +77,7 @@ class ProductCatalogFromImagesSeederTest extends TestCase
$this->assertCount(0, $paginatedGroup->featuredItems);
$this->assertNotNull($carouselGroup);
$this->assertSame('productos-destacados', $carouselGroup->code);
$this->assertSame(ProductLayout::ColumnWithImage, $carouselGroup->product_layout);
$this->assertSame(GroupLayout::Carousel, $carouselGroup->group_layout);
$this->assertSame(FeaturedGroupSource::Manual, $carouselGroup->source_type);

View File

@@ -11,7 +11,6 @@ use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Inventory;
use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Event\Models\EventDate;
use App\Domains\Notification\Events\TicketsAvailable;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Shared\Enums\FieldType;
use App\Domains\Tenant\Models\Tenant;
@@ -22,7 +21,6 @@ use App\Domains\Ticket\Services\TicketGeneratorService;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
use Tests\TestCase;
@@ -220,7 +218,6 @@ class TicketGeneratorServiceTest extends TestCase
public function test_marking_a_purchase_as_paid_generates_its_tickets_once(): void
{
Event::fake([TicketsAvailable::class]);
$item = $this->createTicketableItem('paid-ticket');
$purchase = $this->createPurchase($item, 2);
$purchase->setRelation('items', new EloquentCollection);
@@ -229,7 +226,6 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertSame(Purchase::STATUS_PAID, $purchase->status);
$this->assertDatabaseCount('tickets', 2);
Event::assertDispatchedTimes(TicketsAvailable::class, 1);
$this->actingAs($this->user, 'sanctum')
->getJson("/api/tenants/{$this->tenant->codigo}/compras/{$purchase->id}")
@@ -240,7 +236,6 @@ class TicketGeneratorServiceTest extends TestCase
$purchase->markAsPaid();
$this->assertDatabaseCount('tickets', 2);
Event::assertDispatchedTimes(TicketsAvailable::class, 1);
}
public function test_a_ticket_generated_from_a_purchase_keeps_its_source_ids(): void
@@ -453,7 +448,6 @@ class TicketGeneratorServiceTest extends TestCase
public function test_marking_a_purchase_as_paid_ignores_items_without_tickets(): void
{
Event::fake([TicketsAvailable::class]);
$item = $this->createTicketableItem('regular-product');
$item->update(['has_tickets' => false]);
$purchase = $this->createPurchase($item->fresh(), 1);
@@ -462,7 +456,6 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertSame(Purchase::STATUS_PAID, $purchase->status);
$this->assertDatabaseCount('tickets', 0);
Event::assertNotDispatched(TicketsAvailable::class);
$this->actingAs($this->user, 'sanctum')
->getJson("/api/tenants/{$this->tenant->codigo}/compras/{$purchase->id}")

View File

@@ -42,31 +42,11 @@ class NotificationMailServiceLoggingTest extends TestCase
'purchase_id' => 123,
'reason' => 'purchase_not_found',
'missing_model' => Purchase::class,
'email_type' => 'purchase_paid',
'email_type' => 'purchase_confirmed',
],
);
$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]);
$this->service->sendPurchaseConfirmed(123);
}
public function test_it_logs_successful_delivery_with_the_mailer(): void
@@ -97,13 +77,13 @@ class NotificationMailServiceLoggingTest extends TestCase
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['email_type'] === 'purchase_confirmed'
&& $context['exception'] === $exception),
);
$this->expectExceptionObject($exception);
$this->sendLogged('purchase_paid', ['purchase_id' => 123], function () use ($exception): array {
$this->sendLogged('purchase_confirmed', ['purchase_id' => 123], function () use ($exception): array {
throw $exception;
});
}