complete story 11.3 with qa tests
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Mail\GuestBookingApprovedMail;
|
||||
use App\Mail\GuestBookingRejectedMail;
|
||||
use App\Models\Consultation;
|
||||
use App\Models\User;
|
||||
use App\Notifications\BookingApproved;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
test('admin can see guest bookings in pending list', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$guestConsultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_name' => 'Test Guest User',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.pending')
|
||||
->assertSee('Test Guest User')
|
||||
->assertSee(__('admin.guest'));
|
||||
});
|
||||
|
||||
test('admin can view guest booking details in review page', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_name' => 'Test Guest',
|
||||
'guest_email' => 'testguest@example.com',
|
||||
'guest_phone' => '+1234567890',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->assertSee('Test Guest')
|
||||
->assertSee('testguest@example.com')
|
||||
->assertSee('+1234567890')
|
||||
->assertSee(__('admin.guest'));
|
||||
});
|
||||
|
||||
test('admin can approve guest booking via modal', function () {
|
||||
Mail::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->set('consultationType', 'free')
|
||||
->call('approve')
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect($consultation->fresh()->status)->toBe(ConsultationStatus::Approved);
|
||||
Mail::assertQueued(GuestBookingApprovedMail::class);
|
||||
});
|
||||
|
||||
test('admin can reject guest booking via modal', function () {
|
||||
Mail::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->set('rejectionReason', 'Not available for this time')
|
||||
->call('reject')
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect($consultation->fresh()->status)->toBe(ConsultationStatus::Rejected);
|
||||
Mail::assertQueued(GuestBookingRejectedMail::class);
|
||||
});
|
||||
|
||||
test('admin can quick approve guest booking from pending list', function () {
|
||||
Mail::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.pending')
|
||||
->call('quickApprove', $consultation->id)
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect($consultation->fresh()->status)->toBe(ConsultationStatus::Approved);
|
||||
Mail::assertQueued(GuestBookingApprovedMail::class);
|
||||
});
|
||||
|
||||
test('admin can quick reject guest booking from pending list', function () {
|
||||
Mail::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.pending')
|
||||
->call('quickReject', $consultation->id)
|
||||
->assertHasNoErrors();
|
||||
|
||||
expect($consultation->fresh()->status)->toBe(ConsultationStatus::Rejected);
|
||||
Mail::assertQueued(GuestBookingRejectedMail::class);
|
||||
});
|
||||
|
||||
test('guest booking review shows no consultation history', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$component = Volt::test('admin.bookings.review', ['consultation' => $consultation]);
|
||||
|
||||
// Guest bookings should return empty consultation history
|
||||
expect($component->viewData('consultationHistory'))->toBeEmpty();
|
||||
});
|
||||
|
||||
test('client booking still uses notification system', function () {
|
||||
Notification::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$client = User::factory()->individual()->create();
|
||||
$consultation = Consultation::factory()->pending()->create(['user_id' => $client->id]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->set('consultationType', 'free')
|
||||
->call('approve');
|
||||
|
||||
Notification::assertSentTo($client, BookingApproved::class);
|
||||
});
|
||||
|
||||
test('pending list shows guest email with mailto link', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_email' => 'guest@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.pending')
|
||||
->assertSeeHtml('href="mailto:guest@example.com"');
|
||||
});
|
||||
|
||||
test('review page shows guest email with mailto link', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_email' => 'guest@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->assertSeeHtml('href="mailto:guest@example.com"');
|
||||
});
|
||||
|
||||
test('review page shows guest phone with tel link', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_phone' => '+1234567890',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->assertSeeHtml('href="tel:+1234567890"');
|
||||
});
|
||||
|
||||
test('review page hides client type field for guest bookings', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
// Guest bookings should not show client type row
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->assertDontSee(__('admin.client_type'));
|
||||
});
|
||||
|
||||
test('review page shows client type field for client bookings', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$client = User::factory()->individual()->create();
|
||||
$consultation = Consultation::factory()->pending()->create(['user_id' => $client->id]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->assertSee(__('admin.client_type'));
|
||||
});
|
||||
|
||||
test('approve modal shows guest name in summary', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_name' => 'Guest Modal Test',
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->assertSee('Guest Modal Test');
|
||||
});
|
||||
|
||||
test('guest booking approval with payment instructions sends correct email', function () {
|
||||
Mail::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->set('consultationType', 'paid')
|
||||
->set('paymentAmount', '200.00')
|
||||
->set('paymentInstructions', 'Bank transfer instructions')
|
||||
->call('approve')
|
||||
->assertHasNoErrors();
|
||||
|
||||
Mail::assertQueued(GuestBookingApprovedMail::class, function ($mail) {
|
||||
return $mail->paymentInstructions === 'Bank transfer instructions';
|
||||
});
|
||||
});
|
||||
|
||||
test('guest booking rejection with reason sends correct email', function () {
|
||||
Mail::fake();
|
||||
|
||||
$admin = User::factory()->admin()->create();
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.review', ['consultation' => $consultation])
|
||||
->set('rejectionReason', 'Time slot unavailable')
|
||||
->call('reject')
|
||||
->assertHasNoErrors();
|
||||
|
||||
Mail::assertQueued(GuestBookingRejectedMail::class, function ($mail) {
|
||||
return $mail->reason === 'Time slot unavailable';
|
||||
});
|
||||
});
|
||||
|
||||
test('mixed guest and client bookings appear in pending list', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
$client = User::factory()->individual()->create(['full_name' => 'Regular Client']);
|
||||
|
||||
$guestConsultation = Consultation::factory()->guest()->pending()->create([
|
||||
'guest_name' => 'Guest Person',
|
||||
]);
|
||||
$clientConsultation = Consultation::factory()->pending()->create([
|
||||
'user_id' => $client->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
Volt::test('admin.bookings.pending')
|
||||
->assertSee('Guest Person')
|
||||
->assertSee('Regular Client')
|
||||
->assertSee(__('admin.guest'));
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Enums\ConsultationType;
|
||||
use App\Mail\GuestBookingApprovedMail;
|
||||
use App\Mail\GuestBookingRejectedMail;
|
||||
use App\Mail\GuestBookingSubmittedMail;
|
||||
use App\Mail\NewBookingAdminEmail;
|
||||
use App\Models\Consultation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
test('guest receives confirmation email on booking submission', function () {
|
||||
Mail::fake();
|
||||
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
Mail::to($consultation->guest_email)->queue(
|
||||
new GuestBookingSubmittedMail($consultation)
|
||||
);
|
||||
|
||||
Mail::assertQueued(GuestBookingSubmittedMail::class, function ($mail) use ($consultation) {
|
||||
return $mail->hasTo($consultation->guest_email);
|
||||
});
|
||||
});
|
||||
|
||||
test('guest receives approval email with calendar attachment', function () {
|
||||
Mail::fake();
|
||||
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => ConsultationType::Free,
|
||||
]);
|
||||
|
||||
Mail::to($consultation->guest_email)->queue(
|
||||
new GuestBookingApprovedMail($consultation, emailLocale: 'en')
|
||||
);
|
||||
|
||||
Mail::assertQueued(GuestBookingApprovedMail::class, function ($mail) use ($consultation) {
|
||||
return $mail->hasTo($consultation->guest_email);
|
||||
});
|
||||
});
|
||||
|
||||
test('guest booking approved mail has calendar attachment', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => ConsultationType::Free,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingApprovedMail($consultation, emailLocale: 'en');
|
||||
$attachments = $mail->attachments();
|
||||
|
||||
expect($attachments)->toHaveCount(1);
|
||||
});
|
||||
|
||||
test('guest receives rejection email', function () {
|
||||
Mail::fake();
|
||||
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Rejected,
|
||||
]);
|
||||
|
||||
Mail::to($consultation->guest_email)->queue(
|
||||
new GuestBookingRejectedMail($consultation, emailLocale: 'en', reason: 'Not available')
|
||||
);
|
||||
|
||||
Mail::assertQueued(GuestBookingRejectedMail::class, function ($mail) use ($consultation) {
|
||||
return $mail->hasTo($consultation->guest_email);
|
||||
});
|
||||
});
|
||||
|
||||
test('guest booking rejected mail includes reason when provided', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Rejected,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingRejectedMail($consultation, emailLocale: 'en', reason: 'Schedule conflict');
|
||||
$content = $mail->content();
|
||||
|
||||
expect($content->with['hasReason'])->toBeTrue();
|
||||
expect($content->with['reason'])->toBe('Schedule conflict');
|
||||
});
|
||||
|
||||
test('guest booking rejected mail handles missing reason', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Rejected,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingRejectedMail($consultation, emailLocale: 'en', reason: null);
|
||||
$content = $mail->content();
|
||||
|
||||
expect($content->with['hasReason'])->toBeFalse();
|
||||
expect($content->with['reason'])->toBeNull();
|
||||
});
|
||||
|
||||
test('admin email shows guest indicator for guest bookings', function () {
|
||||
$consultation = Consultation::factory()->guest()->pending()->create();
|
||||
|
||||
$mail = new NewBookingAdminEmail($consultation);
|
||||
$content = $mail->content();
|
||||
|
||||
expect($content->with['isGuest'])->toBeTrue();
|
||||
expect($content->with['clientName'])->toBe($consultation->guest_name);
|
||||
expect($content->with['clientEmail'])->toBe($consultation->guest_email);
|
||||
expect($content->with['clientPhone'])->toBe($consultation->guest_phone);
|
||||
});
|
||||
|
||||
test('admin email shows client info for client bookings', function () {
|
||||
$client = User::factory()->individual()->create();
|
||||
$consultation = Consultation::factory()->pending()->create([
|
||||
'user_id' => $client->id,
|
||||
]);
|
||||
|
||||
$mail = new NewBookingAdminEmail($consultation);
|
||||
$content = $mail->content();
|
||||
|
||||
expect($content->with['isGuest'])->toBeFalse();
|
||||
expect($content->with['clientName'])->toBe($client->full_name);
|
||||
expect($content->with['clientEmail'])->toBe($client->email);
|
||||
expect($content->with['clientPhone'])->toBe($client->phone);
|
||||
});
|
||||
|
||||
test('guest booking approved mail uses correct arabic locale', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => ConsultationType::Free,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingApprovedMail($consultation, emailLocale: 'ar');
|
||||
$envelope = $mail->envelope();
|
||||
$content = $mail->content();
|
||||
|
||||
expect($envelope->subject)->toContain('تأكيد الحجز');
|
||||
expect($content->markdown)->toBe('emails.booking.guest-approved.ar');
|
||||
});
|
||||
|
||||
test('guest booking approved mail uses correct english locale', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => ConsultationType::Free,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingApprovedMail($consultation, emailLocale: 'en');
|
||||
$envelope = $mail->envelope();
|
||||
$content = $mail->content();
|
||||
|
||||
expect($envelope->subject)->toContain('Booking Confirmed');
|
||||
expect($content->markdown)->toBe('emails.booking.guest-approved.en');
|
||||
});
|
||||
|
||||
test('guest booking rejected mail uses correct arabic locale', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Rejected,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingRejectedMail($consultation, emailLocale: 'ar', reason: null);
|
||||
$envelope = $mail->envelope();
|
||||
$content = $mail->content();
|
||||
|
||||
expect($envelope->subject)->toContain('تحديث الحجز');
|
||||
expect($content->markdown)->toBe('emails.booking.guest-rejected.ar');
|
||||
});
|
||||
|
||||
test('guest booking approved mail includes payment info for paid consultations', function () {
|
||||
$consultation = Consultation::factory()->guest()->create([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => ConsultationType::Paid,
|
||||
'payment_amount' => 150.00,
|
||||
]);
|
||||
|
||||
$mail = new GuestBookingApprovedMail($consultation, emailLocale: 'en', paymentInstructions: 'Bank transfer details');
|
||||
$content = $mail->content();
|
||||
|
||||
expect($content->with['isPaid'])->toBeTrue();
|
||||
expect($content->with['paymentAmount'])->toBe('150.00');
|
||||
expect($content->with['paymentInstructions'])->toBe('Bank transfer details');
|
||||
});
|
||||
Reference in New Issue
Block a user