diff --git a/composer.json b/composer.json index 2c4ef7bf..f113980d 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,9 @@ "require-dev": { "brainmaestro/composer-git-hooks": "^3.0", "friendsofphp/php-cs-fixer": "^3.2 != 3.7.0", + "jangregor/phpstan-prophecy": "^1.0", "jetbrains/phpstorm-attributes": "^1.0", + "mikey179/vfsstream": "^1.6", "phpspec/prophecy": "1.x-dev", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.1", diff --git a/src/ZM/Store/FileSystem.php b/src/ZM/Store/FileSystem.php index 0ff2e4a9..f8edf949 100644 --- a/src/ZM/Store/FileSystem.php +++ b/src/ZM/Store/FileSystem.php @@ -129,9 +129,6 @@ class FileSystem $tokens = \PhpToken::tokenize(file_get_contents($path)); $found = false; foreach ($tokens as $token) { - if (!$token instanceof \PhpToken) { - continue; - } if ($token->is(T_CLASS)) { $found = true; break; diff --git a/src/ZM/Utils/CatCode.php b/src/ZM/Utils/CatCode.php index 15d6b6e8..8bb84227 100644 --- a/src/ZM/Utils/CatCode.php +++ b/src/ZM/Utils/CatCode.php @@ -11,7 +11,7 @@ class CatCode /** * 从 MessageSegment 转换为 CatCode 字符串 */ - public static function fromSegment(mixed $message_segment): string + public static function fromSegment(array|MessageSegment|string $message_segment): string { // 传入的必须是段数组或段对象 if (is_array($message_segment)) { @@ -24,13 +24,12 @@ class CatCode } return $str; } + if ($message_segment instanceof MessageSegment) { return self::segment2CatCode($message_segment); } - if (is_string($message_segment)) { - return $message_segment; - } - return ''; + + return $message_segment; } /** diff --git a/tests/Trait/HasLogger.php b/tests/Trait/HasLogger.php index 246086d0..8d4d6774 100644 --- a/tests/Trait/HasLogger.php +++ b/tests/Trait/HasLogger.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Tests\Trait; +use Prophecy\Argument; use Prophecy\Prophet; use Psr\Log\AbstractLogger; +use Psr\Log\LogLevel; /** * 模拟 Logger 行为 @@ -36,9 +38,21 @@ trait HasLogger private function startMockLogger(): void { $logger = $this->prophet->prophesize(AbstractLogger::class); - $logger->log()->will(function ($args) { - $this->mockLog(...$args); - }); + $levels = [ + LogLevel::EMERGENCY, + LogLevel::ALERT, + LogLevel::CRITICAL, + LogLevel::ERROR, + LogLevel::WARNING, + LogLevel::NOTICE, + LogLevel::INFO, + LogLevel::DEBUG, + ]; + $log_it = fn (...$args) => $this->mockLog(...$args); + foreach ($levels as $level) { + $logger->{$level}(Argument::type('string'), Argument::any())->will(fn ($args) => $log_it($level, ...$args)); + } + $logger->log(Argument::in($levels), Argument::type('string'), Argument::any())->will(fn ($args) => $log_it(...$args)); ob_logger_register($logger->reveal()); } } diff --git a/tests/Trait/HasVirtualFileSystem.php b/tests/Trait/HasVirtualFileSystem.php new file mode 100644 index 00000000..dea54a62 --- /dev/null +++ b/tests/Trait/HasVirtualFileSystem.php @@ -0,0 +1,18 @@ +vfs = vfsStream::setup($dir); + } +} diff --git a/tests/ZM/Store/FileSystemTest.php b/tests/ZM/Store/FileSystemTest.php new file mode 100644 index 00000000..fa625ef7 --- /dev/null +++ b/tests/ZM/Store/FileSystemTest.php @@ -0,0 +1,133 @@ +setUpVfs(); + $this->startMockLogger(); + } + + /** + * @dataProvider provideTestDetermineRelativePath + */ + public function testDetermineRelativePath(string $path, bool $expected): void + { + $this->assertSame($expected, FileSystem::isRelativePath($path)); + } + + public function provideTestDetermineRelativePath(): array + { + return [ + 'relative' => ['relative/path', true], + 'absolute' => ['/absolute/path', false], + 'windows' => ['C:\Windows', !(DIRECTORY_SEPARATOR === '\\')], + ]; + } + + public function testCreateDirectoryWithNoPerm(): void + { + $old_perm = $this->vfs->getPermissions(); + $this->vfs->chmod(0000); + $this->assertFalse($this->vfs->hasChild('test')); + $this->expectExceptionMessageMatches('/无法建立目录/'); + FileSystem::createDir($this->vfs->url() . '/test'); + $this->vfs->chmod($old_perm); + } + + public function testCreateDirectory(): void + { + $this->assertFalse($this->vfs->hasChild('test')); + FileSystem::createDir($this->vfs->url() . '/test'); + $this->assertTrue($this->vfs->hasChild('test')); + } + + public function testGetReloadableFiles(): void + { + $files = FileSystem::getReloadableFiles(); + $this->assertIsArray($files); + $this->assertNotEmpty($files); + } + + public function testGetClassesPsr4(): void + { + vfsStream::create([ + 'Foo' => [ + 'Bar.php' => ' ' [ + 'Quux.php' => ' [ + 'global.php' => ' ' '', + ], $this->vfs); + $classes = FileSystem::getClassesPsr4($this->vfs->url(), ''); + $this->assertSame([ + '\Foo\Bar', + '\Foo\Qux\Quux', + ], $classes); + } + + public function testGetClassesPsr4WithCustomRule(): void + { + vfsStream::create([ + 'Foo' => [ + 'Bar.php' => ' 'vfs); + $classes = FileSystem::getClassesPsr4($this->vfs->url(), '', fn (string $dir, array $pathinfo) => $pathinfo['filename'] === 'Bar'); + $this->assertSame(['\Foo\Bar'], $classes); + } + + public function testGetClassesPsr4WithReturnPath(): void + { + vfsStream::create([ + 'Foo' => [ + 'Bar.php' => ' 'vfs); + $classes = FileSystem::getClassesPsr4($this->vfs->url(), '', return_path_value: 'my_path'); + $this->assertSame([ + '\Foo\Bar' => 'my_path/Foo/Bar.php', + '\Foo\Baz' => 'my_path/Foo/Baz.php', + ], $classes); + } + + public function testScanDirFilesWithNotExistsDir(): void + { + FileSystem::scanDirFiles($this->vfs->url() . '/not_exists'); + $this->assertLogged('warning', zm_internal_errcode('E00080') . '扫描目录失败,目录不存在'); + } + + public function testScanDirFilesWithNoPerm(): void + { + $old_perm = $this->vfs->getPermissions(); + $this->vfs->chmod(0000); + FileSystem::scanDirFiles($this->vfs->url()); + $this->assertLogged('warning', zm_internal_errcode('E00080') . '扫描目录失败,目录无法读取: ' . $this->vfs->url()); + $this->vfs->chmod($old_perm); + } +} diff --git a/tests/ZM/Utils/CatCodeTest.php b/tests/ZM/Utils/CatCodeTest.php new file mode 100644 index 00000000..7a5e808a --- /dev/null +++ b/tests/ZM/Utils/CatCodeTest.php @@ -0,0 +1,74 @@ +assertSame($expected, CatCode::fromSegment($segment)); + } + + public function provideTestConvertFromSegment(): array + { + return [ + 'string' => ['[CatCode:mention,user_id=123456789]', '[CatCode:mention,user_id=123456789]'], + 'segment instance' => [new MessageSegment('mention', ['user_id' => '123456789']), '[CatCode:mention,user_id=123456789]'], + 'multiple segment instance' => [ + [ + new MessageSegment('mention', ['user_id' => '123456789']), + new MessageSegment('text', ['text' => 'Hello']), + ], + '[CatCode:mention,user_id=123456789]Hello', + ], + 'array contains non-segment' => [ + [ + new MessageSegment('mention', ['user_id' => '123456789']), + 'Hello', + ], + '', + ], + ]; + } + + /** + * @dataProvider provideTestEscapeText + */ + public function testEscapeText(string $text): void + { + $encoded = CatCode::encode($text); + $decoded = CatCode::decode($encoded); + $this->assertSame($text, $decoded); + } + + public function provideTestEscapeText(): array + { + return [ + ["前缀是'['后缀是']', 还有以及一个特殊的&"], + ["[前缀是'['后缀是']', 还有以及一个特殊的&"], + ["前缀是'['后缀是']', 还有以及一个特殊的"], + ["&前缀是'['后缀是']', 还有以及一个特殊的"], + ["&前缀是'['后缀是']', 还有以及一个特殊的]"], + ]; + } + + public function testEscapeTextForContent(): void + { + $text = "前缀是'['后缀是']', 还有以及一个特殊的&"; + $encoded = CatCode::encode($text, true); + $decoded = CatCode::decode($encoded, true); + $this->assertSame($text, $decoded); + } +}