Files
Gnuboard7/tests/Unit/Extension/ManifestParsingTest.php
2026-04-01 10:30:52 +09:00

505 lines
17 KiB
PHP

<?php
namespace Tests\Unit\Extension;
use Tests\TestCase;
/**
* 매니페스트 파싱 테스트
*
* AbstractModule/AbstractPlugin의 loadManifest()가 JSON 매니페스트를
* 올바르게 파싱하고 메타데이터를 반환하는지 검증합니다.
*/
class ManifestParsingTest extends TestCase
{
private string $tempDir;
protected function setUp(): void
{
parent::setUp();
$this->tempDir = sys_get_temp_dir().'/g7_manifest_test_'.uniqid();
mkdir($this->tempDir, 0777, true);
}
protected function tearDown(): void
{
// 임시 디렉토리 정리
$this->removeDirectory($this->tempDir);
parent::tearDown();
}
/**
* 임시 디렉토리 재귀 삭제
*/
private function removeDirectory(string $dir): void
{
if (! is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir.'/'.$file;
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
}
rmdir($dir);
}
/**
* 모듈 테스트용 ConcreteModule 인스턴스를 생성합니다.
*/
private function createTestModule(string $dir, ?array $manifest = null): object
{
// module.json 생성
if ($manifest !== null) {
file_put_contents($dir.'/module.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// module.php 파일 생성 (동적 클래스)
$className = 'TestModule_'.md5($dir);
if (! class_exists($className)) {
$code = <<<PHP
class {$className} extends \App\Extension\AbstractModule {
protected function getModulePath(): string {
return '{$dir}';
}
}
PHP;
eval($code);
}
return new $className;
}
/**
* 플러그인 테스트용 ConcretePlugin 인스턴스를 생성합니다.
*/
private function createTestPlugin(string $dir, ?array $manifest = null): object
{
// plugin.json 생성
if ($manifest !== null) {
file_put_contents($dir.'/plugin.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// plugin.php 파일 생성 (동적 클래스)
$className = 'TestPlugin_'.md5($dir);
if (! class_exists($className)) {
$code = <<<PHP
class {$className} extends \App\Extension\AbstractPlugin {
protected function getPluginPath(): string {
return '{$dir}';
}
}
PHP;
eval($code);
}
return new $className;
}
// ===== AbstractModule loadManifest() 테스트 =====
/**
* module.json에서 이름을 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_name_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'identifier' => 'test-module',
'vendor' => 'test',
'name' => ['ko' => '테스트 모듈', 'en' => 'Test Module'],
'version' => '1.0.0',
'description' => ['ko' => '설명', 'en' => 'Description'],
]);
$this->assertEquals(['ko' => '테스트 모듈', 'en' => 'Test Module'], $module->getName());
}
/**
* module.json에서 버전을 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_version_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'version' => '2.3.1',
]);
$this->assertEquals('2.3.1', $module->getVersion());
}
/**
* module.json에서 설명을 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_description_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'description' => ['ko' => '한국어 설명', 'en' => 'English description'],
]);
$this->assertEquals(['ko' => '한국어 설명', 'en' => 'English description'], $module->getDescription());
}
/**
* module.json에서 github_url을 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_github_url_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'github_url' => 'https://github.com/test/module',
]);
$this->assertEquals('https://github.com/test/module', $module->getGithubUrl());
}
/**
* module.json에서 g7_version을 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_required_core_version_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'g7_version' => '>=1.0.0',
]);
$this->assertEquals('>=1.0.0', $module->getRequiredCoreVersion());
}
/**
* module.json에서 assets를 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_assets_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'assets' => [
'js' => ['entry' => 'resources/js/index.ts', 'output' => 'dist/js/module.iife.js'],
'handlers' => true,
],
]);
$assets = $module->getAssets();
$this->assertEquals('resources/js/index.ts', $assets['js']['entry']);
$this->assertTrue($assets['handlers']);
}
/**
* module.json에서 loading 설정을 올바르게 파싱하는지 확인합니다.
*/
public function test_module_reads_loading_config_from_manifest(): void
{
$module = $this->createTestModule($this->tempDir, [
'loading' => [
'strategy' => 'lazy',
'priority' => 50,
],
]);
$config = $module->getAssetLoadingConfig();
$this->assertEquals('lazy', $config['strategy']);
$this->assertEquals(50, $config['priority']);
$this->assertEquals([], $config['dependencies']);
}
/**
* module.json이 없는 경우 폴백 값이 올바른지 확인합니다.
*/
public function test_module_falls_back_when_manifest_missing(): void
{
$module = $this->createTestModule($this->tempDir); // manifest 없음
// identifier는 디렉토리명에서 추출 (tempDir basename)
$this->assertEquals(basename($this->tempDir), $module->getName());
$this->assertEquals('0.0.0', $module->getVersion());
$this->assertEquals('', $module->getDescription());
$this->assertNull($module->getGithubUrl());
$this->assertNull($module->getRequiredCoreVersion());
$this->assertEquals([], $module->getAssets());
}
/**
* loadManifest()가 결과를 캐싱하는지 확인합니다.
*/
public function test_module_manifest_is_cached(): void
{
$module = $this->createTestModule($this->tempDir, [
'name' => ['ko' => '최초', 'en' => 'First'],
'version' => '1.0.0',
]);
// 첫 번째 호출
$this->assertEquals(['ko' => '최초', 'en' => 'First'], $module->getName());
// JSON 파일 수정
file_put_contents($this->tempDir.'/module.json', json_encode([
'name' => ['ko' => '수정됨', 'en' => 'Modified'],
'version' => '2.0.0',
]));
// 캐시되어 있으므로 이전 값 반환
$this->assertEquals(['ko' => '최초', 'en' => 'First'], $module->getName());
$this->assertEquals('1.0.0', $module->getVersion());
}
/**
* module.json의 필드가 부분적으로만 있는 경우 올바르게 폴백하는지 확인합니다.
*/
public function test_module_partial_manifest_uses_fallbacks(): void
{
$module = $this->createTestModule($this->tempDir, [
'name' => ['ko' => '이름만', 'en' => 'Name Only'],
// version, description 없음
]);
$this->assertEquals(['ko' => '이름만', 'en' => 'Name Only'], $module->getName());
$this->assertEquals('0.0.0', $module->getVersion());
$this->assertEquals('', $module->getDescription());
}
/**
* upgrades()가 빈 배열을 반환하는지 확인합니다 (upgrades/ 디렉토리 없는 경우).
*/
public function test_module_upgrades_returns_empty_without_directory(): void
{
$module = $this->createTestModule($this->tempDir, []);
$this->assertEquals([], $module->upgrades());
}
// ===== AbstractPlugin loadManifest() 테스트 =====
/**
* plugin.json에서 이름을 올바르게 파싱하는지 확인합니다.
*/
public function test_plugin_reads_name_from_manifest(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'identifier' => 'test-plugin',
'vendor' => 'test',
'name' => ['ko' => '테스트 플러그인', 'en' => 'Test Plugin'],
'version' => '1.0.0',
'description' => ['ko' => '설명', 'en' => 'Description'],
]);
$this->assertEquals(['ko' => '테스트 플러그인', 'en' => 'Test Plugin'], $plugin->getName());
}
/**
* plugin.json에서 버전을 올바르게 파싱하는지 확인합니다.
*/
public function test_plugin_reads_version_from_manifest(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'version' => '3.0.0',
]);
$this->assertEquals('3.0.0', $plugin->getVersion());
}
/**
* plugin.json에서 github_url을 올바르게 파싱하는지 확인합니다.
*/
public function test_plugin_reads_github_url_from_manifest(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'github_url' => 'https://github.com/test/plugin',
]);
$this->assertEquals('https://github.com/test/plugin', $plugin->getGithubUrl());
}
/**
* plugin.json에서 g7_version을 올바르게 파싱하는지 확인합니다.
*/
public function test_plugin_reads_required_core_version_from_manifest(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'g7_version' => '^1.0',
]);
$this->assertEquals('^1.0', $plugin->getRequiredCoreVersion());
}
/**
* plugin.json에서 assets를 올바르게 파싱하는지 확인합니다.
*/
public function test_plugin_reads_assets_from_manifest(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'assets' => [
'js' => ['entry' => 'resources/js/index.ts', 'output' => 'dist/js/plugin.iife.js'],
'css' => ['entry' => 'resources/css/main.css', 'output' => 'dist/css/plugin.css'],
],
]);
$assets = $plugin->getAssets();
$this->assertEquals('dist/js/plugin.iife.js', $assets['js']['output']);
$this->assertEquals('dist/css/plugin.css', $assets['css']['output']);
}
/**
* plugin.json에서 loading 설정을 올바르게 파싱하는지 확인합니다.
*/
public function test_plugin_reads_loading_config_from_manifest(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'loading' => [
'strategy' => 'global',
'priority' => 200,
'dependencies' => ['sirsoft-ecommerce'],
],
]);
$config = $plugin->getAssetLoadingConfig();
$this->assertEquals('global', $config['strategy']);
$this->assertEquals(200, $config['priority']);
$this->assertEquals(['sirsoft-ecommerce'], $config['dependencies']);
}
/**
* plugin.json이 없는 경우 폴백 값이 올바른지 확인합니다.
*/
public function test_plugin_falls_back_when_manifest_missing(): void
{
$plugin = $this->createTestPlugin($this->tempDir); // manifest 없음
$this->assertEquals(basename($this->tempDir), $plugin->getName());
$this->assertEquals('0.0.0', $plugin->getVersion());
$this->assertEquals('', $plugin->getDescription());
$this->assertNull($plugin->getGithubUrl());
$this->assertNull($plugin->getRequiredCoreVersion());
$this->assertEquals([], $plugin->getAssets());
}
/**
* plugin.json의 loading 기본값이 올바른지 확인합니다.
*/
public function test_plugin_loading_config_defaults(): void
{
$plugin = $this->createTestPlugin($this->tempDir, []);
$config = $plugin->getAssetLoadingConfig();
$this->assertEquals('global', $config['strategy']);
$this->assertEquals(100, $config['priority']);
$this->assertEquals([], $config['dependencies']);
}
/**
* loadManifest()가 결과를 캐싱하는지 확인합니다.
*/
public function test_plugin_manifest_is_cached(): void
{
$plugin = $this->createTestPlugin($this->tempDir, [
'version' => '1.0.0',
]);
// 첫 번째 호출
$this->assertEquals('1.0.0', $plugin->getVersion());
// JSON 파일 수정
file_put_contents($this->tempDir.'/plugin.json', json_encode([
'version' => '9.9.9',
]));
// 캐시되어 있으므로 이전 값 반환
$this->assertEquals('1.0.0', $plugin->getVersion());
}
/**
* upgrades()가 빈 배열을 반환하는지 확인합니다 (upgrades/ 디렉토리 없는 경우).
*/
public function test_plugin_upgrades_returns_empty_without_directory(): void
{
$plugin = $this->createTestPlugin($this->tempDir, []);
$this->assertEquals([], $plugin->upgrades());
}
// ===== _bundled 디렉토리 내 실제 매니페스트 검증 =====
/**
* _bundled 디렉토리의 모든 module.json이 유효한 JSON인지 확인합니다.
*/
public function test_all_bundled_module_manifests_are_valid_json(): void
{
$bundledDir = base_path('modules/_bundled');
if (! is_dir($bundledDir)) {
$this->markTestSkipped('modules/_bundled directory not found');
}
$dirs = array_filter(scandir($bundledDir), fn ($d) => $d !== '.' && $d !== '..');
foreach ($dirs as $dir) {
$manifestPath = $bundledDir.'/'.$dir.'/module.json';
if (! file_exists($manifestPath)) {
continue;
}
$content = file_get_contents($manifestPath);
$data = json_decode($content, true);
$this->assertNotNull($data, "module.json in {$dir} is not valid JSON");
$this->assertArrayHasKey('identifier', $data, "module.json in {$dir} missing 'identifier'");
$this->assertArrayHasKey('name', $data, "module.json in {$dir} missing 'name'");
$this->assertArrayHasKey('version', $data, "module.json in {$dir} missing 'version'");
}
}
/**
* _bundled 디렉토리의 모든 plugin.json이 유효한 JSON인지 확인합니다.
*/
public function test_all_bundled_plugin_manifests_are_valid_json(): void
{
$bundledDir = base_path('plugins/_bundled');
if (! is_dir($bundledDir)) {
$this->markTestSkipped('plugins/_bundled directory not found');
}
$dirs = array_filter(scandir($bundledDir), fn ($d) => $d !== '.' && $d !== '..');
foreach ($dirs as $dir) {
$manifestPath = $bundledDir.'/'.$dir.'/plugin.json';
if (! file_exists($manifestPath)) {
continue;
}
$content = file_get_contents($manifestPath);
$data = json_decode($content, true);
$this->assertNotNull($data, "plugin.json in {$dir} is not valid JSON");
$this->assertArrayHasKey('identifier', $data, "plugin.json in {$dir} missing 'identifier'");
$this->assertArrayHasKey('name', $data, "plugin.json in {$dir} missing 'name'");
$this->assertArrayHasKey('version', $data, "plugin.json in {$dir} missing 'version'");
}
}
/**
* 매니페스트의 identifier가 디렉토리명과 일치하는지 확인합니다.
*/
public function test_manifest_identifiers_match_directory_names(): void
{
$checks = [
'modules/_bundled' => 'module.json',
'plugins/_bundled' => 'plugin.json',
];
foreach ($checks as $basePath => $manifestFile) {
$fullPath = base_path($basePath);
if (! is_dir($fullPath)) {
continue;
}
$dirs = array_filter(scandir($fullPath), fn ($d) => $d !== '.' && $d !== '..');
foreach ($dirs as $dir) {
$manifestPath = $fullPath.'/'.$dir.'/'.$manifestFile;
if (! file_exists($manifestPath)) {
continue;
}
$data = json_decode(file_get_contents($manifestPath), true);
$this->assertEquals(
$dir,
$data['identifier'],
"{$basePath}/{$dir}/{$manifestFile}: identifier '{$data['identifier']}' does not match directory name '{$dir}'"
);
}
}
}
}