insert([ 'uuid' => $uuid, 'connection' => 'database', 'queue' => 'emails', 'payload' => json_encode([ 'uuid' => $uuid, 'displayName' => $displayName, 'job' => 'Illuminate\Queue\CallQueuedHandler@call', 'data' => [ 'commandName' => 'Illuminate\Mail\SendQueuedMailable', 'command' => serializedMailLeadCommand($leadId), ], ]), 'exception' => 'Too Many Requests', 'failed_at' => now(), ]); return $uuid; } /** * Insert a fake failed VerifyUserLeadEmailNotification job, referencing the given lead UUID. */ function insertFailedNotificationJob(string $leadId): string { $uuid = (string) Str::uuid(); DB::table('failed_jobs')->insert([ 'uuid' => $uuid, 'connection' => 'database', 'queue' => 'emails', 'payload' => json_encode([ 'uuid' => $uuid, 'displayName' => 'App\Notifications\VerifyUserLeadEmailNotification', 'job' => 'Illuminate\Queue\CallQueuedHandler@call', 'data' => [ 'commandName' => 'Illuminate\Notifications\SendQueuedNotifications', 'command' => serializedNotificationLeadCommand($leadId), ], ]), 'exception' => 'Too Many Requests', 'failed_at' => now(), ]); return $uuid; } test('leads:retry-failed-jobs does nothing when no failed jobs exist', function () { artisan('leads:retry-failed-jobs') ->expectsOutputToContain('No failed email jobs found.') ->assertSuccessful(); }); test('leads:retry-failed-jobs forgets jobs for deleted leads', function () { $deletedLeadId = (string) Str::uuid(); insertFailedMailJob('App\Mail\WaitlistWelcome', $deletedLeadId); artisan('leads:retry-failed-jobs')->assertSuccessful(); expect(DB::table('failed_jobs')->count())->toBe(0); }); test('leads:retry-failed-jobs retries jobs for verified leads', function () { $lead = UserLead::factory()->create(); insertFailedMailJob('App\Mail\WaitlistOvertaken', $lead->id); artisan('leads:retry-failed-jobs')->assertSuccessful(); expect(DB::table('failed_jobs')->count())->toBe(0); }); test('leads:retry-failed-jobs forgets waitlist jobs for unverified leads', function () { $lead = UserLead::factory()->unverified()->create(); insertFailedMailJob('App\Mail\WaitlistOvertaken', $lead->id); artisan('leads:retry-failed-jobs')->assertSuccessful(); expect(DB::table('failed_jobs')->count())->toBe(0); }); test('leads:retry-failed-jobs retries VerifyUserLeadEmailNotification for unverified leads', function () { $lead = UserLead::factory()->unverified()->create(); insertFailedNotificationJob($lead->id); artisan('leads:retry-failed-jobs')->assertSuccessful(); expect(DB::table('failed_jobs')->count())->toBe(0); }); test('leads:retry-failed-jobs handles a mix of job types correctly', function () { $verifiedLead = UserLead::factory()->create(); $unverifiedLead = UserLead::factory()->unverified()->create(); $deletedLeadId = (string) Str::uuid(); insertFailedMailJob('App\Mail\WaitlistOvertaken', $verifiedLead->id); // retry insertFailedMailJob('App\Mail\WaitlistReferralNotification', $verifiedLead->id); // retry insertFailedMailJob('App\Mail\WaitlistWelcome', $deletedLeadId); // forget insertFailedMailJob('App\Mail\WaitlistOvertaken', $unverifiedLead->id); // forget insertFailedNotificationJob($unverifiedLead->id); // retry artisan('leads:retry-failed-jobs')->assertSuccessful(); expect(DB::table('failed_jobs')->count())->toBe(0); }); test('leads:retry-failed-jobs --dry-run does not modify failed_jobs', function () { $lead = UserLead::factory()->create(); $deletedLeadId = (string) Str::uuid(); insertFailedMailJob('App\Mail\WaitlistOvertaken', $lead->id); insertFailedMailJob('App\Mail\WaitlistWelcome', $deletedLeadId); artisan('leads:retry-failed-jobs', ['--dry-run' => true]) ->expectsOutputToContain('DRY RUN') ->assertSuccessful(); // Nothing should be touched in dry-run mode expect(DB::table('failed_jobs')->count())->toBe(2); });