complete story 6.1 with qa tests
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
<?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'));
|
||||
});
|
||||
Reference in New Issue
Block a user