384 lines
12 KiB
PHP
384 lines
12 KiB
PHP
<?php
|
|
|
|
use App\Enums\ConsultationStatus;
|
|
use App\Enums\ConsultationType;
|
|
use App\Enums\TimelineStatus;
|
|
use App\Enums\UserStatus;
|
|
use App\Models\Consultation;
|
|
use App\Models\Post;
|
|
use App\Models\Timeline;
|
|
use App\Models\TimelineUpdate;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Livewire\Volt\Volt;
|
|
|
|
beforeEach(function () {
|
|
$this->admin = User::factory()->admin()->create();
|
|
Cache::flush();
|
|
});
|
|
|
|
// ===========================================
|
|
// Access Control Tests
|
|
// ===========================================
|
|
|
|
test('admin can view dashboard', function () {
|
|
$this->actingAs($this->admin)
|
|
->get(route('admin.dashboard'))
|
|
->assertSuccessful()
|
|
->assertSee(__('admin_metrics.title'));
|
|
});
|
|
|
|
test('non-admin cannot access dashboard', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($client)
|
|
->get(route('admin.dashboard'))
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('guest cannot access dashboard', function () {
|
|
$this->get(route('admin.dashboard'))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
// ===========================================
|
|
// User Metrics Tests
|
|
// ===========================================
|
|
|
|
test('dashboard displays correct user metrics', function () {
|
|
User::factory()->count(5)->individual()->create(['status' => UserStatus::Active]);
|
|
User::factory()->count(3)->company()->create(['status' => UserStatus::Active]);
|
|
User::factory()->count(2)->individual()->create(['status' => UserStatus::Deactivated]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$userMetrics = $component->viewData('userMetrics');
|
|
|
|
expect($userMetrics['total_active'])->toBe(8)
|
|
->and($userMetrics['individual'])->toBe(5)
|
|
->and($userMetrics['company'])->toBe(3)
|
|
->and($userMetrics['deactivated'])->toBe(2);
|
|
});
|
|
|
|
test('dashboard counts new clients this month correctly', function () {
|
|
// Create clients this month
|
|
User::factory()->count(3)->individual()->create([
|
|
'status' => UserStatus::Active,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
// Create clients from last month
|
|
User::factory()->count(2)->individual()->create([
|
|
'status' => UserStatus::Active,
|
|
'created_at' => now()->subMonth(),
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$userMetrics = $component->viewData('userMetrics');
|
|
|
|
expect($userMetrics['new_this_month'])->toBe(3);
|
|
});
|
|
|
|
test('user metrics excludes admin users from counts', function () {
|
|
User::factory()->admin()->create();
|
|
User::factory()->count(2)->individual()->create(['status' => UserStatus::Active]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$userMetrics = $component->viewData('userMetrics');
|
|
|
|
// Should only count the 2 individual clients, not the admin
|
|
expect($userMetrics['total_active'])->toBe(2);
|
|
});
|
|
|
|
// ===========================================
|
|
// Booking Metrics Tests
|
|
// ===========================================
|
|
|
|
test('dashboard displays correct booking metrics', function () {
|
|
// Create 3 pending consultations
|
|
Consultation::factory()->count(3)->create(['status' => ConsultationStatus::Pending]);
|
|
|
|
// Create 2 approved consultations for today
|
|
Consultation::factory()->count(2)->create([
|
|
'status' => ConsultationStatus::Approved,
|
|
'booking_date' => today(),
|
|
]);
|
|
|
|
// Create 5 free consultations (completed status to avoid counting as pending)
|
|
Consultation::factory()->count(5)->create([
|
|
'consultation_type' => ConsultationType::Free,
|
|
'status' => ConsultationStatus::Completed,
|
|
]);
|
|
|
|
// Create 4 paid consultations (completed status to avoid counting as pending)
|
|
Consultation::factory()->count(4)->create([
|
|
'consultation_type' => ConsultationType::Paid,
|
|
'status' => ConsultationStatus::Completed,
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$bookingMetrics = $component->viewData('bookingMetrics');
|
|
|
|
// 3 pending + 2 approved today (not pending) = 3 pending
|
|
// 2 approved for today
|
|
// 5 free + some from the other consultations
|
|
// 4 paid + some from the other consultations
|
|
expect($bookingMetrics['pending'])->toBe(3)
|
|
->and($bookingMetrics['today'])->toBe(2);
|
|
});
|
|
|
|
test('no-show rate calculates correctly', function () {
|
|
// Create 10 completed consultations
|
|
Consultation::factory()->count(8)->create(['status' => ConsultationStatus::Completed]);
|
|
// Create 2 no-shows = 20% rate
|
|
Consultation::factory()->count(2)->create(['status' => ConsultationStatus::NoShow]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$bookingMetrics = $component->viewData('bookingMetrics');
|
|
|
|
expect($bookingMetrics['no_show_rate'])->toBe(20.0);
|
|
});
|
|
|
|
test('no-show rate handles division by zero', function () {
|
|
// No completed or no-show consultations
|
|
Consultation::factory()->create(['status' => ConsultationStatus::Pending]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$bookingMetrics = $component->viewData('bookingMetrics');
|
|
|
|
expect($bookingMetrics['no_show_rate'])->toBe(0);
|
|
});
|
|
|
|
test('this week consultations counts correctly', function () {
|
|
// Create consultations this week
|
|
Consultation::factory()->count(3)->create([
|
|
'booking_date' => now()->startOfWeek()->addDays(2),
|
|
'status' => ConsultationStatus::Approved,
|
|
]);
|
|
|
|
// Create consultations outside this week
|
|
Consultation::factory()->count(2)->create([
|
|
'booking_date' => now()->subWeeks(2),
|
|
'status' => ConsultationStatus::Approved,
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$bookingMetrics = $component->viewData('bookingMetrics');
|
|
|
|
expect($bookingMetrics['this_week'])->toBe(3);
|
|
});
|
|
|
|
test('this month consultations counts correctly', function () {
|
|
// Create consultations this month
|
|
Consultation::factory()->count(4)->create([
|
|
'booking_date' => now()->startOfMonth()->addDays(5),
|
|
]);
|
|
|
|
// Create consultations last month
|
|
Consultation::factory()->count(2)->create([
|
|
'booking_date' => now()->subMonth(),
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$bookingMetrics = $component->viewData('bookingMetrics');
|
|
|
|
expect($bookingMetrics['this_month'])->toBe(4);
|
|
});
|
|
|
|
// ===========================================
|
|
// Timeline Metrics Tests
|
|
// ===========================================
|
|
|
|
test('dashboard displays correct timeline metrics', function () {
|
|
Timeline::factory()->count(5)->create(['status' => TimelineStatus::Active]);
|
|
Timeline::factory()->count(3)->create(['status' => TimelineStatus::Archived]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$timelineMetrics = $component->viewData('timelineMetrics');
|
|
|
|
expect($timelineMetrics['active'])->toBe(5)
|
|
->and($timelineMetrics['archived'])->toBe(3);
|
|
});
|
|
|
|
test('timeline updates this week counts correctly', function () {
|
|
$timeline = Timeline::factory()->create();
|
|
|
|
// Create updates this week
|
|
TimelineUpdate::factory()->count(4)->create([
|
|
'timeline_id' => $timeline->id,
|
|
'created_at' => now()->subDays(3),
|
|
]);
|
|
|
|
// Create updates older than a week
|
|
TimelineUpdate::factory()->count(2)->create([
|
|
'timeline_id' => $timeline->id,
|
|
'created_at' => now()->subWeeks(2),
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$timelineMetrics = $component->viewData('timelineMetrics');
|
|
|
|
expect($timelineMetrics['updates_this_week'])->toBe(4);
|
|
});
|
|
|
|
// ===========================================
|
|
// Post Metrics Tests
|
|
// ===========================================
|
|
|
|
test('dashboard displays correct post metrics', function () {
|
|
Post::factory()->count(5)->published()->create();
|
|
Post::factory()->count(3)->draft()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$postMetrics = $component->viewData('postMetrics');
|
|
|
|
expect($postMetrics['total_published'])->toBe(5);
|
|
});
|
|
|
|
test('posts published this month counts correctly', function () {
|
|
// Create posts published this month
|
|
Post::factory()->count(3)->published()->create([
|
|
'published_at' => now(),
|
|
]);
|
|
|
|
// Create posts published last month
|
|
Post::factory()->count(2)->published()->create([
|
|
'published_at' => now()->subMonth(),
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$postMetrics = $component->viewData('postMetrics');
|
|
|
|
expect($postMetrics['this_month'])->toBe(3);
|
|
});
|
|
|
|
// ===========================================
|
|
// Empty State Tests
|
|
// ===========================================
|
|
|
|
test('dashboard handles empty database gracefully', function () {
|
|
$this->actingAs($this->admin)
|
|
->get(route('admin.dashboard'))
|
|
->assertSuccessful()
|
|
->assertSee('0');
|
|
});
|
|
|
|
test('all metrics show zero when no data exists', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.dashboard');
|
|
|
|
$userMetrics = $component->viewData('userMetrics');
|
|
$bookingMetrics = $component->viewData('bookingMetrics');
|
|
$timelineMetrics = $component->viewData('timelineMetrics');
|
|
$postMetrics = $component->viewData('postMetrics');
|
|
|
|
expect($userMetrics['total_active'])->toBe(0)
|
|
->and($userMetrics['individual'])->toBe(0)
|
|
->and($userMetrics['company'])->toBe(0)
|
|
->and($userMetrics['deactivated'])->toBe(0)
|
|
->and($userMetrics['new_this_month'])->toBe(0)
|
|
->and($bookingMetrics['pending'])->toBe(0)
|
|
->and($bookingMetrics['today'])->toBe(0)
|
|
->and($bookingMetrics['this_week'])->toBe(0)
|
|
->and($bookingMetrics['this_month'])->toBe(0)
|
|
->and($bookingMetrics['free'])->toBe(0)
|
|
->and($bookingMetrics['paid'])->toBe(0)
|
|
->and($bookingMetrics['no_show_rate'])->toBe(0)
|
|
->and($timelineMetrics['active'])->toBe(0)
|
|
->and($timelineMetrics['archived'])->toBe(0)
|
|
->and($timelineMetrics['updates_this_week'])->toBe(0)
|
|
->and($postMetrics['total_published'])->toBe(0)
|
|
->and($postMetrics['this_month'])->toBe(0);
|
|
});
|
|
|
|
// ===========================================
|
|
// Cache Tests
|
|
// ===========================================
|
|
|
|
test('metrics are cached for 5 minutes', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
// First load to populate cache
|
|
Volt::test('admin.dashboard');
|
|
|
|
// Verify cache keys exist
|
|
expect(Cache::has('admin.metrics.users'))->toBeTrue()
|
|
->and(Cache::has('admin.metrics.bookings'))->toBeTrue()
|
|
->and(Cache::has('admin.metrics.timelines'))->toBeTrue()
|
|
->and(Cache::has('admin.metrics.posts'))->toBeTrue();
|
|
});
|
|
|
|
test('cached metrics persist after data changes', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
// First load to populate cache
|
|
$component = Volt::test('admin.dashboard');
|
|
$initialCount = $component->viewData('userMetrics')['total_active'];
|
|
|
|
// Add new users
|
|
User::factory()->count(5)->individual()->create(['status' => UserStatus::Active]);
|
|
|
|
// Reload component - should still show cached value
|
|
$component = Volt::test('admin.dashboard');
|
|
$cachedCount = $component->viewData('userMetrics')['total_active'];
|
|
|
|
expect($cachedCount)->toBe($initialCount);
|
|
});
|
|
|
|
// ===========================================
|
|
// UI Element Tests
|
|
// ===========================================
|
|
|
|
test('dashboard displays all metric cards', function () {
|
|
$this->actingAs($this->admin)
|
|
->get(route('admin.dashboard'))
|
|
->assertSee(__('admin_metrics.clients'))
|
|
->assertSee(__('admin_metrics.consultations'))
|
|
->assertSee(__('admin_metrics.timelines'))
|
|
->assertSee(__('admin_metrics.posts'));
|
|
});
|
|
|
|
test('pending requests badge is displayed', function () {
|
|
Consultation::factory()->count(5)->create(['status' => ConsultationStatus::Pending]);
|
|
|
|
$this->actingAs($this->admin)
|
|
->get(route('admin.dashboard'))
|
|
->assertSee(__('admin_metrics.pending_requests'));
|
|
});
|