create(['must_change_password' => false]); } private function operator(): User { return User::factory()->operator()->create(['must_change_password' => false]); } private function viewer(): User { return User::factory()->viewer()->create(['must_change_password' => false]); } // ── route gating (manage-panel = admin) ───────────────────────────────────── public function test_viewer_cannot_open_alerts(): void { $this->actingAs($this->viewer())->get('/alerts')->assertForbidden(); } public function test_operator_cannot_open_alerts(): void { $this->actingAs($this->operator())->get('/alerts')->assertForbidden(); } public function test_admin_can_open_alerts(): void { $this->actingAs($this->admin())->get('/alerts')->assertOk(); } // ── rule create ───────────────────────────────────────────────────────────── public function test_admin_creates_a_fleet_wide_numeric_rule(): void { $this->actingAs($this->admin()); Livewire::test(Index::class) ->set('name', 'CPU high') ->set('metric', 'cpu') ->set('comparator', 'gt') ->set('threshold', 90) ->set('scopeType', 'all') ->call('createRule') ->assertHasNoErrors(); $this->assertDatabaseHas('alert_rules', ['name' => 'CPU high', 'metric' => 'cpu', 'threshold' => 90, 'scope_type' => 'all', 'enabled' => true]); $this->assertDatabaseHas('audit_events', ['action' => 'alert.rule_create', 'target' => 'CPU high']); } public function test_group_scope_resolves_the_uuid_to_an_id(): void { $this->actingAs($this->admin()); $group = ServerGroup::create(['name' => 'prod', 'color' => 'online']); Livewire::test(Index::class) ->set('name', 'prod cpu') ->set('metric', 'cpu') ->set('scopeType', 'group') ->set('scopeUuid', $group->uuid) ->call('createRule') ->assertHasNoErrors(); $this->assertDatabaseHas('alert_rules', ['name' => 'prod cpu', 'scope_type' => 'group', 'scope_id' => $group->id]); } public function test_offline_metric_stores_threshold_zero(): void { $this->actingAs($this->admin()); Livewire::test(Index::class) ->set('name', 'down') ->set('metric', 'offline') ->set('threshold', 80) ->set('scopeType', 'all') ->call('createRule') ->assertHasNoErrors(); $this->assertDatabaseHas('alert_rules', ['name' => 'down', 'metric' => 'offline', 'threshold' => 0]); } public function test_create_rejects_an_unknown_metric(): void { $this->actingAs($this->admin()); Livewire::test(Index::class) ->set('name', 'x') ->set('metric', 'temperature') ->call('createRule') ->assertHasErrors('metric'); $this->assertDatabaseCount('alert_rules', 0); } // ── toggle / delete ───────────────────────────────────────────────────────── public function test_toggle_flips_enabled(): void { $this->actingAs($this->admin()); $rule = AlertRule::create(['name' => 'r', 'metric' => 'cpu', 'comparator' => 'gt', 'threshold' => 80, 'scope_type' => 'all', 'enabled' => true]); Livewire::test(Index::class)->call('toggleRule', $rule->uuid); $this->assertFalse($rule->fresh()->enabled); } public function test_admin_deletes_a_rule_via_a_confirmed_token(): void { $this->actingAs($this->admin()); $rule = AlertRule::create(['name' => 'gone', 'metric' => 'cpu', 'comparator' => 'gt', 'threshold' => 80, 'scope_type' => 'all', 'enabled' => true]); $token = ConfirmToken::issue('alertRuleDeleted', ['uuid' => $rule->uuid], 'alert.rule_delete', $rule->name, null); ConfirmToken::confirm($token); Livewire::test(Index::class)->call('deleteRule', $token)->assertOk(); $this->assertDatabaseMissing('alert_rules', ['id' => $rule->id]); } // ── channels ──────────────────────────────────────────────────────────────── public function test_admin_saves_notification_channels(): void { $this->actingAs($this->admin()); Livewire::test(Index::class) ->set('emailTo', "ops@example.com\n") ->set('webhooks', 'https://8.8.8.8/x') // public IP literal — passes the SSRF host check ->call('saveChannels'); $this->assertSame('ops@example.com', Setting::get('alert_email_to')); $this->assertSame('https://8.8.8.8/x', Setting::get('alert_webhooks')); $this->assertDatabaseHas('audit_events', ['action' => 'alert.channels_update']); } public function test_save_channels_rejects_an_unsafe_webhook_url(): void { $this->actingAs($this->admin()); Livewire::test(Index::class) ->set('webhooks', 'http://169.254.169.254/latest/meta-data') // SSRF to cloud metadata ->call('saveChannels') ->assertHasErrors('webhooks'); $this->assertNull(Setting::get('alert_webhooks')); // not stored } // ── incidents render ──────────────────────────────────────────────────────── public function test_active_incidents_are_shown(): void { $this->actingAs($this->admin()); $rule = AlertRule::create(['name' => 'CPU high', 'metric' => 'cpu', 'comparator' => 'gt', 'threshold' => 80, 'scope_type' => 'all', 'enabled' => true]); $server = Server::create(['name' => 'web1', 'ip' => '10.0.0.1', 'ssh_port' => 22, 'status' => 'online']); AlertIncident::create(['alert_rule_id' => $rule->id, 'server_id' => $server->id, 'state' => 'firing', 'value' => 95, 'started_at' => now()]); Livewire::test(Index::class) ->assertSee('CPU high') ->assertSee('web1'); } }