whisper-money/tests/Feature/Commands/SendUserLeadReInvitationsTe...

114 lines
5.0 KiB
PHP

<?php
use App\Mail\UserLeadReInvitation;
use App\Models\User;
use App\Models\UserLead;
use Illuminate\Database\Eloquent\Factories\Sequence;
use Illuminate\Support\Facades\Mail;
beforeEach(function (): void {
Mail::fake();
});
it('sends re-invitations to invited leads who have not signed up', function (): void {
UserLead::factory()->count(3)->state(new Sequence(
['email' => 'first@example.com', 'invitation_sent_at' => now()->subDays(5)],
['email' => 'second@example.com', 'invitation_sent_at' => now()->subDays(4)],
['email' => 'third@example.com', 'invitation_sent_at' => now()->subDays(3)],
))->create();
$this->artisan('leads:send-re-invitations', ['--limit' => 2, '--force' => true])
->assertSuccessful();
Mail::assertQueued(UserLeadReInvitation::class, 2);
expect(UserLead::query()->whereNotNull('re_invitation_sent_at')->count())->toBe(2)
->and(UserLead::query()->whereNotNull('re_invitation_sent_at')->sum('re_invitation_count'))->toBe('2');
$emails = UserLead::query()->whereNotNull('re_invitation_sent_at')->orderBy('invitation_sent_at')->pluck('email')->all();
expect($emails)->toBe(['first@example.com', 'second@example.com']);
});
it('skips leads that already signed up or were already re-invited', function (): void {
UserLead::factory()->create(['email' => 'pending@example.com', 'invitation_sent_at' => now()->subDays(3)]);
UserLead::factory()->create(['email' => 'signed-up@example.com', 'invitation_sent_at' => now()->subDays(3)]);
UserLead::factory()->create([
'email' => 'already-reinvited@example.com',
'invitation_sent_at' => now()->subDays(3),
're_invitation_sent_at' => now()->subDay(),
're_invitation_count' => 1,
]);
User::factory()->create(['email' => 'signed-up@example.com']);
$this->artisan('leads:send-re-invitations', ['--limit' => 10, '--force' => true])
->assertSuccessful();
Mail::assertQueued(UserLeadReInvitation::class, 1);
Mail::assertQueued(UserLeadReInvitation::class, fn (UserLeadReInvitation $mail): bool => $mail->hasTo('pending@example.com'));
});
it('can re-invite a specific email', function (): void {
$target = UserLead::factory()->create(['email' => 'target@example.com', 'invitation_sent_at' => now()->subDays(3)]);
UserLead::factory()->create(['email' => 'other@example.com', 'invitation_sent_at' => now()->subDays(3)]);
$this->artisan('leads:send-re-invitations', ['--email' => 'target@example.com', '--force' => true])
->assertSuccessful();
Mail::assertQueued(UserLeadReInvitation::class, 1);
Mail::assertQueued(UserLeadReInvitation::class, fn (UserLeadReInvitation $mail): bool => $mail->hasTo('target@example.com'));
expect($target->refresh()->re_invitation_sent_at)->not->toBeNull()
->and(UserLead::query()->whereNotNull('re_invitation_sent_at')->pluck('email')->all())->toBe(['target@example.com']);
});
it('shows re-invitation signup stats', function (): void {
UserLead::factory()->create([
'email' => 'converted@example.com',
'invitation_sent_at' => now()->subDays(4),
're_invitation_sent_at' => now()->subDays(2),
're_invitation_count' => 1,
]);
UserLead::factory()->create([
'email' => 'not-converted@example.com',
'invitation_sent_at' => now()->subDays(4),
're_invitation_sent_at' => now()->subDays(2),
're_invitation_count' => 1,
]);
User::factory()->create(['email' => 'converted@example.com', 'created_at' => now()->subDay()]);
$this->artisan('leads:send-re-invitations', ['--stats' => true])
->expectsTable(['Metric', 'Value'], [
['Re-invited leads', 2],
['Re-invited leads signed up', 1],
['Success rate', '50%'],
])
->assertSuccessful();
Mail::assertNothingQueued();
});
it('defaults to leads invited at least three days ago', function (): void {
UserLead::factory()->create(['email' => 'old@example.com', 'invitation_sent_at' => now()->subDays(3)]);
UserLead::factory()->create(['email' => 'fresh@example.com', 'invitation_sent_at' => now()->subDays(2)]);
$this->artisan('leads:send-re-invitations', ['--limit' => 10, '--force' => true])
->assertSuccessful();
Mail::assertQueued(UserLeadReInvitation::class, 1);
Mail::assertQueued(UserLeadReInvitation::class, fn (UserLeadReInvitation $mail): bool => $mail->hasTo('old@example.com'));
});
it('respects the minimum days since original invite filter', function (): void {
UserLead::factory()->create(['email' => 'old@example.com', 'invitation_sent_at' => now()->subDays(10)]);
UserLead::factory()->create(['email' => 'fresh@example.com', 'invitation_sent_at' => now()->subDays(2)]);
$this->artisan('leads:send-re-invitations', [
'--limit' => 10,
'--min-days-since-invite' => 7,
'--force' => true,
])->assertSuccessful();
Mail::assertQueued(UserLeadReInvitation::class, 1);
Mail::assertQueued(UserLeadReInvitation::class, fn (UserLeadReInvitation $mail): bool => $mail->hasTo('old@example.com'));
});