Bicycle/tests/Unit/LogTest.php
Egor Isaev b6a8133170 dev
2026-06-24 10:52:46 +03:00

138 lines
4.7 KiB
PHP

<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use System\Classes\Log;
class LogTest extends TestCase
{
private string $dir;
protected function setUp(): void
{
$this->dir = sys_get_temp_dir() . '/bicycle_log_' . uniqid();
Log::$directory = $this->dir;
Log::$threshold = Log::DEBUG;
}
protected function tearDown(): void
{
if (is_dir($this->dir)) {
foreach (glob($this->dir . '/*') as $file) {
unlink($file);
}
rmdir($this->dir);
}
Log::$directory = null;
Log::$threshold = null;
}
private function logFile(string $channel): string
{
return $this->dir . '/' . $channel . '-' . date('Y-m-d') . '.log';
}
public function testWriteCreatesFileWithFormattedLine(): void
{
Log::info('Привет');
$this->assertFileExists($this->logFile('action'));
$content = file_get_contents($this->logFile('action'));
$this->assertMatchesRegularExpression('/^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] INFO: Привет\n/', $content);
}
public function testChannelsSplitErrorsFromActions(): void
{
Log::info('действие');
Log::error('сбой');
// Действия — в action-файле, ошибки — в error-файле, не смешаны.
$action = file_get_contents($this->logFile('action'));
$error = file_get_contents($this->logFile('error'));
$this->assertStringContainsString('INFO: действие', $action);
$this->assertStringNotContainsString('сбой', $action);
$this->assertStringContainsString('ERROR: сбой', $error);
$this->assertStringNotContainsString('действие', $error);
}
public function testWarningGoesToErrorChannel(): void
{
Log::warning('предупреждение');
$this->assertFileDoesNotExist($this->logFile('action'));
$this->assertStringContainsString('WARNING: предупреждение', file_get_contents($this->logFile('error')));
}
public function testThresholdFiltersLowerLevels(): void
{
Log::$threshold = Log::WARNING;
Log::debug('низкий');
Log::info('низкий');
$this->assertFileDoesNotExist($this->logFile('action'));
Log::warning('высокий');
Log::error('высокий');
$content = file_get_contents($this->logFile('error'));
$this->assertStringContainsString('WARNING: высокий', $content);
$this->assertStringContainsString('ERROR: высокий', $content);
}
public function testShortcutsUseCorrectLevelLabel(): void
{
Log::debug('d');
Log::info('i');
Log::warning('w');
Log::error('e');
$action = file_get_contents($this->logFile('action'));
$error = file_get_contents($this->logFile('error'));
$this->assertStringContainsString('DEBUG: d', $action);
$this->assertStringContainsString('INFO: i', $action);
$this->assertStringContainsString('WARNING: w', $error);
$this->assertStringContainsString('ERROR: e', $error);
}
public function testStrtrSubstitution(): void
{
Log::info('Пользователь :id вошёл', [':id' => 42]);
$this->assertStringContainsString('Пользователь 42 вошёл', file_get_contents($this->logFile('action')));
}
public function testAppendsWithoutOverwriting(): void
{
Log::info('первая');
Log::info('вторая');
$content = file_get_contents($this->logFile('action'));
$this->assertStringContainsString('первая', $content);
$this->assertStringContainsString('вторая', $content);
$this->assertSame(2, substr_count($content, "\n"));
}
public function testRequestInfoEmptyWithoutCurrentRequest(): void
{
// Request::$current не установлен в юнит-окружении.
$this->assertSame('', Log::requestInfo());
}
public function testMaskHidesSensitiveKeys(): void
{
$method = new \ReflectionMethod(Log::class, 'mask');
$method->setAccessible(true);
$result = $method->invoke(null, [
'email' => 'a@b.com',
'password' => 'secret',
'csrf_token' => 'abc123',
'CSRF_TOKEN' => 'xyz', // регистр не важен
]);
$this->assertSame('a@b.com', $result['email']);
$this->assertSame('***', $result['password']);
$this->assertSame('***', $result['csrf_token']);
$this->assertSame('***', $result['CSRF_TOKEN']);
}
}