setAccessible(true); return $m->invoke(app(FleetService::class), $body); } public function test_split_pulls_the_trailing_cursor_off_the_body(): void { $body = "2026-06-14T10:00:01+02:00 host nginx[1]: started\n-- cursor: s=abc123;i=4f;b=9"; [$kept, $cursor] = $this->invokePrivate('splitJournalCursor', $body); $this->assertSame('s=abc123;i=4f;b=9', $cursor); $this->assertStringNotContainsString('-- cursor:', $kept); $this->assertStringContainsString('nginx[1]: started', $kept); } public function test_split_returns_null_cursor_when_absent(): void { [, $cursor] = $this->invokePrivate('splitJournalCursor', '2026-06-14T10:00:01+02:00 host nginx[1]: started'); $this->assertNull($cursor); } public function test_parse_lines_classifies_levels_and_never_injects_a_placeholder(): void { $body = implode("\n", [ '2026-06-14T10:00:01+02:00 host nginx[1]: connection refused', '2026-06-14T10:00:02+02:00 host cron[9]: warning clock skew detected', '2026-06-14T10:00:03+02:00 host sshd[7]: accepted publickey', ]); $rows = $this->invokePrivate('parseJournalLines', $body); $this->assertCount(3, $rows); $this->assertSame('error', $rows[0]['level']); $this->assertSame('warn', $rows[1]['level']); $this->assertSame('info', $rows[2]['level']); $this->assertSame('2026-06-14 10:00:01', $rows[0]['time']); } public function test_parse_lines_on_empty_input_returns_empty_no_placeholder(): void { // Empty = "nothing new since the cursor", which must NOT look like a permission error. $this->assertSame([], $this->invokePrivate('parseJournalLines', '')); $this->assertSame([], $this->invokePrivate('parseJournalLines', '-- No entries --')); } }