diff --git a/src/Watchers/JobWatcher.php b/src/Watchers/JobWatcher.php index bdb7a93e1..b6d05063c 100644 --- a/src/Watchers/JobWatcher.php +++ b/src/Watchers/JobWatcher.php @@ -4,6 +4,7 @@ use Illuminate\Bus\BatchRepository; use Illuminate\Contracts\Encryption\Encrypter; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Queue\Events\JobFailed; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Queue; @@ -210,25 +211,21 @@ protected function updateBatch($payload) Telescope::$shouldRecord = false; - $command = $this->getCommand($payload['data']); + $batchId = $this->getBatchId($payload['data']); if ($wasRecordingEnabled) { Telescope::$shouldRecord = true; } - $properties = ExtractProperties::from( - $command - ); - - if (isset($properties['batchId'])) { - $batch = app(BatchRepository::class)->find($properties['batchId']); + if (! is_null($batchId)) { + $batch = app(BatchRepository::class)->find($batchId); if (is_null($batch)) { return; } Telescope::recordUpdate(EntryUpdate::make( - $properties['batchId'], EntryType::BATCH, $batch->toArray() + $batchId, EntryType::BATCH, $batch->toArray() )); } } @@ -253,4 +250,29 @@ protected function getCommand(array $data) throw new RuntimeException('Unable to extract job payload.'); } + + /** + * Get the Batch ID from the given payload. + * + * @param array $data + * @return int|null + * + * @throws \RuntimeException + */ + protected function getBatchId(array $data) + { + try { + $command = $this->getCommand($data); + + $properties = ExtractProperties::from($command); + + return $properties['batchId'] ?? null; + } catch (ModelNotFoundException $e) { + if (preg_match('/"batchId";s:\d+:"([^"]+)"/', $data['command'], $matches)) { + return $matches[1]; + } + } + + return null; + } } diff --git a/tests/Watchers/JobWatcherTest.php b/tests/Watchers/JobWatcherTest.php index 534e75383..55f6de440 100644 --- a/tests/Watchers/JobWatcherTest.php +++ b/tests/Watchers/JobWatcherTest.php @@ -6,16 +6,19 @@ use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Database\Schema\Blueprint; +use Illuminate\Foundation\Auth\User; use Illuminate\Queue\Jobs\Job; use Illuminate\Queue\QueueManager; -use Illuminate\Support\Facades\Schema; +use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; use Laravel\Telescope\EntryType; use Laravel\Telescope\Tests\FeatureTestCase; use Laravel\Telescope\Watchers\JobWatcher; +use Orchestra\Testbench\Attributes\WithMigration; +use Orchestra\Testbench\Factories\UserFactory; use Throwable; +#[WithMigration('queue')] class JobWatcherTest extends FeatureTestCase { protected function getEnvironmentSetUp($app) @@ -31,13 +34,6 @@ protected function getEnvironmentSetUp($app) $app->get('config')->set('logging.default', 'syslog'); } - protected function setUp(): void - { - parent::setUp(); - - $this->createJobsTable(); - } - public function test_job_registers_entry() { $this->app->get(Dispatcher::class)->dispatch(new MyDatabaseJob('Awesome Laravel')); @@ -130,31 +126,28 @@ public function test_it_handles_pushed_jobs() $this->assertSame(['framework' => 'Laravel'], $entry->content['data']); } - private function createJobsTable(): void + public function test_job_can_handle_deleted_serialized_model() { - if (! Schema::hasTable('jobs')) { - Schema::create('jobs', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('queue')->index(); - $table->longText('payload'); - $table->unsignedTinyInteger('attempts'); - $table->unsignedInteger('reserved_at')->nullable(); - $table->unsignedInteger('available_at'); - $table->unsignedInteger('created_at'); - }); - } - - if (! Schema::hasTable('failed_jobs')) { - Schema::create('failed_jobs', function (Blueprint $table) { - $table->uuid('uuid'); - $table->bigIncrements('id'); - $table->text('connection'); - $table->text('queue'); - $table->longText('payload'); - $table->longText('exception'); - $table->timestamp('failed_at')->useCurrent(); - }); - } + $user = UserFactory::new()->create(); + + $this->app->get(Dispatcher::class)->dispatch( + new MockedDeleteUserJob($user) + ); + + $this->artisan('queue:work', [ + 'connection' => 'database', + '--once' => true, + ])->run(); + + $entry = $this->loadTelescopeEntries()->first(); + + $this->assertSame(EntryType::JOB, $entry->type); + $this->assertSame('processed', $entry->content['status']); + $this->assertSame('database', $entry->content['connection']); + $this->assertSame(MockedDeleteUserJob::class, $entry->content['name']); + $this->assertSame('default', $entry->content['queue']); + + $this->assertSame(sprintf('%s:%s', get_class($user), $user->getKey()), $entry->content['data']['user']); } } @@ -177,6 +170,27 @@ public function handle() } } +class MockedDeleteUserJob implements ShouldQueue +{ + use SerializesModels; + + public $connection = 'database'; + + public $deleteWhenMissingModels = true; + + public $user; + + public function __construct(User $user) + { + $this->user = $user; + } + + public function handle() + { + $this->user->delete(); + } +} + class MyDatabaseJob implements ShouldQueue { public $connection = 'database';