bootApp(); $_SESSION['_user'] = (new UserContext(1, 'Admin', 'admin', null, UserContext::SOURCE_LOCAL))->toArray(); $_SESSION['_last_active'] = time(); $_SESSION['_authenticated_at'] = time(); } public function testDashboardRendersStatsAndChartCanvas(): void { $this->enqueueApiResponse(200, [ 'active_blocks' => 12, 'manual_blocks_count' => 3, 'allowlist_count' => 1, 'reports_24h' => 42, 'reports_24h_by_hour' => [ ['hour' => '2026-04-29T10:00:00Z', 'count' => 7], ['hour' => '2026-04-29T11:00:00Z', 'count' => 35], ], 'top_reporters_24h' => [['name' => 'web-prod-01', 'count' => 30]], 'top_categories_24h' => [['slug' => 'brute_force', 'count' => 25]], 'bans_by_day_7d' => [ ['day' => '2026-04-23', 'count' => 0], ['day' => '2026-04-24', 'count' => 2], ['day' => '2026-04-25', 'count' => 1], ['day' => '2026-04-26', 'count' => 0], ['day' => '2026-04-27', 'count' => 3], ['day' => '2026-04-28', 'count' => 5], ['day' => '2026-04-29', 'count' => 1], ], 'jobs_status' => [['name' => 'recompute-scores', 'last_finished_at' => '2026-04-29T10:55:00Z', 'status' => 'success', 'overdue' => false]], 'reference_policy' => 'moderate', ]); $response = $this->request('GET', '/app/dashboard'); self::assertSame(200, $response->getStatusCode()); $body = (string) $response->getBody(); self::assertStringContainsString('Dashboard', $body); self::assertStringContainsString('reports', $body); self::assertStringContainsString('id="reports-chart"', $body); self::assertStringContainsString('id="top-reporters-chart"', $body); self::assertStringContainsString('id="top-categories-chart"', $body); self::assertStringContainsString('id="bans-trend-chart"', $body); self::assertStringContainsString('web-prod-01', $body); self::assertStringContainsString('brute_force', $body); self::assertStringContainsString('recompute-scores', $body); self::assertStringContainsString('moderate', $body); } public function testDashboardDegradesWhenApiUnreachable(): void { $this->enqueueApiException(new \GuzzleHttp\Exception\ConnectException( 'down', new \GuzzleHttp\Psr7\Request('GET', '/'), )); $this->enqueueApiException(new \GuzzleHttp\Exception\ConnectException( 'down', new \GuzzleHttp\Psr7\Request('GET', '/'), )); $response = $this->request('GET', '/app/dashboard'); self::assertSame(200, $response->getStatusCode()); $body = (string) $response->getBody(); self::assertStringContainsString('Dashboard', $body); self::assertStringContainsString('API unreachable', $body); } }