Refactor notification event payloads and listener naming
linter / quality (pull_request) Successful in 1m17s
tests / ci (8.4) (pull_request) Failing after 1m35s

This commit is contained in:
2026-04-25 21:54:42 +00:00
parent 20f89952e7
commit 61505ace23
16 changed files with 286 additions and 115 deletions
@@ -2,7 +2,6 @@
namespace App\Actions\Notifications;
use App\Enums\NotificationEventType;
use App\Models\Project;
use App\Models\Ticket;
use App\Models\TicketStatus;
@@ -17,9 +16,8 @@ class BuildTicketCreatedPayload
TicketStatus $ticketStatus,
?User $actor,
CarbonImmutable $occurredAt,
): TicketStatusChangedPayload {
return new TicketStatusChangedPayload(
eventType: NotificationEventType::TicketCreate,
): TicketCreatedPayload {
return new TicketCreatedPayload(
projectId: $project->id,
projectKey: $project->key,
projectName: $project->name,
@@ -29,8 +27,6 @@ class BuildTicketCreatedPayload
ticketUrl: route('projects.tickets.show', [$project, $ticket]),
actorId: $actor?->id,
actorName: $actor?->name,
fromStatusId: null,
fromStatusLabel: null,
toStatusId: $ticketStatus->id,
toStatusLabel: $this->statusLabel($ticketStatus),
occurredAt: $occurredAt,
@@ -2,7 +2,6 @@
namespace App\Actions\Notifications;
use App\Enums\NotificationEventType;
use App\Models\Project;
use App\Models\Ticket;
use App\Models\TicketStatus;
@@ -22,7 +21,6 @@ class BuildTicketStatusChangedPayload
CarbonImmutable $occurredAt,
): TicketStatusChangedPayload {
return new TicketStatusChangedPayload(
eventType: NotificationEventType::TicketStatusChanged,
projectId: $project->id,
projectKey: $project->key,
projectName: $project->name,
@@ -28,7 +28,7 @@ class DeliverNotificationAction implements ShouldQueue
public function __construct(
public int $notificationActionId,
public TicketStatusChangedPayload $payload,
public NotificationEvent $event,
public ?int $notificationDeliveryLogId,
) {}
@@ -60,7 +60,7 @@ class DeliverNotificationAction implements ShouldQueue
if ($notificationAction->channel === NotificationChannel::Discord) {
$discordMessage = app(RenderDiscordMessageTemplate::class)->handle(
$notificationAction->message_template,
$this->payload,
$this->event,
);
$validationError = $this->discordMessageValidationError($discordMessage);
@@ -245,7 +245,7 @@ class DeliverNotificationAction implements ShouldQueue
'attempt_count' => 0,
'channel' => $notificationAction->channel,
'delivery_status' => NotificationDeliveryStatus::Pending,
'event_type' => $this->payload->eventType,
'event_type' => $this->event->payload()->eventType(),
'notification_action_id' => $notificationAction->id,
'payload_metadata' => $this->payloadMetadata(),
]);
@@ -334,7 +334,7 @@ class DeliverNotificationAction implements ShouldQueue
->asJson()
->connectTimeout(5)
->timeout(10)
->post($notificationAction->delivery_url, $this->payload->toArray());
->post($notificationAction->delivery_url, $this->event->payload()->toArray());
}
private function hasAttemptsRemaining(int $attemptCount): bool
@@ -398,14 +398,11 @@ class DeliverNotificationAction implements ShouldQueue
}
/**
* @return array{project_key: string, ticket_key: string}
* @return array<string, scalar|null>
*/
private function payloadMetadata(): array
{
return [
'project_key' => $this->payload->projectKey,
'ticket_key' => $this->payload->ticketKey,
];
return $this->event->payload()->deliveryMetadata();
}
private function excerpt(?string $value): ?string
@@ -0,0 +1,8 @@
<?php
namespace App\Actions\Notifications;
interface NotificationEvent
{
public function payload(): NotificationEventPayload;
}
@@ -0,0 +1,26 @@
<?php
namespace App\Actions\Notifications;
use App\Enums\NotificationEventType;
use Illuminate\Contracts\Support\Arrayable;
/**
* @extends Arrayable<string, mixed>
*/
interface NotificationEventPayload extends Arrayable
{
public function eventType(): NotificationEventType;
public function projectId(): int;
/**
* @return array<string, scalar|null>
*/
public function deliveryMetadata(): array;
/**
* @return array<string, string>
*/
public function discordTemplateVariables(): array;
}
@@ -7,16 +7,18 @@ use App\Enums\NotificationDeliveryStatus;
use App\Models\NotificationAction;
use App\Models\NotificationDeliveryLog;
class QueueTicketStatusChangedNotificationDeliveries
class QueueNotificationEventDeliveries
{
public function handle(TicketStatusChanged|TicketCreated $event): void
public function handle(NotificationEvent $event): void
{
$payload = $event->payload();
NotificationAction::query()
->enabled()
->where('project_id', $event->payload->projectId)
->where('event_type', $event->payload->eventType->value)
->where('project_id', $payload->projectId())
->where('event_type', $payload->eventType()->value)
->get(['channel', 'id'])
->each(function (NotificationAction $notificationAction) use ($event): void {
->each(function (NotificationAction $notificationAction) use ($event, $payload): void {
$notificationDeliveryLogId = null;
if (in_array($notificationAction->channel, [NotificationChannel::Discord, NotificationChannel::Webhook], true)) {
@@ -24,18 +26,15 @@ class QueueTicketStatusChangedNotificationDeliveries
'attempt_count' => 0,
'channel' => $notificationAction->channel,
'delivery_status' => NotificationDeliveryStatus::Pending,
'event_type' => $event->payload->eventType,
'event_type' => $payload->eventType(),
'notification_action_id' => $notificationAction->id,
'payload_metadata' => [
'project_key' => $event->payload->projectKey,
'ticket_key' => $event->payload->ticketKey,
],
'payload_metadata' => $payload->deliveryMetadata(),
])->id;
}
DeliverNotificationAction::dispatch(
notificationActionId: $notificationAction->id,
payload: $event->payload,
event: $event,
notificationDeliveryLogId: $notificationDeliveryLogId,
);
});
@@ -6,16 +6,8 @@ use Illuminate\Support\Str;
class RenderDiscordMessageTemplate
{
public function handle(?string $messageTemplate, TicketStatusChangedPayload $payload): string
public function handle(?string $messageTemplate, NotificationEvent $event): string
{
return strtr((string) Str::of((string) $messageTemplate), [
'$url' => $payload->ticketUrl,
'$ticket_key' => $payload->ticketKey,
'$ticket_title' => $payload->ticketTitle,
'$project_name' => $payload->projectName,
'$status_from' => $payload->fromStatusLabel ?? '',
'$status_to' => $payload->toStatusLabel,
'$actor_name' => $payload->actorName ?? '',
]);
return strtr((string) Str::of((string) $messageTemplate), $event->payload()->discordTemplateVariables());
}
}
+7 -2
View File
@@ -5,11 +5,16 @@ namespace App\Actions\Notifications;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
final readonly class TicketCreated implements ShouldDispatchAfterCommit
final readonly class TicketCreated implements NotificationEvent, ShouldDispatchAfterCommit
{
use Dispatchable;
public function __construct(
public TicketStatusChangedPayload $payload,
public TicketCreatedPayload $payload,
) {}
public function payload(): NotificationEventPayload
{
return $this->payload;
}
}
@@ -0,0 +1,113 @@
<?php
namespace App\Actions\Notifications;
use App\Enums\NotificationEventType;
use Carbon\CarbonImmutable;
final readonly class TicketCreatedPayload implements NotificationEventPayload
{
public NotificationEventType $eventType;
public ?int $fromStatusId;
public ?string $fromStatusLabel;
public function __construct(
public int $projectId,
public string $projectKey,
public string $projectName,
public int $ticketId,
public string $ticketKey,
public string $ticketTitle,
public string $ticketUrl,
public ?int $actorId,
public ?string $actorName,
public int $toStatusId,
public string $toStatusLabel,
public CarbonImmutable $occurredAt,
) {
$this->eventType = NotificationEventType::TicketCreate;
$this->fromStatusId = null;
$this->fromStatusLabel = null;
}
public function eventType(): NotificationEventType
{
return $this->eventType;
}
public function projectId(): int
{
return $this->projectId;
}
/**
* @return array<string, scalar|null>
*/
public function deliveryMetadata(): array
{
return [
'project_key' => $this->projectKey,
'ticket_key' => $this->ticketKey,
];
}
/**
* @return array<string, string>
*/
public function discordTemplateVariables(): array
{
return [
'$url' => $this->ticketUrl,
'$ticket_key' => $this->ticketKey,
'$ticket_title' => $this->ticketTitle,
'$project_name' => $this->projectName,
'$status_from' => '',
'$status_to' => $this->toStatusLabel,
'$actor_name' => $this->actorName ?? '',
];
}
/**
* @return array{
* event_type: string,
* project: array{id: int, key: string, name: string},
* ticket: array{id: int, key: string, title: string},
* actor: array{id: int, name: string}|null,
* from: null,
* to: array{id: int, label: string},
* ticket_url: string,
* occurred_at: string
* }
*/
public function toArray(): array
{
return [
'event_type' => $this->eventType->value,
'project' => [
'id' => $this->projectId,
'key' => $this->projectKey,
'name' => $this->projectName,
],
'ticket' => [
'id' => $this->ticketId,
'key' => $this->ticketKey,
'title' => $this->ticketTitle,
],
'actor' => $this->actorId === null || $this->actorName === null
? null
: [
'id' => $this->actorId,
'name' => $this->actorName,
],
'from' => null,
'to' => [
'id' => $this->toStatusId,
'label' => $this->toStatusLabel,
],
'ticket_url' => $this->ticketUrl,
'occurred_at' => $this->occurredAt->toISOString(),
];
}
}
@@ -5,11 +5,16 @@ namespace App\Actions\Notifications;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
final readonly class TicketStatusChanged implements ShouldDispatchAfterCommit
final readonly class TicketStatusChanged implements NotificationEvent, ShouldDispatchAfterCommit
{
use Dispatchable;
public function __construct(
public TicketStatusChangedPayload $payload,
) {}
public function payload(): NotificationEventPayload
{
return $this->payload;
}
}
@@ -4,15 +4,12 @@ namespace App\Actions\Notifications;
use App\Enums\NotificationEventType;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Support\Arrayable;
/**
* @implements Arrayable<string, mixed>
*/
final readonly class TicketStatusChangedPayload implements Arrayable
final readonly class TicketStatusChangedPayload implements NotificationEventPayload
{
public NotificationEventType $eventType;
public function __construct(
public NotificationEventType $eventType,
public int $projectId,
public string $projectKey,
public string $projectName,
@@ -22,12 +19,51 @@ final readonly class TicketStatusChangedPayload implements Arrayable
public string $ticketUrl,
public ?int $actorId,
public ?string $actorName,
public ?int $fromStatusId,
public ?string $fromStatusLabel,
public int $fromStatusId,
public string $fromStatusLabel,
public int $toStatusId,
public string $toStatusLabel,
public CarbonImmutable $occurredAt,
) {}
) {
$this->eventType = NotificationEventType::TicketStatusChanged;
}
public function eventType(): NotificationEventType
{
return $this->eventType;
}
public function projectId(): int
{
return $this->projectId;
}
/**
* @return array<string, scalar|null>
*/
public function deliveryMetadata(): array
{
return [
'project_key' => $this->projectKey,
'ticket_key' => $this->ticketKey,
];
}
/**
* @return array<string, string>
*/
public function discordTemplateVariables(): array
{
return [
'$url' => $this->ticketUrl,
'$ticket_key' => $this->ticketKey,
'$ticket_title' => $this->ticketTitle,
'$project_name' => $this->projectName,
'$status_from' => $this->fromStatusLabel,
'$status_to' => $this->toStatusLabel,
'$actor_name' => $this->actorName ?? '',
];
}
/**
* @return array{
@@ -35,7 +71,7 @@ final readonly class TicketStatusChangedPayload implements Arrayable
* project: array{id: int, key: string, name: string},
* ticket: array{id: int, key: string, title: string},
* actor: array{id: int, name: string}|null,
* from: array{id: int, label: string}|null,
* from: array{id: int, label: string},
* to: array{id: int, label: string},
* ticket_url: string,
* occurred_at: string
@@ -61,9 +97,7 @@ final readonly class TicketStatusChangedPayload implements Arrayable
'id' => $this->actorId,
'name' => $this->actorName,
],
'from' => $this->fromStatusId === null || $this->fromStatusLabel === null
? null
: [
'from' => [
'id' => $this->fromStatusId,
'label' => $this->fromStatusLabel,
],
+3 -3
View File
@@ -2,7 +2,7 @@
namespace App\Providers;
use App\Actions\Notifications\QueueTicketStatusChangedNotificationDeliveries;
use App\Actions\Notifications\QueueNotificationEventDeliveries;
use App\Actions\Notifications\TicketCreated;
use App\Actions\Notifications\TicketStatusChanged;
use App\Actions\Permissions\PermissionService;
@@ -98,12 +98,12 @@ class AppServiceProvider extends ServiceProvider
{
Event::listen(
TicketStatusChanged::class,
QueueTicketStatusChangedNotificationDeliveries::class,
QueueNotificationEventDeliveries::class,
);
Event::listen(
TicketCreated::class,
QueueTicketStatusChangedNotificationDeliveries::class,
QueueNotificationEventDeliveries::class,
);
}
}
@@ -3,9 +3,9 @@
namespace Tests\Feature\Notifications;
use App\Actions\Notifications\DeliverNotificationAction;
use App\Actions\Notifications\TicketStatusChanged;
use App\Actions\Notifications\TicketStatusChangedPayload;
use App\Enums\NotificationDeliveryStatus;
use App\Enums\NotificationEventType;
use App\Models\NotificationAction;
use App\Models\NotificationDeliveryLog;
use Carbon\CarbonImmutable;
@@ -35,7 +35,7 @@ class DeliverNotificationActionTest extends TestCase
(new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->handle();
@@ -77,7 +77,7 @@ class DeliverNotificationActionTest extends TestCase
(new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->handle();
@@ -162,7 +162,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -200,7 +200,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -237,7 +237,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -274,7 +274,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -310,7 +310,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -349,7 +349,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -385,7 +385,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -413,7 +413,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(actorName: null),
event: $this->event(actorName: null),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -445,7 +445,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(ticketTitle: str_repeat('A', 2001)),
event: $this->event(ticketTitle: str_repeat('A', 2001)),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -482,7 +482,7 @@ class DeliverNotificationActionTest extends TestCase
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
$job = (new DeliverNotificationAction(
notificationActionId: $notificationAction->id,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: $deliveryLog->id,
))->withFakeQueueInteractions();
@@ -508,7 +508,7 @@ class DeliverNotificationActionTest extends TestCase
{
$job = new DeliverNotificationAction(
notificationActionId: 1,
payload: $this->payload(),
event: $this->event(),
notificationDeliveryLogId: 1,
);
@@ -530,12 +530,11 @@ class DeliverNotificationActionTest extends TestCase
]);
}
private function payload(
private function event(
?string $actorName = 'Erica Example',
string $ticketTitle = 'Fix webhook delivery',
): TicketStatusChangedPayload {
return new TicketStatusChangedPayload(
eventType: NotificationEventType::TicketStatusChanged,
): TicketStatusChanged {
return new TicketStatusChanged(new TicketStatusChangedPayload(
projectId: 10,
projectKey: 'CORE',
projectName: 'Core Platform',
@@ -550,7 +549,7 @@ class DeliverNotificationActionTest extends TestCase
toStatusId: 2,
toStatusLabel: 'Doing',
occurredAt: CarbonImmutable::parse('2026-04-24T09:15:30+00:00'),
);
));
}
/**
@@ -3,8 +3,8 @@
namespace Tests\Feature\Notifications;
use App\Actions\Notifications\RenderDiscordMessageTemplate;
use App\Actions\Notifications\TicketStatusChanged;
use App\Actions\Notifications\TicketStatusChangedPayload;
use App\Enums\NotificationEventType;
use Carbon\CarbonImmutable;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
@@ -54,7 +54,7 @@ class RenderDiscordMessageTemplateTest extends TestCase
#[DataProvider('supportedVariableProvider')]
public function test_it_replaces_supported_template_variables(string $template, string $expected): void
{
$renderedMessage = app(RenderDiscordMessageTemplate::class)->handle($template, $this->payload());
$renderedMessage = app(RenderDiscordMessageTemplate::class)->handle($template, $this->event());
$this->assertSame($expected, $renderedMessage);
}
@@ -66,16 +66,15 @@ class RenderDiscordMessageTemplateTest extends TestCase
{
$renderedMessage = app(RenderDiscordMessageTemplate::class)->handle(
'[$ticket_key] $unknown moved to $status_to',
$this->payload(),
$this->event(),
);
$this->assertSame('[CORE-42] $unknown moved to Doing', $renderedMessage);
}
private function payload(): TicketStatusChangedPayload
private function event(): TicketStatusChanged
{
return new TicketStatusChangedPayload(
eventType: NotificationEventType::TicketStatusChanged,
return new TicketStatusChanged(new TicketStatusChangedPayload(
projectId: 10,
projectKey: 'CORE',
projectName: 'Core Platform',
@@ -90,6 +89,6 @@ class RenderDiscordMessageTemplateTest extends TestCase
toStatusId: 2,
toStatusLabel: 'Doing',
occurredAt: CarbonImmutable::parse('2026-04-24T09:15:30+00:00'),
);
));
}
}
@@ -103,24 +103,24 @@ class TicketCreatedNotificationDispatchTest extends TestCase
Queue::assertPushed(DeliverNotificationAction::class, 2);
Queue::assertPushed(DeliverNotificationAction::class, function (DeliverNotificationAction $job) use ($webhookAction, $project, $ticket, $user, $backlogStatus): bool {
return $job->notificationActionId === $webhookAction->id
&& $job->payload->projectId === $project->id
&& $job->payload->projectKey === 'CORE'
&& $job->payload->projectName === 'Core Platform'
&& $job->payload->ticketId === $ticket->id
&& $job->payload->ticketKey === $ticket->ticket_key
&& $job->payload->ticketTitle === 'Trigger ticket created notification'
&& $job->payload->ticketUrl === route('projects.tickets.show', [$project, $ticket])
&& $job->payload->actorId === $user->id
&& $job->payload->actorName === 'Erica Example'
&& $job->payload->fromStatusId === null
&& $job->payload->fromStatusLabel === null
&& $job->payload->toStatusId === $backlogStatus->id
&& $job->payload->toStatusLabel === 'Backlog'
&& $job->payload->eventType === NotificationEventType::TicketCreate
&& $job->event->payload()->projectId === $project->id
&& $job->event->payload()->projectKey === 'CORE'
&& $job->event->payload()->projectName === 'Core Platform'
&& $job->event->payload()->ticketId === $ticket->id
&& $job->event->payload()->ticketKey === $ticket->ticket_key
&& $job->event->payload()->ticketTitle === 'Trigger ticket created notification'
&& $job->event->payload()->ticketUrl === route('projects.tickets.show', [$project, $ticket])
&& $job->event->payload()->actorId === $user->id
&& $job->event->payload()->actorName === 'Erica Example'
&& $job->event->payload()->fromStatusId === null
&& $job->event->payload()->fromStatusLabel === null
&& $job->event->payload()->toStatusId === $backlogStatus->id
&& $job->event->payload()->toStatusLabel === 'Backlog'
&& $job->event->payload()->eventType() === NotificationEventType::TicketCreate
&& $job->notificationDeliveryLogId !== null;
});
Queue::assertPushed(DeliverNotificationAction::class, fn (DeliverNotificationAction $job): bool => $job->notificationActionId === $discordAction->id
&& $job->payload->eventType === NotificationEventType::TicketCreate
&& $job->event->payload()->eventType() === NotificationEventType::TicketCreate
&& $job->notificationDeliveryLogId !== null);
Queue::assertNotPushed(DeliverNotificationAction::class, fn (DeliverNotificationAction $job): bool => $job->notificationActionId === $disabledAction->id);
Queue::assertNotPushed(DeliverNotificationAction::class, fn (DeliverNotificationAction $job): bool => $job->notificationActionId === $statusChangedAction->id);
@@ -104,20 +104,20 @@ class TicketStatusChangedNotificationDispatchTest extends TestCase
Queue::assertPushed(DeliverNotificationAction::class, 2);
Queue::assertPushed(DeliverNotificationAction::class, function (DeliverNotificationAction $job) use ($webhookAction, $project, $ticket, $user, $backlogStatus, $doingStatus): bool {
return $job->notificationActionId === $webhookAction->id
&& $job->payload->projectId === $project->id
&& $job->payload->projectKey === 'CORE'
&& $job->payload->projectName === 'Core Platform'
&& $job->payload->ticketId === $ticket->id
&& $job->payload->ticketKey === 'CORE-1'
&& $job->payload->ticketTitle === 'Fix webhook dispatch'
&& $job->payload->ticketUrl === route('projects.tickets.show', [$project, $ticket])
&& $job->payload->actorId === $user->id
&& $job->payload->actorName === 'Erica Example'
&& $job->payload->fromStatusId === $backlogStatus->id
&& $job->payload->fromStatusLabel === 'Backlog'
&& $job->payload->toStatusId === $doingStatus->id
&& $job->payload->toStatusLabel === 'Doing'
&& $job->payload->eventType === NotificationEventType::TicketStatusChanged
&& $job->event->payload()->projectId === $project->id
&& $job->event->payload()->projectKey === 'CORE'
&& $job->event->payload()->projectName === 'Core Platform'
&& $job->event->payload()->ticketId === $ticket->id
&& $job->event->payload()->ticketKey === 'CORE-1'
&& $job->event->payload()->ticketTitle === 'Fix webhook dispatch'
&& $job->event->payload()->ticketUrl === route('projects.tickets.show', [$project, $ticket])
&& $job->event->payload()->actorId === $user->id
&& $job->event->payload()->actorName === 'Erica Example'
&& $job->event->payload()->fromStatusId === $backlogStatus->id
&& $job->event->payload()->fromStatusLabel === 'Backlog'
&& $job->event->payload()->toStatusId === $doingStatus->id
&& $job->event->payload()->toStatusLabel === 'Doing'
&& $job->event->payload()->eventType() === NotificationEventType::TicketStatusChanged
&& $job->notificationDeliveryLogId !== null;
});
Queue::assertPushed(DeliverNotificationAction::class, fn (DeliverNotificationAction $job): bool => $job->notificationActionId === $discordAction->id