390 lines
12 KiB
PHP
390 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('quickApprove', $consultation->id)
|
|
->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';
|
|
});
|
|
});
|