Files
Gnuboard7/tests/Feature/Template/TemplateLayoutRegistrationTest.php
T
2026-07-01 10:30:32 +09:00

283 lines
11 KiB
PHP

<?php
namespace Tests\Feature\Template;
use App\Enums\LayoutSourceType;
use App\Extension\TemplateManager;
use App\Models\Template;
use App\Models\TemplateLayout;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Tests\Helpers\ProtectsExtensionDirectories;
use Tests\TestCase;
class TemplateLayoutRegistrationTest extends TestCase
{
use ProtectsExtensionDirectories;
use RefreshDatabase;
protected TemplateManager $templateManager;
protected function setUp(): void
{
parent::setUp();
// 테스트용 레이아웃 JSON 파일 생성 (보호 활성화 전에 수행)
$this->createTestLayoutFiles();
// 확장 디렉토리 보호 활성화
$this->setUpExtensionProtection();
$this->templateManager = app(TemplateManager::class);
$this->templateManager->loadTemplates();
}
protected function tearDown(): void
{
// 캐시 부작용 방지: 테스트 중 생성/삭제된 캐시를 정리
// RefreshDatabase는 DB만 롤백하므로, 캐시는 수동 정리 필수
Cache::flush();
// 확장 디렉토리 보호 해제
$this->tearDownExtensionProtection();
// 테스트용 레이아웃 파일 정리
$this->cleanupTestLayoutFiles();
parent::tearDown();
}
/**
* 템플릿 설치 시 레이아웃 JSON이 DB에 등록되는지 테스트
*/
public function test_install_template_registers_layouts(): void
{
// Act
$result = $this->templateManager->installTemplate('sirsoft-admin_basic');
// Assert
$this->assertTrue($result);
$template = Template::where('identifier', 'sirsoft-admin_basic')->first();
$this->assertNotNull($template);
// 레이아웃이 등록되었는지 확인
$layouts = TemplateLayout::where('template_id', $template->id)->get();
$this->assertGreaterThan(0, $layouts->count(), '레이아웃이 최소 1개 이상 등록되어야 합니다.');
// 특정 레이아웃 확인
$adminBaseLayout = TemplateLayout::where('template_id', $template->id)
->where('name', 'test_layout')
->first();
$this->assertNotNull($adminBaseLayout, 'test_layout 레이아웃이 등록되어야 합니다.');
$this->assertIsArray($adminBaseLayout->content);
$this->assertEquals('test_layout', $adminBaseLayout->content['layout_name']);
}
/**
* 템플릿 제거 시 레이아웃이 DB에서 삭제되는지 테스트
*/
public function test_uninstall_template_removes_layouts(): void
{
// Arrange
$this->templateManager->installTemplate('sirsoft-admin_basic');
$template = Template::where('identifier', 'sirsoft-admin_basic')->first();
$layoutsBeforeUninstall = TemplateLayout::where('template_id', $template->id)->count();
$this->assertGreaterThan(0, $layoutsBeforeUninstall);
// Act (spy가 활성 디렉토리 삭제 차단)
$result = $this->templateManager->uninstallTemplate('sirsoft-admin_basic');
// Assert
$this->assertTrue($result);
$layoutsAfterUninstall = TemplateLayout::where('template_id', $template->id)->count();
$this->assertEquals(0, $layoutsAfterUninstall, '템플릿 제거 시 모든 레이아웃이 삭제되어야 합니다.');
}
/**
* 활성화/비활성화 시 템플릿 자체 레이아웃에 영향 없음 확인
*
* activateTemplate()은 활성 모듈/플러그인 레이아웃을 해당 템플릿에 추가 등록하므로
* 전체 레이아웃 수는 증가할 수 있지만, 템플릿 자체(source_type=template) 레이아웃은 변하지 않아야 합니다.
*/
public function test_activate_deactivate_template_does_not_affect_layouts(): void
{
// Arrange
$this->templateManager->installTemplate('sirsoft-admin_basic');
$template = Template::where('identifier', 'sirsoft-admin_basic')->first();
$templateLayoutsBefore = TemplateLayout::where('template_id', $template->id)
->where('source_type', LayoutSourceType::Template)
->count();
$this->assertGreaterThan(0, $templateLayoutsBefore, '설치 후 템플릿 자체 레이아웃이 존재해야 합니다.');
// Act - 활성화
$this->templateManager->activateTemplate('sirsoft-admin_basic');
// Assert - 템플릿 자체 레이아웃 수 불변
$templateLayoutsAfterActivate = TemplateLayout::where('template_id', $template->id)
->where('source_type', LayoutSourceType::Template)
->count();
$this->assertEquals($templateLayoutsBefore, $templateLayoutsAfterActivate, '활성화 시 템플릿 자체 레이아웃 수는 변경되지 않아야 합니다.');
// Assert - 모듈/플러그인 레이아웃이 추가 등록될 수 있음 (정상 동작)
$totalAfterActivate = TemplateLayout::where('template_id', $template->id)->count();
$this->assertGreaterThanOrEqual($templateLayoutsBefore, $totalAfterActivate, '활성화 시 모듈/플러그인 레이아웃이 추가 등록될 수 있습니다.');
// Act - 비활성화
$this->templateManager->deactivateTemplate('sirsoft-admin_basic');
// Assert - 템플릿 자체 레이아웃 수 불변
$templateLayoutsAfterDeactivate = TemplateLayout::where('template_id', $template->id)
->where('source_type', LayoutSourceType::Template)
->count();
$this->assertEquals($templateLayoutsBefore, $templateLayoutsAfterDeactivate, '비활성화 시 템플릿 자체 레이아웃 수는 변경되지 않아야 합니다.');
}
/**
* (S4'') / 카테고리 D — errors/* layout_name 식별자 통일 회귀
*
* errors/{code}.json 파일의 layout_name 필드가 다른 카테고리(auth/login,
* board/show, mypage/profile) 와 동일하게 디렉토리 접두사를 포함한 "errors/{code}"
* 형식이어야 하며, DB 에도 동일 형식으로 시드되어야 routes.json 의
* `"layout": "errors/404"` 참조와 정합한다. 이전 결함: layout_name="404" 만
* 저장되어 라우트 35/36 (404/403 페이지) 가 Layout not found 로 떨어졌음.
*/
public function test_install_template_seeds_errors_layouts_with_directory_prefix(): void
{
// Act
$result = $this->templateManager->installTemplate('sirsoft-admin_basic');
$this->assertTrue($result);
$template = Template::where('identifier', 'sirsoft-admin_basic')->first();
$this->assertNotNull($template);
// Assert — errors/{code} 형식으로 시드되어야 함
foreach (['errors/401', 'errors/403', 'errors/404', 'errors/500', 'errors/503', 'errors/maintenance'] as $expectedName) {
$layout = TemplateLayout::where('template_id', $template->id)
->where('name', $expectedName)
->first();
$this->assertNotNull(
$layout,
"{$expectedName} 레이아웃이 errors/ 접두사를 포함한 식별자로 시드되어야 합니다."
);
$this->assertSame($expectedName, $layout->content['layout_name'] ?? null);
}
// Assert — 접두사 없는 "404"/"403" 등은 시드되지 않아야 함
foreach (['401', '403', '404', '500', '503', 'maintenance'] as $bareName) {
$stale = TemplateLayout::where('template_id', $template->id)
->where('name', $bareName)
->first();
$this->assertNull(
$stale,
"디렉토리 접두사 없는 {$bareName} 식별자는 시드되지 않아야 합니다 (다른 카테고리 비대칭 회귀)."
);
}
}
/**
* 멱등성 테스트: 같은 템플릿 2번 설치 시 레이아웃 중복 생성 방지
*/
public function test_install_template_twice_does_not_duplicate_layouts(): void
{
// Arrange
$this->templateManager->installTemplate('sirsoft-admin_basic');
$template = Template::where('identifier', 'sirsoft-admin_basic')->first();
$layoutsAfterFirstInstall = TemplateLayout::where('template_id', $template->id)->count();
// Act - 2번째 설치
$this->templateManager->installTemplate('sirsoft-admin_basic');
// Assert
$layoutsAfterSecondInstall = TemplateLayout::where('template_id', $template->id)->count();
$this->assertEquals(
$layoutsAfterFirstInstall,
$layoutsAfterSecondInstall,
'같은 템플릿을 2번 설치해도 레이아웃 수는 동일해야 합니다 (updateOrCreate 멱등성).'
);
}
/**
* 레이아웃 디렉토리(에러 레이아웃 포함)가 없으면 템플릿 설치 실패
*
* 템플릿에는 error_config의 에러 레이아웃 파일이 필수이므로,
* 레이아웃 디렉토리가 없으면 설치가 실패해야 합니다.
*/
public function test_install_template_without_layouts_directory_fails(): void
{
// Arrange - 레이아웃 디렉토리 제거
$layoutsPath = base_path('templates/sirsoft-admin_basic/layouts');
$originalPath = $layoutsPath.'_backup_'.time();
if (File::exists($layoutsPath)) {
File::moveDirectory($layoutsPath, $originalPath);
}
try {
// Act & Assert - 레이아웃 디렉토리가 없으면 에러 레이아웃도 없으므로 예외 발생
$this->expectException(\Exception::class);
$this->templateManager->installTemplate('sirsoft-admin_basic');
} finally {
// Cleanup - 원래 디렉토리 복원
if (File::exists($originalPath)) {
File::moveDirectory($originalPath, $layoutsPath);
}
}
}
/**
* 테스트용 레이아웃 JSON 파일 생성
*/
protected function createTestLayoutFiles(): void
{
$layoutsPath = base_path('templates/sirsoft-admin_basic/layouts');
if (! File::exists($layoutsPath)) {
File::makeDirectory($layoutsPath, 0755, true);
}
// 테스트용 레이아웃 JSON 생성
$testLayout = [
'version' => '1.0.0',
'layout_name' => 'test_layout',
'meta' => [
'title' => 'Test Layout',
'description' => 'Test layout for automated tests',
],
'components' => [
[
'id' => 'root',
'type' => 'basic',
'name' => 'div',
'props' => [
'className' => 'container',
],
],
],
];
File::put(
"{$layoutsPath}/test_layout.json",
json_encode($testLayout, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
}
/**
* 테스트용 레이아웃 파일 정리
*/
protected function cleanupTestLayoutFiles(): void
{
$layoutsPath = base_path('templates/sirsoft-admin_basic/layouts');
$testLayoutFile = "{$layoutsPath}/test_layout.json";
if (File::exists($testLayoutFile)) {
File::delete($testLayoutFile);
}
}
}