118 lines
5.5 KiB
PHP
118 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Livewire\Audit\Index;
|
|
use App\Models\AuditEvent;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
class AuditLogDisplayTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->actingAs(User::factory()->create(['must_change_password' => false]));
|
|
}
|
|
|
|
public function test_action_label_maps_a_known_code(): void
|
|
{
|
|
// Default locale is German; the raw code maps to a readable label.
|
|
$this->assertSame('WireGuard-Endpoint geändert', (new AuditEvent(['action' => 'wg.set-endpoint']))->action_label);
|
|
}
|
|
|
|
public function test_action_label_falls_back_to_a_tidied_form_for_unknown_codes(): void
|
|
{
|
|
// Dynamic harden.* codes are not individually mapped → tidied, never a bare machine code.
|
|
$e = new AuditEvent(['action' => 'harden.ssh_password.off']);
|
|
$this->assertSame('Harden · ssh password · off', $e->action_label);
|
|
}
|
|
|
|
public function test_is_error_flags_failure_and_security_events(): void
|
|
{
|
|
$this->assertTrue((new AuditEvent(['action' => 'auth.login_failed']))->is_error);
|
|
$this->assertTrue((new AuditEvent(['action' => 'wg.action-failed']))->is_error);
|
|
$this->assertFalse((new AuditEvent(['action' => 'wg.set-endpoint']))->is_error);
|
|
}
|
|
|
|
public function test_page_renders_the_readable_label_not_the_raw_code(): void
|
|
{
|
|
AuditEvent::create(['actor' => 'Administrator', 'action' => 'wg.set-endpoint', 'target' => '1.2.3.4:51820', 'ip' => '127.0.0.1']);
|
|
|
|
Livewire::test(Index::class)
|
|
->assertSee('WireGuard-Endpoint geändert')
|
|
->assertDontSee('wg.set-endpoint');
|
|
}
|
|
|
|
public function test_honeypot_events_render_disguised_and_never_leak_the_deception(): void
|
|
{
|
|
// OPSEC: the audit log is readable by every role, so the honeypot/deception action labels must
|
|
// NOT reveal that the decoy paths are a trap. The DB action keys stay security.honeypot_* (for
|
|
// the Threats intel + history), but the VISIBLE label reads like generic WAF/IDS threat logging.
|
|
AuditEvent::create(['actor' => 'system', 'action' => 'security.honeypot_hit', 'target' => '/wp-admin', 'ip' => '203.0.113.9']);
|
|
AuditEvent::create(['actor' => 'system', 'action' => 'security.honeypot_login', 'target' => '/wp-login.php', 'ip' => '203.0.113.9']);
|
|
AuditEvent::create(['actor' => 'system', 'action' => 'security.honeytoken_used', 'target' => 'db_password', 'ip' => '203.0.113.9']);
|
|
|
|
Livewire::test(Index::class)
|
|
->assertSee('Angriffsversuch') // honeypot_hit → disguised
|
|
->assertSee('Login-Angriffsversuch') // honeypot_login → disguised
|
|
->assertSee('Kompromittierte Zugangsdaten') // honeytoken_used → disguised
|
|
->assertDontSee('Honeypot') // the word never reaches the audit log
|
|
->assertDontSee('Honeytoken')
|
|
->assertDontSee('honeypot'); // nor the raw action key
|
|
}
|
|
|
|
public function test_search_matches_the_readable_label(): void
|
|
{
|
|
AuditEvent::create(['actor' => 'Administrator', 'action' => 'wg.set-endpoint', 'target' => 'x', 'ip' => '127.0.0.1']);
|
|
AuditEvent::create(['actor' => 'Administrator', 'action' => 'user.create', 'target' => 'y', 'ip' => '127.0.0.1']);
|
|
|
|
Livewire::test(Index::class)
|
|
->set('q', 'Endpoint geändert')
|
|
->assertSee('WireGuard-Endpoint geändert')
|
|
->assertDontSee('Benutzer angelegt');
|
|
}
|
|
|
|
public function test_paginates_the_audit_log_with_the_page_in_the_url(): void
|
|
{
|
|
for ($i = 0; $i < 30; $i++) {
|
|
AuditEvent::create(['actor' => 'A', 'action' => 'user.create', 'target' => "t{$i}", 'ip' => '127.0.0.1', 'created_at' => now()->subMinutes($i)]);
|
|
}
|
|
|
|
Livewire::test(Index::class)
|
|
->assertViewHas('events', fn ($e) => $e->total() === 30 && $e->count() === 25 && $e->currentPage() === 1)
|
|
->call('gotoPage', 2)
|
|
->assertViewHas('events', fn ($e) => $e->currentPage() === 2 && $e->count() === 5);
|
|
}
|
|
|
|
public function test_changing_the_search_resets_to_the_first_page(): void
|
|
{
|
|
for ($i = 0; $i < 30; $i++) {
|
|
AuditEvent::create(['actor' => 'A', 'action' => 'user.create', 'target' => "t{$i}", 'ip' => '127.0.0.1', 'created_at' => now()->subMinutes($i)]);
|
|
}
|
|
|
|
Livewire::test(Index::class)
|
|
->call('gotoPage', 2)
|
|
->set('q', 'user')
|
|
->assertViewHas('events', fn ($e) => $e->currentPage() === 1);
|
|
}
|
|
|
|
public function test_search_filters_in_sql_across_all_pages_not_just_one(): void
|
|
{
|
|
// 25 filler (fills page 1) + one older match that would otherwise sit on page 2 unfiltered.
|
|
for ($i = 0; $i < 25; $i++) {
|
|
AuditEvent::create(['actor' => 'A', 'action' => 'user.create', 'target' => "t{$i}", 'ip' => '127.0.0.1', 'created_at' => now()->subMinutes($i)]);
|
|
}
|
|
AuditEvent::create(['actor' => 'A', 'action' => 'wg.set-endpoint', 'target' => 'x', 'ip' => '127.0.0.1', 'created_at' => now()->subMinutes(99)]);
|
|
|
|
Livewire::test(Index::class)
|
|
->set('q', 'Endpoint geändert') // label search → SQL-matches the single entry, now on page 1
|
|
->assertViewHas('events', fn ($e) => $e->total() === 1)
|
|
->assertSee('WireGuard-Endpoint geändert');
|
|
}
|
|
}
|