diff options
Diffstat (limited to 'tests/lib/LoggerTest.php')
-rw-r--r-- | tests/lib/LoggerTest.php | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/tests/lib/LoggerTest.php b/tests/lib/LoggerTest.php new file mode 100644 index 00000000000..7ad6638537f --- /dev/null +++ b/tests/lib/LoggerTest.php @@ -0,0 +1,309 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test; + +use OC\Log; +use OC\SystemConfig; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Log\IWriter; +use OCP\Support\CrashReport\IRegistry; +use PHPUnit\Framework\MockObject\MockObject; + +class LoggerTest extends TestCase implements IWriter { + private SystemConfig&MockObject $config; + + private IRegistry&MockObject $registry; + + private Log $logger; + + /** @var array */ + private array $logs = []; + + protected function setUp(): void { + parent::setUp(); + + $this->logs = []; + $this->config = $this->createMock(SystemConfig::class); + $this->registry = $this->createMock(IRegistry::class); + $this->logger = new Log($this, $this->config, crashReporters: $this->registry); + } + + private function mockDefaultLogLevel(): void { + $this->config->expects($this->any()) + ->method('getValue') + ->willReturnMap([ + ['loglevel', ILogger::WARN, ILogger::WARN], + ]); + } + + public function testInterpolation(): void { + $this->mockDefaultLogLevel(); + $logger = $this->logger; + $logger->warning('{Message {nothing} {user} {foo.bar} a}', ['user' => 'Bob', 'foo.bar' => 'Bar']); + + $expected = ['2 {Message {nothing} Bob Bar a}']; + $this->assertEquals($expected, $this->getLogs()); + } + + public function testAppCondition(): void { + $this->config->expects($this->any()) + ->method('getValue') + ->willReturnMap([ + ['loglevel', ILogger::WARN, ILogger::WARN], + ['log.condition', [], ['apps' => ['files']]] + ]); + $logger = $this->logger; + + $logger->info('Don\'t display info messages'); + $logger->info('Show info messages of files app', ['app' => 'files']); + $logger->warning('Show warning messages of other apps'); + + $expected = [ + '1 Show info messages of files app', + '2 Show warning messages of other apps', + ]; + $this->assertEquals($expected, $this->getLogs()); + } + + public static function dataMatchesCondition(): array { + return [ + [ + 'user0', + [ + 'apps' => ['app2'], + ], + [ + '1 Info of app2', + ], + ], + [ + 'user2', + [ + 'users' => ['user1', 'user2'], + 'apps' => ['app1'], + ], + [ + '1 Info of app1', + ], + ], + [ + 'user3', + [ + 'users' => ['user3'], + ], + [ + '1 Info without app', + '1 Info of app1', + '1 Info of app2', + '0 Debug of app3', + ], + ], + [ + 'user4', + [ + 'users' => ['user4'], + 'apps' => ['app3'], + 'loglevel' => 0, + ], + [ + '0 Debug of app3', + ], + ], + [ + 'user4', + [ + 'message' => ' of ', + ], + [ + '1 Info of app1', + '1 Info of app2', + '0 Debug of app3', + ], + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataMatchesCondition')] + public function testMatchesCondition(string $userId, array $conditions, array $expectedLogs): void { + $this->config->expects($this->any()) + ->method('getValue') + ->willReturnMap([ + ['loglevel', ILogger::WARN, ILogger::WARN], + ['log.condition', [], ['matches' => [ + $conditions, + ]]], + ]); + $logger = $this->logger; + + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn($userId); + $userSession = $this->createMock(IUserSession::class); + $userSession->method('getUser') + ->willReturn($user); + $this->overwriteService(IUserSession::class, $userSession); + + $logger->info('Info without app'); + $logger->info('Info of app1', ['app' => 'app1']); + $logger->info('Info of app2', ['app' => 'app2']); + $logger->debug('Debug of app3', ['app' => 'app3']); + + $this->assertEquals($expectedLogs, $this->getLogs()); + } + + public function testLoggingWithDataArray(): void { + $this->mockDefaultLogLevel(); + /** @var IWriter&MockObject */ + $writerMock = $this->createMock(IWriter::class); + $logFile = new Log($writerMock, $this->config); + $writerMock->expects($this->once())->method('write')->with('no app in context', ['something' => 'extra', 'message' => 'Testing logging with john']); + $logFile->error('Testing logging with {user}', ['something' => 'extra', 'user' => 'john']); + } + + private function getLogs(): array { + return $this->logs; + } + + public function write(string $app, $message, int $level) { + $textMessage = $message; + if (is_array($message)) { + $textMessage = $message['message']; + } + $this->logs[] = $level . ' ' . $textMessage; + } + + public static function userAndPasswordData(): array { + return [ + ['mySpecialUsername', 'MySuperSecretPassword'], + ['my-user', '324324()#รค234'], + ['my-user', ')qwer'], + ['my-user', 'qwer)asdf'], + ['my-user', 'qwer)'], + ['my-user', '(qwer'], + ['my-user', 'qwer(asdf'], + ['my-user', 'qwer('], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('userAndPasswordData')] + public function testDetectlogin(string $user, string $password): void { + $this->mockDefaultLogLevel(); + $e = new \Exception('test'); + $this->registry->expects($this->once()) + ->method('delegateReport') + ->with($e, ['level' => 3]); + + $this->logger->logException($e); + + $logLines = $this->getLogs(); + foreach ($logLines as $logLine) { + if (is_array($logLine)) { + $logLine = json_encode($logLine); + } + $this->assertStringNotContainsString($user, $logLine); + $this->assertStringNotContainsString($password, $logLine); + $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine); + } + } + + #[\PHPUnit\Framework\Attributes\DataProvider('userAndPasswordData')] + public function testDetectcheckPassword(string $user, string $password): void { + $this->mockDefaultLogLevel(); + $e = new \Exception('test'); + $this->registry->expects($this->once()) + ->method('delegateReport') + ->with($e, ['level' => 3]); + + $this->logger->logException($e); + + $logLines = $this->getLogs(); + foreach ($logLines as $logLine) { + if (is_array($logLine)) { + $logLine = json_encode($logLine); + } + $this->assertStringNotContainsString($user, $logLine); + $this->assertStringNotContainsString($password, $logLine); + $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine); + } + } + + #[\PHPUnit\Framework\Attributes\DataProvider('userAndPasswordData')] + public function testDetectvalidateUserPass(string $user, string $password): void { + $this->mockDefaultLogLevel(); + $e = new \Exception('test'); + $this->registry->expects($this->once()) + ->method('delegateReport') + ->with($e, ['level' => 3]); + + $this->logger->logException($e); + + $logLines = $this->getLogs(); + foreach ($logLines as $logLine) { + if (is_array($logLine)) { + $logLine = json_encode($logLine); + } + $this->assertStringNotContainsString($user, $logLine); + $this->assertStringNotContainsString($password, $logLine); + $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine); + } + } + + #[\PHPUnit\Framework\Attributes\DataProvider('userAndPasswordData')] + public function testDetecttryLogin(string $user, string $password): void { + $this->mockDefaultLogLevel(); + $e = new \Exception('test'); + $this->registry->expects($this->once()) + ->method('delegateReport') + ->with($e, ['level' => 3]); + + $this->logger->logException($e); + + $logLines = $this->getLogs(); + foreach ($logLines as $logLine) { + if (is_array($logLine)) { + $logLine = json_encode($logLine); + } + $this->assertStringNotContainsString($user, $logLine); + $this->assertStringNotContainsString($password, $logLine); + $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine); + } + } + + #[\PHPUnit\Framework\Attributes\DataProvider('userAndPasswordData')] + public function testDetectclosure(string $user, string $password): void { + $this->mockDefaultLogLevel(); + $a = function ($user, $password): void { + throw new \Exception('test'); + }; + $this->registry->expects($this->once()) + ->method('delegateReport'); + + try { + $a($user, $password); + } catch (\Exception $e) { + $this->logger->logException($e); + } + + $logLines = $this->getLogs(); + foreach ($logLines as $logLine) { + if (is_array($logLine)) { + $logLine = json_encode($logLine); + } + $log = explode('\n', $logLine); + unset($log[1]); // Remove `testDetectclosure(` because we are not testing this here, but the closure on stack trace 0 + $logLine = implode('\n', $log); + + $this->assertStringNotContainsString($user, $logLine); + $this->assertStringNotContainsString($password, $logLine); + $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine); + } + } +} |