libra/tests/Feature/Admin/BookingReviewApprovalTest.php

394 lines
12 KiB
PHP

<?php
use App\Enums\ConsultationStatus;
use App\Enums\ConsultationType;
use App\Enums\PaymentStatus;
use App\Models\AdminLog;
use App\Models\Consultation;
use App\Models\User;
use App\Notifications\BookingApproved;
use App\Notifications\BookingRejected;
use Illuminate\Support\Facades\Notification;
use Livewire\Volt\Volt;
test('guest cannot access pending bookings page', function () {
$this->get(route('admin.bookings.pending'))
->assertRedirect(route('login'));
});
test('client cannot access pending bookings page', function () {
$client = User::factory()->individual()->create();
$this->actingAs($client)
->get(route('admin.bookings.pending'))
->assertForbidden();
});
test('admin can access pending bookings page', function () {
$admin = User::factory()->admin()->create();
$this->actingAs($admin)
->get(route('admin.bookings.pending'))
->assertOk();
});
test('pending bookings list displays pending consultations', 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.pending')
->assertSee($client->full_name);
});
test('pending bookings list does not show non-pending consultations', function () {
$admin = User::factory()->admin()->create();
$client = User::factory()->individual()->create();
Consultation::factory()->approved()->create([
'user_id' => $client->id,
]);
$this->actingAs($admin);
Volt::test('admin.bookings.pending')
->assertDontSee($client->full_name);
});
test('admin can filter bookings by date range', function () {
$admin = User::factory()->admin()->create();
$oldClient = User::factory()->individual()->create(['full_name' => 'Old Client']);
$newClient = User::factory()->individual()->create(['full_name' => 'New Client']);
Consultation::factory()->pending()->create([
'user_id' => $oldClient->id,
'booking_date' => now()->subDays(10),
]);
Consultation::factory()->pending()->create([
'user_id' => $newClient->id,
'booking_date' => now()->addDays(5),
]);
$this->actingAs($admin);
Volt::test('admin.bookings.pending')
->set('dateFrom', now()->format('Y-m-d'))
->assertSee('New Client')
->assertDontSee('Old Client');
});
test('admin can access booking review page', function () {
$admin = User::factory()->admin()->create();
$consultation = Consultation::factory()->pending()->create();
$this->actingAs($admin)
->get(route('admin.bookings.review', $consultation))
->assertOk();
});
test('admin can approve booking as free consultation', 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')
->assertHasNoErrors()
->assertRedirect(route('admin.bookings.pending'));
expect($consultation->fresh())
->status->toBe(ConsultationStatus::Approved)
->consultation_type->toBe(ConsultationType::Free)
->payment_status->toBe(PaymentStatus::NotApplicable);
Notification::assertSentTo($client, BookingApproved::class);
});
test('admin can approve booking as paid consultation with amount', 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', 'paid')
->set('paymentAmount', '150.00')
->set('paymentInstructions', 'Bank transfer to account XYZ')
->call('approve')
->assertHasNoErrors();
expect($consultation->fresh())
->status->toBe(ConsultationStatus::Approved)
->consultation_type->toBe(ConsultationType::Paid)
->payment_amount->toBe('150.00')
->payment_status->toBe(PaymentStatus::Pending);
});
test('paid consultation requires payment amount', function () {
$admin = User::factory()->admin()->create();
$consultation = Consultation::factory()->pending()->create();
$this->actingAs($admin);
Volt::test('admin.bookings.review', ['consultation' => $consultation])
->set('consultationType', 'paid')
->set('paymentAmount', null)
->call('approve')
->assertHasErrors(['paymentAmount']);
});
test('admin can reject booking with reason', 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('rejectionReason', 'Schedule conflict')
->call('reject')
->assertHasNoErrors()
->assertRedirect(route('admin.bookings.pending'));
expect($consultation->fresh())->status->toBe(ConsultationStatus::Rejected);
Notification::assertSentTo($client, BookingRejected::class);
});
test('admin can reject booking without reason', 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])
->call('reject')
->assertHasNoErrors();
expect($consultation->fresh())->status->toBe(ConsultationStatus::Rejected);
});
test('quick approve from list works', 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.pending')
->call('openApproveModal', $consultation->id)
->assertSet('showApproveModal', true)
->assertSet('approvingBookingId', $consultation->id)
->set('consultationType', 'free')
->call('quickApprove')
->assertHasNoErrors();
expect($consultation->fresh())
->status->toBe(ConsultationStatus::Approved)
->consultation_type->toBe(ConsultationType::Free);
Notification::assertSentTo($client, BookingApproved::class);
});
test('quick reject from list works', 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.pending')
->call('quickReject', $consultation->id)
->assertHasNoErrors();
expect($consultation->fresh())->status->toBe(ConsultationStatus::Rejected);
Notification::assertSentTo($client, BookingRejected::class);
});
test('audit log entry created on approval', 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');
expect(AdminLog::query()
->where('admin_id', $admin->id)
->where('action', 'approve')
->where('target_type', 'consultation')
->where('target_id', $consultation->id)
->exists()
)->toBeTrue();
});
test('audit log entry created on rejection', 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('rejectionReason', 'Not available')
->call('reject');
expect(AdminLog::query()
->where('admin_id', $admin->id)
->where('action', 'reject')
->where('target_type', 'consultation')
->where('target_id', $consultation->id)
->exists()
)->toBeTrue();
});
test('cannot approve already approved booking', function () {
Notification::fake();
$admin = User::factory()->admin()->create();
$consultation = Consultation::factory()->approved()->create();
$this->actingAs($admin);
// Ensure status doesn't change
Volt::test('admin.bookings.review', ['consultation' => $consultation])
->call('approve');
// Verify consultation status is still approved (not changed)
expect($consultation->fresh()->status)->toBe(ConsultationStatus::Approved);
// Verify no duplicate notification was sent
Notification::assertNothingSent();
});
test('cannot reject already rejected booking', function () {
Notification::fake();
$admin = User::factory()->admin()->create();
$consultation = Consultation::factory()->create([
'status' => ConsultationStatus::Rejected,
]);
$this->actingAs($admin);
// Ensure status doesn't change
Volt::test('admin.bookings.review', ['consultation' => $consultation])
->call('reject');
// Verify consultation status is still rejected (not changed)
expect($consultation->fresh()->status)->toBe(ConsultationStatus::Rejected);
// Verify no notification was sent
Notification::assertNothingSent();
});
test('booking details view shows client consultation history', function () {
$admin = User::factory()->admin()->create();
$client = User::factory()->individual()->create();
// Create past consultations
Consultation::factory()->completed()->create([
'user_id' => $client->id,
'booking_date' => now()->subMonth(),
]);
// Create current pending consultation
$consultation = Consultation::factory()->pending()->create([
'user_id' => $client->id,
]);
$this->actingAs($admin);
Volt::test('admin.bookings.review', ['consultation' => $consultation])
->assertOk();
});
test('notification sent in client preferred language arabic', function () {
Notification::fake();
$admin = User::factory()->admin()->create();
$arabicClient = User::factory()->individual()->create([
'preferred_language' => 'ar',
]);
$consultation = Consultation::factory()->pending()->create([
'user_id' => $arabicClient->id,
]);
$this->actingAs($admin);
Volt::test('admin.bookings.review', ['consultation' => $consultation])
->set('consultationType', 'free')
->call('approve');
Notification::assertSentTo($arabicClient, BookingApproved::class, function ($notification) {
return $notification->consultation->user->preferred_language === 'ar';
});
});
test('notification sent in client preferred language english', function () {
Notification::fake();
$admin = User::factory()->admin()->create();
$englishClient = User::factory()->individual()->create([
'preferred_language' => 'en',
]);
$consultation = Consultation::factory()->pending()->create([
'user_id' => $englishClient->id,
]);
$this->actingAs($admin);
Volt::test('admin.bookings.review', ['consultation' => $consultation])
->set('consultationType', 'free')
->call('approve');
Notification::assertSentTo($englishClient, BookingApproved::class, function ($notification) {
return $notification->consultation->user->preferred_language === 'en';
});
});