418 lines
13 KiB
PHP
418 lines
13 KiB
PHP
<?php
|
|
|
|
use App\Models\Consultation;
|
|
use App\Models\Post;
|
|
use App\Models\Timeline;
|
|
use App\Models\TimelineUpdate;
|
|
use App\Models\User;
|
|
use App\Services\MonthlyReportService;
|
|
use Livewire\Volt\Volt;
|
|
|
|
beforeEach(function () {
|
|
$this->admin = User::factory()->admin()->create();
|
|
});
|
|
|
|
// ===========================================
|
|
// Access Tests
|
|
// ===========================================
|
|
|
|
test('admin can access monthly report page', function () {
|
|
$this->actingAs($this->admin)
|
|
->get(route('admin.reports.monthly'))
|
|
->assertOk()
|
|
->assertSee(__('report.monthly_report'));
|
|
});
|
|
|
|
test('non-admin cannot access monthly report page', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($client)
|
|
->get(route('admin.reports.monthly'))
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('unauthenticated user cannot access monthly report page', function () {
|
|
$this->get(route('admin.reports.monthly'))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
// ===========================================
|
|
// Component Tests
|
|
// ===========================================
|
|
|
|
test('monthly report component mounts with previous month as default', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
$previousMonth = now()->subMonth();
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->assertSet('selectedPeriod', $previousMonth->format('Y-m'));
|
|
});
|
|
|
|
test('available months shows only last 12 months', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.reports.monthly-report');
|
|
|
|
$availableMonths = $component->get('availableMonths');
|
|
|
|
expect(count($availableMonths))->toBe(12);
|
|
});
|
|
|
|
test('available months does not include current month', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.reports.monthly-report');
|
|
|
|
$availableMonths = $component->get('availableMonths');
|
|
$currentMonth = now()->format('Y-m');
|
|
|
|
$values = array_column($availableMonths, 'value');
|
|
|
|
expect($values)->not->toContain($currentMonth);
|
|
});
|
|
|
|
test('available months includes previous month', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.reports.monthly-report');
|
|
|
|
$availableMonths = $component->get('availableMonths');
|
|
$previousMonth = now()->subMonth()->format('Y-m');
|
|
|
|
$values = array_column($availableMonths, 'value');
|
|
|
|
expect($values)->toContain($previousMonth);
|
|
});
|
|
|
|
// ===========================================
|
|
// PDF Generation Tests
|
|
// ===========================================
|
|
|
|
test('monthly report generates valid PDF', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
// Create test data for the month
|
|
$targetMonth = now()->subMonth();
|
|
User::factory()->count(5)->individual()->create([
|
|
'created_at' => $targetMonth,
|
|
]);
|
|
Consultation::factory()->count(10)->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->set('selectedPeriod', $targetMonth->format('Y-m'))
|
|
->call('generate')
|
|
->assertFileDownloaded("monthly-report-{$targetMonth->year}-{$targetMonth->month}.pdf");
|
|
});
|
|
|
|
test('report handles month with no data gracefully', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
// Generate for a month with no data (far in the past)
|
|
$emptyMonth = now()->subMonths(10);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->set('selectedPeriod', $emptyMonth->format('Y-m'))
|
|
->call('generate')
|
|
->assertFileDownloaded();
|
|
});
|
|
|
|
// ===========================================
|
|
// Service Tests - User Statistics
|
|
// ===========================================
|
|
|
|
test('user statistics are accurate for selected month', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
// Create 3 users in target month
|
|
User::factory()->count(3)->individual()->create([
|
|
'created_at' => $targetMonth,
|
|
]);
|
|
|
|
// Create 2 users in different month (should not be counted as new)
|
|
User::factory()->count(2)->individual()->create([
|
|
'created_at' => now()->subMonths(3),
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getUserStats($startDate, $endDate);
|
|
|
|
expect($stats['new_clients'])->toBe(3);
|
|
});
|
|
|
|
test('user statistics count individual and company clients separately', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
User::factory()->count(4)->individual()->create([
|
|
'created_at' => $targetMonth->copy()->subMonth(),
|
|
]);
|
|
User::factory()->count(2)->company()->create([
|
|
'created_at' => $targetMonth->copy()->subMonth(),
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getUserStats($startDate, $endDate);
|
|
|
|
expect($stats['individual'])->toBe(4);
|
|
expect($stats['company'])->toBe(2);
|
|
expect($stats['total_active'])->toBe(6);
|
|
});
|
|
|
|
// ===========================================
|
|
// Service Tests - Consultation Statistics
|
|
// ===========================================
|
|
|
|
test('consultation statistics calculate totals correctly', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
Consultation::factory()->count(5)->completed()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
Consultation::factory()->count(3)->pending()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
Consultation::factory()->count(2)->cancelled()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getConsultationStats($startDate, $endDate);
|
|
|
|
expect($stats['total'])->toBe(10);
|
|
expect($stats['completed'])->toBe(5);
|
|
expect($stats['cancelled'])->toBe(2);
|
|
});
|
|
|
|
test('consultation statistics calculate no-show rate correctly', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
// 8 completed + 2 no-shows = 20% no-show rate
|
|
Consultation::factory()->count(8)->completed()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
Consultation::factory()->count(2)->noShow()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getConsultationStats($startDate, $endDate);
|
|
|
|
expect($stats['no_show_rate'])->toBe(20.0);
|
|
});
|
|
|
|
test('consultation statistics handle zero completed consultations', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
// Only pending consultations, no completed ones
|
|
Consultation::factory()->count(5)->pending()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getConsultationStats($startDate, $endDate);
|
|
|
|
expect($stats['no_show_rate'])->toBe(0);
|
|
});
|
|
|
|
test('consultation statistics count free and paid types', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
Consultation::factory()->count(4)->free()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
Consultation::factory()->count(6)->paid()->create([
|
|
'booking_date' => $targetMonth,
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getConsultationStats($startDate, $endDate);
|
|
|
|
expect($stats['free'])->toBe(4);
|
|
expect($stats['paid'])->toBe(6);
|
|
});
|
|
|
|
// ===========================================
|
|
// Service Tests - Timeline Statistics
|
|
// ===========================================
|
|
|
|
test('timeline statistics count active timelines correctly', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
Timeline::factory()->count(5)->active()->create([
|
|
'created_at' => $targetMonth->copy()->subMonth(),
|
|
]);
|
|
Timeline::factory()->count(2)->archived()->create([
|
|
'created_at' => $targetMonth->copy()->subMonth(),
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getTimelineStats($startDate, $endDate);
|
|
|
|
expect($stats['active'])->toBe(5);
|
|
});
|
|
|
|
test('timeline statistics count new timelines in month', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
Timeline::factory()->count(3)->create([
|
|
'created_at' => $targetMonth,
|
|
]);
|
|
Timeline::factory()->count(2)->create([
|
|
'created_at' => now()->subMonths(3),
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getTimelineStats($startDate, $endDate);
|
|
|
|
expect($stats['new'])->toBe(3);
|
|
});
|
|
|
|
test('timeline statistics count updates in month', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
$timeline = Timeline::factory()->create();
|
|
TimelineUpdate::factory()->count(5)->create([
|
|
'timeline_id' => $timeline->id,
|
|
'created_at' => $targetMonth,
|
|
]);
|
|
TimelineUpdate::factory()->count(2)->create([
|
|
'timeline_id' => $timeline->id,
|
|
'created_at' => now()->subMonths(3),
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getTimelineStats($startDate, $endDate);
|
|
|
|
expect($stats['updates'])->toBe(5);
|
|
});
|
|
|
|
// ===========================================
|
|
// Service Tests - Post Statistics
|
|
// ===========================================
|
|
|
|
test('post statistics count published posts in month', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$startDate = $targetMonth->copy()->startOfMonth();
|
|
$endDate = $targetMonth->copy()->endOfMonth();
|
|
|
|
Post::factory()->count(4)->published()->create([
|
|
'published_at' => $targetMonth,
|
|
]);
|
|
Post::factory()->count(2)->published()->create([
|
|
'published_at' => now()->subMonths(3),
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$stats = $service->getPostStats($startDate, $endDate);
|
|
|
|
expect($stats['this_month'])->toBe(4);
|
|
expect($stats['total'])->toBe(6);
|
|
});
|
|
|
|
// ===========================================
|
|
// Service Tests - Previous Month Comparison
|
|
// ===========================================
|
|
|
|
test('previous month comparison returns null when no prior data exists', function () {
|
|
$targetMonth = now()->subMonth()->startOfMonth();
|
|
|
|
$service = new MonthlyReportService;
|
|
$comparison = $service->getPreviousMonthComparison($targetMonth);
|
|
|
|
expect($comparison)->toBeNull();
|
|
});
|
|
|
|
test('previous month comparison returns data when prior month has data', function () {
|
|
$targetMonth = now()->subMonth();
|
|
$previousMonth = now()->subMonths(2);
|
|
|
|
// Create data in the previous month
|
|
User::factory()->count(3)->individual()->create([
|
|
'created_at' => $previousMonth,
|
|
]);
|
|
Consultation::factory()->count(5)->create([
|
|
'booking_date' => $previousMonth,
|
|
]);
|
|
|
|
$service = new MonthlyReportService;
|
|
$comparison = $service->getPreviousMonthComparison($targetMonth->startOfMonth());
|
|
|
|
expect($comparison)->not->toBeNull();
|
|
expect($comparison['consultations'])->toBe(5);
|
|
expect($comparison['clients'])->toBe(3);
|
|
});
|
|
|
|
// ===========================================
|
|
// Language Tests
|
|
// ===========================================
|
|
|
|
test('report respects admin language preference for Arabic', function () {
|
|
$adminArabic = User::factory()->admin()->create(['preferred_language' => 'ar']);
|
|
|
|
$this->actingAs($adminArabic);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->assertSee(__('report.monthly_report', [], 'ar'));
|
|
});
|
|
|
|
test('report respects admin language preference for English', function () {
|
|
$adminEnglish = User::factory()->admin()->create(['preferred_language' => 'en']);
|
|
|
|
app()->setLocale('en');
|
|
$this->actingAs($adminEnglish);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->assertSee('Monthly Statistics Report');
|
|
});
|
|
|
|
// ===========================================
|
|
// UI Elements Tests
|
|
// ===========================================
|
|
|
|
test('monthly report page shows table of contents preview', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->assertSee(__('report.table_of_contents'))
|
|
->assertSee(__('report.executive_summary'))
|
|
->assertSee(__('report.user_statistics'))
|
|
->assertSee(__('report.consultation_statistics'))
|
|
->assertSee(__('report.timeline_statistics'))
|
|
->assertSee(__('report.post_statistics'))
|
|
->assertSee(__('report.trends_chart'));
|
|
});
|
|
|
|
test('monthly report page shows generate button', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->assertSee(__('report.generate'));
|
|
});
|
|
|
|
test('monthly report page shows period selector', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.reports.monthly-report')
|
|
->assertSee(__('report.select_period'));
|
|
});
|