.failed * entry with the complete output in meta) where the operator can re-read it. */ class ServiceActionAuditTest extends TestCase { use RefreshDatabase; protected function tearDown(): void { Mockery::close(); parent::tearDown(); } private function activeServer(): Server { $server = Server::create(['name' => 'box', 'ip' => '10.0.0.2', 'ssh_port' => 22, 'status' => 'online']); $server->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']); session(['active_server_id' => $server->id]); return $server; } private function confirmed(string $event, array $params, int $serverId): string { $token = ConfirmToken::issue($event, $params, '', null, $serverId); ConfirmToken::confirm($token); return $token; } public function test_failed_start_is_persisted_to_the_audit_log_with_the_full_reason(): void { $this->actingAs(User::factory()->create(['must_change_password' => false])); $server = $this->activeServer(); $reason = "monit.service: Failed with result 'exit-code'.\n" .'monit.service - Pro-active monitoring utility for unix systems: a very long detailed ' .'reason that would be truncated in a 90-char toast but must survive in full here.'; $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('serviceAction')->once()->andReturn(['ok' => false, 'output' => $reason]); $fleet->shouldReceive('systemd')->andReturn(['services' => []]); app()->instance(FleetService::class, $fleet); $token = $this->confirmed('serviceConfirmed', ['op' => 'start', 'name' => 'monit.service'], $server->id); Livewire::test(Services::class)->call('applyService', $token); $event = AuditEvent::where('action', 'service.start.failed')->first(); $this->assertNotNull($event, 'a service.start.failed audit event should be written'); $this->assertTrue($event->is_error); $this->assertSame($server->id, $event->server_id); // The FULL reason is retained (not truncated), so it is re-readable later. $this->assertStringContainsString('must survive in full here.', $event->meta['output']); $this->assertStringContainsString('Failed with result', $event->meta['output']); } public function test_successful_action_writes_no_failure_event(): void { $this->actingAs(User::factory()->create(['must_change_password' => false])); $server = $this->activeServer(); $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('serviceAction')->once()->andReturn(['ok' => true, 'output' => '']); $fleet->shouldReceive('systemd')->andReturn(['services' => []]); app()->instance(FleetService::class, $fleet); $token = $this->confirmed('serviceConfirmed', ['op' => 'restart', 'name' => 'nginx.service'], $server->id); Livewire::test(Services::class)->call('applyService', $token); $this->assertSame(0, AuditEvent::where('action', 'like', 'service.%.failed')->count()); } }