libra/tests/Feature/Admin/DashboardTest.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'));
});