From b655aac575222b0dbe98d4baeb6d7ab3522f7b58 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 1 Sep 2026 16:19:44 +0200 Subject: [PATCH] fix: Rewind iterator before calling current/next Signed-off-by: Carl Schwan --- lib/Command/Watch.php | 2 - lib/Controller/LogController.php | 3 +- tests/Unit/Controller/LogControllerTest.php | 96 +++++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 tests/Unit/Controller/LogControllerTest.php diff --git a/lib/Command/Watch.php b/lib/Command/Watch.php index 5f5c5975..3084a551 100644 --- a/lib/Command/Watch.php +++ b/lib/Command/Watch.php @@ -44,7 +44,6 @@ protected function configure() { private function getLastLogId() { $logIterator = $this->logIteratorFactory->getLogIterator(self::ALL_LEVELS); - $logIterator->next(); if ($logIterator->current() !== null) { return $logIterator->current()['reqId']; } @@ -74,7 +73,6 @@ public function watch(bool $raw, OutputInterface $output): int { $id = $this->getLastLogId(); if ($id !== $lastId) { $iterator = $this->logIteratorFactory->getLogIterator(self::ALL_LEVELS); - $iterator->next(); $lines = []; diff --git a/lib/Controller/LogController.php b/lib/Controller/LogController.php index 018e9358..486c1c29 100644 --- a/lib/Controller/LogController.php +++ b/lib/Controller/LogController.php @@ -71,6 +71,7 @@ public function get($query = '', $count = 50, $offset = 0): JSONResponse { */ private function getLastItem() { $iterator = $this->logIteratorFactory->getLogIterator($this->settingsService->getShownLevels()); + $iterator->rewind(); return $iterator->current(); } @@ -102,7 +103,7 @@ public function poll(string $lastReqId): JSONResponse { } $iterator = $this->logIteratorFactory->getLogIterator($this->settingsService->getShownLevels()); - $iterator->next(); + $iterator->rewind(); $data = []; diff --git a/tests/Unit/Controller/LogControllerTest.php b/tests/Unit/Controller/LogControllerTest.php new file mode 100644 index 00000000..635564b5 --- /dev/null +++ b/tests/Unit/Controller/LogControllerTest.php @@ -0,0 +1,96 @@ +logIteratorFactory = $this->createStub(LogIteratorFactory::class); + $this->settingsService = $this->createStub(SettingsService::class); + $this->logger = $this->createStub(LoggerInterface::class); + $this->request = $this->createStub(IRequest::class); + + $this->settingsService->method('getLoggingType')->willReturn('file'); + $this->settingsService->method('getShownLevels')->willReturn([0, 1, 2, 3, 4]); + + $this->logController = new LogController( + 'logreader', + $this->request, + $this->logIteratorFactory, + $this->settingsService, + $this->logger, + ); + } + + private function getLogIterator(string $log): LogIterator { + $handle = fopen('php://temp', 'r+'); + fwrite($handle, $log); + rewind($handle); + return new LogIterator($handle, \DateTime::ATOM, 'UTC'); + } + + /** + * Every call to the factory must return a freshly rewound iterator over + * the same log content, mirroring how the real factory opens the log + * file anew for each call. + */ + private function mockLogWithEntries(string $log): void { + $this->logIteratorFactory->method('getLogIterator') + ->willReturnCallback(fn () => $this->getLogIterator($log)); + } + + public function testPollIncludesTheNewestLogEntry(): void { + $log = '{"reqId":"1","level":3,"time":"2019-11-04T18:50:57+00:00","app":"comments"}' . "\n" + . '{"reqId":"2","level":3,"time":"2019-11-04T18:50:58+00:00","app":"gallery"}' . "\n" + . '{"reqId":"3","level":3,"time":"2019-11-04T18:50:59+00:00","app":"files"}'; + $this->mockLogWithEntries($log); + + $response = $this->logController->poll('1'); + $data = $response->getData(); + + $this->assertCount(2, $data); + // sorted newest first, the last written entry (reqId 3) must be included + $this->assertEquals('3', $data[0]['reqId']); + $this->assertEquals('2', $data[1]['reqId']); + } + + public function testPollReturnsEmptyWhenThereIsNoNewEntry(): void { + $log = '{"reqId":"1","level":3,"time":"2019-11-04T18:50:57+00:00","app":"comments"}'; + $this->mockLogWithEntries($log); + + $response = $this->logController->poll('1'); + + $this->assertEquals([], $response->getData()); + } +}