libra/tests/Feature/Public/GuestBookingTest.php

319 lines
10 KiB
PHP

<?php
use App\Enums\ConsultationStatus;
use App\Models\Consultation;
use App\Models\User;
use App\Models\WorkingHour;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Volt\Volt;
beforeEach(function () {
Mail::fake();
RateLimiter::clear('guest-booking:127.0.0.1');
});
test('guest can view booking page', function () {
$this->get(route('booking'))
->assertOk()
->assertSee(__('booking.request_consultation'));
});
test('logged in user sees message to book from dashboard', function () {
$user = User::factory()->client()->create();
$this->actingAs($user)
->get(route('booking'))
->assertOk()
->assertSee(__('booking.logged_in_title'))
->assertSee(__('booking.logged_in_message'))
->assertSee(__('booking.go_to_dashboard'));
});
test('guest booking page shows calendar', function () {
$this->get(route('booking'))
->assertOk()
->assertSee(__('booking.guest_intro'));
});
test('guest can select a slot and see contact form', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
$date = now()->addDay()->format('Y-m-d');
Volt::test('pages.booking')
->call('selectSlot', $date, '09:00')
->assertSet('selectedDate', $date)
->assertSet('selectedTime', '09:00')
->assertSee(__('booking.guest_name'));
});
test('guest can submit booking request', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
$date = now()->addDay()->format('Y-m-d');
$component = Volt::test('pages.booking')
->call('selectSlot', $date, '09:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'john@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'I need legal advice regarding a contract dispute with my employer.')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->assertSet('showConfirmation', true)
->call('submit')
->assertRedirect(route('booking.success'));
$this->assertDatabaseHas('consultations', [
'guest_email' => 'john@example.com',
'guest_name' => 'John Doe',
'user_id' => null,
'status' => ConsultationStatus::Pending->value,
]);
});
test('guest cannot book twice on same day', function () {
$date = now()->addDay()->format('Y-m-d');
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
// Create existing booking for this email
Consultation::factory()->guest()->create([
'guest_email' => 'john@example.com',
'booking_date' => $date,
'status' => ConsultationStatus::Pending,
]);
Volt::test('pages.booking')
->call('selectSlot', $date, '10:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'john@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'Another consultation request for testing purposes.')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->assertHasErrors(['guestEmail']);
});
test('invalid captcha prevents submission', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
Volt::test('pages.booking')
->call('selectSlot', now()->addDay()->format('Y-m-d'), '09:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'john@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'I need legal advice regarding a contract dispute.')
->set('captchaAnswer', 'wrong-answer')
->call('showConfirm')
->assertHasErrors(['captchaAnswer']);
});
test('rate limiting prevents excessive booking attempts', function () {
$ipKey = 'guest-booking:127.0.0.1';
// Exhaust the rate limit (5 attempts)
for ($i = 0; $i < 5; $i++) {
RateLimiter::hit($ipKey, 60 * 60 * 24);
}
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
$date = now()->addDay()->format('Y-m-d');
Volt::test('pages.booking')
->call('selectSlot', $date, '09:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'john@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'I need legal advice regarding a contract dispute with my employer.')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->call('submit')
->assertHasErrors(['guestEmail']);
RateLimiter::clear($ipKey);
});
test('slot taken during submission shows error', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
$date = now()->addDay()->format('Y-m-d');
// Start the booking process
$component = Volt::test('pages.booking')
->call('selectSlot', $date, '09:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'john@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'I need legal advice regarding a contract dispute with my employer.')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->assertSet('showConfirmation', true);
// Simulate another booking taking the slot before submission
Consultation::factory()->guest()->create([
'booking_date' => $date,
'booking_time' => '09:00',
'status' => ConsultationStatus::Pending,
]);
// Try to submit - should fail with slot taken error
$component->call('submit')
->assertHasErrors(['selectedTime']);
});
test('guest can clear slot selection', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
$date = now()->addDay()->format('Y-m-d');
Volt::test('pages.booking')
->call('selectSlot', $date, '09:00')
->assertSet('selectedDate', $date)
->call('clearSelection')
->assertSet('selectedDate', null)
->assertSet('selectedTime', null);
});
test('guest can refresh captcha', function () {
$component = Volt::test('pages.booking');
$firstQuestion = $component->get('captchaQuestion');
$component->call('refreshCaptcha');
// Just verify the captcha answer was reset
$component->assertSet('captchaAnswer', '');
});
test('success page is accessible after booking', function () {
$this->withSession(['success' => 'Test success message'])
->get(route('booking.success'))
->assertOk()
->assertSee(__('booking.success_title'));
});
test('form validation requires all fields', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
Volt::test('pages.booking')
->call('selectSlot', now()->addDay()->format('Y-m-d'), '09:00')
->set('guestName', '')
->set('guestEmail', '')
->set('guestPhone', '')
->set('problemSummary', '')
->set('captchaAnswer', '')
->call('showConfirm')
->assertHasErrors(['guestName', 'guestEmail', 'guestPhone', 'problemSummary', 'captchaAnswer']);
});
test('guest name must be at least 3 characters', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
Volt::test('pages.booking')
->call('selectSlot', now()->addDay()->format('Y-m-d'), '09:00')
->set('guestName', 'AB')
->set('guestEmail', 'test@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'This is a valid problem summary for testing.')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->assertHasErrors(['guestName']);
});
test('problem summary must be at least 20 characters', function () {
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '17:00',
]);
Volt::test('pages.booking')
->call('selectSlot', now()->addDay()->format('Y-m-d'), '09:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'test@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'Too short')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->assertHasErrors(['problemSummary']);
});
test('slot no longer available error is shown', function () {
// Create a working hour but no available slots
WorkingHour::factory()->create([
'day_of_week' => now()->addDay()->dayOfWeek,
'is_active' => true,
'start_time' => '09:00',
'end_time' => '10:00',
]);
$date = now()->addDay()->format('Y-m-d');
// Book the only available slot
Consultation::factory()->guest()->create([
'booking_date' => $date,
'booking_time' => '09:00',
'status' => ConsultationStatus::Approved,
]);
// Try to book the same slot
Volt::test('pages.booking')
->call('selectSlot', $date, '09:00')
->set('guestName', 'John Doe')
->set('guestEmail', 'different@example.com')
->set('guestPhone', '+970599123456')
->set('problemSummary', 'I need legal advice regarding a contract dispute.')
->set('captchaAnswer', session('captcha_answer'))
->call('showConfirm')
->assertHasErrors(['selectedTime']);
});