forget('dashboard:summary'); Cache::store('file')->forget('dashboard:summary_ts'); parent::tearDown(); } private function seedSummary(array $overrides = []): void { $payload = array_merge([ 'stats' => [ 'users_total' => 128, 'users_disabled' => 4, 'users_locked' => 2, 'pwd_never_expires' => 6, 'pwd_expiring_7d' => 3, 'pwd_expired' => 5, 'inactive_90d' => 9, 'account_expired' => 1, 'users_no_manager' => 7, 'computers_inactive_90d' => 3, ], 'pwd_age' => ['lt30' => 40, 'd30_60' => 30, 'd60_90' => 20, 'gt90' => 30, 'unknown' => 8], 'computers_total' => 44, 'groups_total' => 17, 'expiring_soon' => [['cn' => 'Jan Kowalski', 'sam' => 'jkowalski', 'date' => '2026-09-10']], 'inactive' => [], 'recent' => [], 'pwd_never_list' => [['cn' => 'Anna Nowak', 'sam' => 'anowak']], 'locked_list' => [['cn' => 'Piotr Lis', 'sam' => 'plis', 'since' => '2026-09-08 07:30']], 'no_manager_list' => [['cn' => 'Ewa Mak', 'sam' => 'emak']], 'computers_inactive' => [['cn' => 'WS-OLD-01', 'last' => '2026-01-02']], 'max_password_age_days' => 42, ], $overrides); Cache::store('file')->put('dashboard:summary', $payload, 300); Cache::store('file')->put('dashboard:summary_ts', now()->subMinutes(2)->format('Y-m-d H:i:s'), 300); } public function test_it_renders_clickable_and_alerting_tiles(): void { $this->seedSummary(); $res = $this->actingAsPanelAdmin()->get('/dashboard')->assertOk(); // Klikalne kafelki prowadzą do przefiltrowanej listy (href w HTML ma &, // więc porównujemy z domyślnym escapowaniem oczekiwanego łańcucha). $res->assertSee(route('users.index', ['all_ou' => 1, 'filter' => 'locked'])); $res->assertSee(route('users.index', ['all_ou' => 1, 'filter' => 'pwd_expired'])); $res->assertSee(route('users.index', ['all_ou' => 1, 'status' => 'disabled'])); $res->assertSee(route('computers.index', ['all_ou' => 1])); // Kafelek "z problemem" (locked > 0) dostaje wyróżniającą klasę. $res->assertSee('dashboard-tile-alert', escape: false); // Histogram wieku hasła i wiek danych. $res->assertSee(__('dashboard.pwd_age.heading')); $res->assertSee(__('dashboard.reports.expiring_date')); } public function test_it_shows_a_global_empty_state_when_ldap_returned_nothing(): void { $this->seedSummary([ 'stats' => [ 'users_total' => 0, 'users_disabled' => 0, 'users_locked' => 0, 'pwd_never_expires' => 0, 'pwd_expiring_7d' => 0, 'pwd_expired' => 0, 'inactive_90d' => 0, 'account_expired' => 0, ], 'pwd_age' => ['lt30' => 0, 'd30_60' => 0, 'd60_90' => 0, 'gt90' => 0, 'unknown' => 0], 'computers_total' => 0, 'groups_total' => 0, 'expiring_soon' => [], ]); $this->actingAsPanelAdmin()->get('/dashboard') ->assertOk() ->assertSee(__('dashboard.empty')) ->assertDontSee('dashboard-tile', escape: false); } public function test_it_renders_the_new_signal_cards_and_actions(): void { $this->seedSummary(); $res = $this->actingAsPanelAdmin()->get('/dashboard')->assertOk(); // Nowe kafelki + ich linki do przefiltrowanych list. $res->assertSee(route('users.index', ['all_ou' => 1, 'filter' => 'no_manager'])); $res->assertSee(route('computers.index', ['all_ou' => 1, 'inactive' => 1])); // Nowe karty raportów. $res->assertSee(__('dashboard.reports.pwd_never')); $res->assertSee(__('dashboard.reports.locked')); $res->assertSee(__('dashboard.reports.no_manager')); $res->assertSee(__('dashboard.reports.computers_inactive')); // Wiersze z list. $res->assertSee('anowak')->assertSee('plis')->assertSee('WS-OLD-01'); // Akcja "odblokuj" w karcie zablokowanych + "resetuj" w karcie wygasających. $res->assertSee(route('users.unlock', 'plis')); $res->assertSee(route('users.index', ['all_ou' => 1, 'q' => 'jkowalski', 'open' => 'reset'])); } public function test_preferences_endpoint_stores_layout_cookie(): void { $this->actingAsPanelAdmin()->post('/dashboard/preferences', [ 'section_tiles' => '0', 'section_pwd_age' => '1', 'report_order' => ['r_recent', 'r_expiring', 'r_locked'], 'visible' => ['r_recent', 'r_expiring'], ]) ->assertRedirect(route('dashboard.index')) ->assertCookie('dashboard_layout'); } public function test_layout_cookie_hides_and_reorders_report_cards(): void { $this->seedSummary([ 'recent' => [['cn' => 'Nowe Konto', 'sam' => 'nowe', 'created' => '2026-09-07']], ]); $layout = json_encode([ 'tiles' => false, 'pwd_age' => true, 'reports' => [ ['key' => 'r_recent', 'visible' => true], ['key' => 'r_expiring', 'visible' => true], ['key' => 'r_locked', 'visible' => false], ], ]); $res = $this->actingAsPanelAdmin() ->withoutMiddleware(EncryptCookies::class) ->withUnencryptedCookie('dashboard_layout', $layout) ->get('/dashboard') ->assertOk(); // Ukryta sekcja kafelków. $res->assertDontSee('dashboard-tile', escape: false); // Ukryta karta "zablokowane" - jej wiersz i nagłówek kolumny nie występują // (sama nazwa raportu jest jeszcze na liście w oknie preferencji). $res->assertDontSee('Piotr Lis'); $res->assertDontSee(__('dashboard.reports.locked_since')); // Karta "Nowe konta" przed "Hasła wygasające" - nagłówki kolumn dat // występują tylko w kartach (nie w oknie preferencji). $res->assertSeeInOrder([ __('dashboard.reports.recent_date'), __('dashboard.reports.expiring_date'), ]); } public function test_stale_cache_is_flagged(): void { $this->seedSummary(); Cache::store('file')->put('dashboard:summary_ts', now()->subHour()->format('Y-m-d H:i:s'), 300); $this->actingAsPanelAdmin()->get('/dashboard') ->assertOk() ->assertSee('text-bg-warning', escape: false); } }