workspace_id; $destination = AlertDestination::factory()->create([ 'workspace_id' => $workspaceId, 'type' => AlertDestination::TYPE_TEAMS_WEBHOOK, 'config' => ['webhook_url' => 'https://example.invalid/hook'], ]); $rule = AlertRule::factory()->create([ 'workspace_id' => $workspaceId, ]); $rule->destinations()->syncWithPivotValues([(int) $destination->getKey()], ['workspace_id' => $workspaceId]); $delivery = AlertDelivery::factory()->create([ 'workspace_id' => $workspaceId, 'tenant_id' => (int) $tenant->getKey(), 'alert_rule_id' => (int) $rule->getKey(), 'alert_destination_id' => (int) $destination->getKey(), 'status' => AlertDelivery::STATUS_QUEUED, 'attempt_count' => 0, 'send_after' => null, ]); $sender = \Mockery::mock(AlertSender::class); $sender->shouldReceive('send') ->times(3) ->andThrow(new RuntimeException('simulated sender failure')); app()->instance(AlertSender::class, $sender); $runJob = static function (int $workspaceId): void { $job = new DeliverAlertsJob($workspaceId); app()->call([$job, 'handle']); }; $runJob($workspaceId); $delivery->refresh(); expect($delivery->attempt_count)->toBe(1); expect($delivery->status)->toBe(AlertDelivery::STATUS_QUEUED); expect($delivery->send_after)->not->toBeNull(); $delivery->forceFill(['send_after' => now()->subSecond()])->save(); $runJob($workspaceId); $delivery->refresh(); expect($delivery->attempt_count)->toBe(2); expect($delivery->status)->toBe(AlertDelivery::STATUS_QUEUED); expect($delivery->send_after)->not->toBeNull(); $delivery->forceFill(['send_after' => now()->subSecond()])->save(); $runJob($workspaceId); $delivery->refresh(); expect($delivery->attempt_count)->toBe(3); expect($delivery->status)->toBe(AlertDelivery::STATUS_FAILED); expect($delivery->last_error_message)->toContain('simulated sender failure'); });