# Test Update Hello {{ $user->name }}, This is a test update email. Thanks, Victor BLADE; File::put( resource_path('views/mail/updates/test-update.blade.php'), $testViewContent ); }); afterEach(function () { $testViewPath = resource_path('views/mail/updates/test-update.blade.php'); if (File::exists($testViewPath)) { File::delete($testViewPath); } }); test('command dispatches jobs for all users', function () { $users = User::factory()->count(3)->create(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--force' => true, ])->assertSuccessful(); Queue::assertPushed(SendUpdateEmailJob::class, 3); foreach ($users as $user) { Queue::assertPushed(SendUpdateEmailJob::class, function ($job) use ($user) { return $job->user->id === $user->id && $job->viewName === 'test-update' && str_starts_with($job->emailIdentifier, 'test-2026_force_') && $job->subject === 'Update from Whisper Money'; }); } }); test('command skips deleted users', function () { $activeUser = User::factory()->create(); $deletedUser = User::factory()->create(); $deletedUser->delete(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--force' => true, ])->assertSuccessful(); Queue::assertPushed(SendUpdateEmailJob::class, function ($job) use ($activeUser) { return $job->user->id === $activeUser->id; }); Queue::assertNotPushed(SendUpdateEmailJob::class, function ($job) use ($deletedUser) { return $job->user->id === $deletedUser->id; }); Queue::assertCount(1); }); test('command excludes demo account when flag is set', function () { $regularUser = User::factory()->create(); $demoUser = User::factory()->create(['email' => config('app.demo.email')]); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--exclude-demo' => true, '--force' => true, ])->assertSuccessful(); Queue::assertPushed(SendUpdateEmailJob::class, function ($job) use ($regularUser) { return $job->user->id === $regularUser->id; }); Queue::assertNotPushed(SendUpdateEmailJob::class, function ($job) use ($demoUser) { return $job->user->id === $demoUser->id; }); Queue::assertCount(1); }); test('command fails when view does not exist', function () { User::factory()->create(); artisan('email:update', [ 'view' => 'non-existent-view', 'identifier' => 'test-2026', '--force' => true, ])->assertFailed(); Queue::assertNothingPushed(); }); test('command uses custom subject when provided', function () { User::factory()->create(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--subject' => 'Custom Subject Here', '--force' => true, ])->assertSuccessful(); Queue::assertPushed(SendUpdateEmailJob::class, function ($job) { return $job->subject === 'Custom Subject Here'; }); }); test('job is dispatched to emails queue', function () { User::factory()->create(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--force' => true, ])->assertSuccessful(); Queue::assertPushedOn('emails', SendUpdateEmailJob::class); }); test('command handles empty user database gracefully', function () { artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--force' => true, ])->assertSuccessful(); Queue::assertNothingPushed(); }); test('job skips users who already received the update', function () { Queue::fake([]); $user = User::factory()->create(); UserMailLog::create([ 'user_id' => $user->id, 'email_type' => DripEmailType::Update, 'email_identifier' => 'test-2026', 'sent_at' => now(), ]); $job = new SendUpdateEmailJob($user, 'test-update', 'test-2026'); $job->handle(); expect(UserMailLog::where('user_id', $user->id)->count())->toBe(1); }); test('job creates mail log entry after sending', function () { Queue::fake([]); $user = User::factory()->create(); expect(UserMailLog::where('user_id', $user->id)->exists())->toBeFalse(); $job = new SendUpdateEmailJob($user, 'test-update', 'test-2026'); $job->handle(); expect(UserMailLog::where('user_id', $user->id) ->where('email_type', DripEmailType::Update) ->where('email_identifier', 'test-2026') ->exists())->toBeTrue(); }); test('job sends email with correct view and user data', function () { Queue::fake([]); $user = User::factory()->create(['name' => 'John Doe']); $job = new SendUpdateEmailJob($user, 'test-update', 'test-2026', 'Custom Subject'); $job->handle(); expect(UserMailLog::where('user_id', $user->id)->exists())->toBeTrue(); }); test('command requires confirmation by default', function () { User::factory()->create(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', ])->expectsConfirmation("About to send 'test-2026' email to 1 user(s). Continue?", 'no') ->assertSuccessful(); Queue::assertNothingPushed(); }); test('command skips confirmation with force flag', function () { User::factory()->create(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--force' => true, ])->doesntExpectOutput('Continue?') ->assertSuccessful(); Queue::assertPushed(SendUpdateEmailJob::class, 1); }); test('command applies rate limiting delay for large batches', function () { // Create 150 users to test rate limiting across 3 days User::factory()->count(150)->create(); artisan('email:update', [ 'view' => 'test-update', 'identifier' => 'test-2026', '--force' => true, ])->assertSuccessful(); Queue::assertPushed(SendUpdateEmailJob::class, 150); // Check that delays are applied correctly // First 50 emails: no delay (day 0) // Next 50 emails: 1 day delay (day 1) // Last 50 emails: 2 days delay (day 2) $pushedJobs = Queue::pushedJobs()[SendUpdateEmailJob::class] ?? []; expect($pushedJobs)->toHaveCount(150); // Verify delays are applied correctly // Note: We can't easily check the exact delay value in the test, // but we can verify all jobs were pushed expect($pushedJobs)->toHaveCount(150); });