Files
Gnuboard7/tests/Feature/ModuleLayoutOverrideTest.php
T
2026-04-01 10:30:52 +09:00

711 lines
29 KiB
PHP

<?php
namespace Tests\Feature;
use App\Enums\ExtensionStatus;
use App\Enums\LayoutSourceType;
use App\Extension\ModuleManager;
use App\Models\Template;
use App\Models\TemplateLayout;
use App\Services\LayoutResolverService;
use App\Services\LayoutService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
/**
* 모듈 레이아웃 오버라이드 통합 테스트
*
* Phase 7.2에 따른 전체 흐름 통합 테스트:
* - 모듈 설치 → 레이아웃 등록 → API 조회
* - 템플릿 오버라이드 → 우선순위 적용
* - 모듈 비활성화 → 레이아웃 제거
*/
class ModuleLayoutOverrideTest extends TestCase
{
use RefreshDatabase;
private ModuleManager $moduleManager;
private LayoutService $layoutService;
private LayoutResolverService $resolverService;
private Template $adminTemplate;
protected function setUp(): void
{
parent::setUp();
// 이전 테스트에서 남은 레이아웃 캐시 전체 초기화
Cache::flush();
// 모든 관련 서비스 인스턴스 초기화 (이전 테스트의 상태 제거)
// TemplateService도 LayoutService에 의존하므로 함께 초기화
app()->forgetInstance(ModuleManager::class);
app()->forgetInstance(LayoutService::class);
app()->forgetInstance(LayoutResolverService::class);
app()->forgetInstance(\App\Services\TemplateService::class);
// DI 컨테이너를 통해 새로운 인스턴스 획득
$this->moduleManager = app(ModuleManager::class);
$this->layoutService = app(LayoutService::class);
$this->resolverService = app(LayoutResolverService::class);
// 테스트용 활성화된 admin 템플릿 생성
$this->adminTemplate = Template::create([
'identifier' => 'sirsoft-admin_basic',
'vendor' => 'sirsoft',
'name' => ['ko' => '기본 관리자 템플릿', 'en' => 'Basic Admin Template'],
'version' => '1.0.0',
'type' => 'admin',
'status' => ExtensionStatus::Active->value,
'description' => ['ko' => '기본 관리자 템플릿', 'en' => 'Basic Admin Template'],
]);
}
/**
* 모든 레이아웃 관련 캐시를 무효화합니다.
*
* 캐시 키가 소스 해시를 포함하므로 오버라이드 추가/삭제 시 키가 변경됩니다.
* 테스트 환경에서는 Cache::flush()로 모든 캐시를 초기화하고,
* 서비스 인스턴스도 재생성하여 내부 상태를 초기화합니다.
*/
private function clearAllLayoutCaches(string $templateIdentifier, int $templateId, string $layoutName): void
{
// 1. 모든 캐시 초기화
Cache::flush();
// 2. 모든 관련 서비스 인스턴스 초기화 (다음 요청에서 새로 생성되도록)
// 이렇게 하면 컨트롤러에 주입되는 서비스도 새로운 인스턴스가 됨
app()->forgetInstance(LayoutService::class);
app()->forgetInstance(LayoutResolverService::class);
app()->forgetInstance(\App\Services\TemplateService::class);
// 3. 테스트 클래스의 참조도 새로운 인스턴스로 갱신
$this->layoutService = app(LayoutService::class);
$this->resolverService = app(LayoutResolverService::class);
}
/**
* 테스트 케이스 1: 모듈 설치 → 레이아웃 등록 → API 조회
*
* 모듈이 설치되고 활성화되면 해당 모듈의 레이아웃이
* DB에 등록되고 API를 통해 조회할 수 있어야 합니다.
*/
public function test_module_layout_registration_and_api_query(): void
{
$moduleIdentifier = 'sirsoft-sample';
$layoutName = "{$moduleIdentifier}_admin_index";
// 1. 모듈 레이아웃을 DB에 직접 등록 (모듈 활성화 시뮬레이션)
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'meta' => [
'title' => 'Sample Module Index',
'description' => 'Sample module index page',
],
'data_sources' => [],
'components' => [
[
'component' => 'Container',
'children' => [
[
'component' => 'DataTable',
'props' => [
'columns' => [
['key' => 'id', 'label' => 'ID'],
['key' => 'name', 'label' => 'Name'],
],
],
],
],
],
],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 2. 레이아웃이 DB에 등록되었는지 확인
$this->assertDatabaseHas('template_layouts', [
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'source_type' => LayoutSourceType::Module->value,
'source_identifier' => $moduleIdentifier,
]);
// 3. API를 통한 레이아웃 조회
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
// 4. 성공 응답 확인
$response->assertStatus(200)
->assertJson([
'success' => true,
])
->assertJsonPath('data.meta.title', 'Sample Module Index');
// 5. LayoutResolverService를 통한 해석 확인
$resolved = $this->resolverService->resolve($layoutName, $this->adminTemplate->id);
$this->assertNotNull($resolved);
$this->assertEquals($moduleLayout->id, $resolved->id);
$this->assertEquals(LayoutSourceType::Module, $resolved->source_type);
}
/**
* 테스트 케이스 2: 템플릿 오버라이드 → 우선순위 적용
*
* 템플릿이 모듈 레이아웃을 오버라이드하면
* 오버라이드 레이아웃이 우선적으로 사용되어야 합니다.
*/
public function test_template_override_priority_application(): void
{
$moduleIdentifier = 'sirsoft-sample';
$layoutName = "{$moduleIdentifier}_admin_products_index";
// 1. 모듈 기본 레이아웃 등록
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'meta' => [
'title' => 'Module Default Layout',
'theme' => 'default',
],
'data_sources' => [],
'components' => [
['component' => 'ModuleDefaultComponent'],
],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 2. 템플릿 오버라이드 레이아웃 등록
$overrideLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'meta' => [
'title' => 'Template Override Layout',
'theme' => 'custom-dark',
],
'data_sources' => [],
'components' => [
['component' => 'CustomOverrideComponent', 'props' => ['theme' => 'dark']],
],
],
'source_type' => LayoutSourceType::Template,
'source_identifier' => $this->adminTemplate->identifier,
]);
// 3. 두 레이아웃이 모두 DB에 존재하는지 확인
$layoutCount = TemplateLayout::where('template_id', $this->adminTemplate->id)
->where('name', $layoutName)
->count();
$this->assertEquals(2, $layoutCount);
// 4. LayoutResolverService를 통해 해석 - 오버라이드가 우선
$resolved = $this->resolverService->resolve($layoutName, $this->adminTemplate->id);
$this->assertNotNull($resolved);
$this->assertEquals($overrideLayout->id, $resolved->id);
$this->assertEquals(LayoutSourceType::Template, $resolved->source_type);
// 5. API 조회 시 오버라이드 레이아웃이 반환되는지 확인
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200)
->assertJsonPath('data.meta.title', 'Template Override Layout')
->assertJsonPath('data.meta.theme', 'custom-dark');
// 6. isOverridden 메서드 확인
$this->assertTrue($this->resolverService->isOverridden($layoutName, $this->adminTemplate->id));
// 사용되지 않는 변수 경고 방지
$this->assertNotNull($moduleLayout);
}
/**
* 테스트 케이스 3: 모듈 비활성화 → 레이아웃 제거
*
* 모듈이 비활성화되면 해당 모듈의 레이아웃이
* soft delete되어 API 조회에서 제외되어야 합니다.
*/
public function test_module_deactivation_layout_removal(): void
{
$moduleIdentifier = 'sirsoft-deactivate-test';
$layoutName = "{$moduleIdentifier}_admin_index";
// 1. 모듈 레이아웃 등록
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'meta' => ['title' => 'Deactivate Test Layout'],
'data_sources' => [],
'components' => [],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 2. 레이아웃이 활성 상태인지 확인
$this->assertNotNull(TemplateLayout::find($moduleLayout->id));
// 3. API 조회 성공 확인
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200);
// 4. 모듈 비활성화 시뮬레이션 - soft delete 및 캐시 무효화
$moduleLayout->delete();
$this->clearAllLayoutCaches($this->adminTemplate->identifier, $this->adminTemplate->id, $layoutName);
// 5. 일반 조회에서는 나타나지 않음
$this->assertNull(TemplateLayout::find($moduleLayout->id));
// 6. withTrashed로는 조회 가능
$this->assertNotNull(TemplateLayout::withTrashed()->find($moduleLayout->id));
// 7. API 조회 실패 확인 (레이아웃을 찾을 수 없음)
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(404);
// 8. LayoutResolverService도 null 반환
$resolved = $this->resolverService->resolve($layoutName, $this->adminTemplate->id);
$this->assertNull($resolved);
}
/**
* 테스트: 모듈 재활성화 시 레이아웃 복원
*
* soft delete된 모듈 레이아웃이 모듈 재활성화 시 복원되어야 합니다.
*/
public function test_module_reactivation_restores_layouts(): void
{
$moduleIdentifier = 'sirsoft-reactivate-test';
$layoutName = "{$moduleIdentifier}_admin_index";
// 1. 모듈 레이아웃 생성 및 soft delete
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'meta' => ['title' => 'Reactivate Test'],
'data_sources' => [],
'components' => [],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
$moduleLayout->delete();
// 2. soft deleted 상태 확인
$this->assertSoftDeleted('template_layouts', ['id' => $moduleLayout->id]);
// 3. ModuleManager의 restoreModuleLayouts 호출 (protected 메서드)
$reflection = new \ReflectionClass($this->moduleManager);
$method = $reflection->getMethod('restoreModuleLayouts');
$method->setAccessible(true);
$method->invoke($this->moduleManager, $moduleIdentifier);
// 4. 레이아웃 복원 확인
$restoredLayout = TemplateLayout::find($moduleLayout->id);
$this->assertNotNull($restoredLayout);
$this->assertNull($restoredLayout->deleted_at);
// 5. API 조회 성공 확인
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200);
}
/**
* 테스트: 레이아웃 상속과 오버라이드 통합 동작
*
* 모듈 레이아웃이 템플릿의 베이스 레이아웃을 extends하고
* 템플릿이 해당 모듈 레이아웃을 오버라이드하는 전체 흐름을 테스트합니다.
*
* 이 테스트는 오버라이드가 이미 존재할 때 API가 오버라이드를 반환하는지 검증합니다.
* 캐시 상태 변경 테스트는 test_cache_invalidation_on_layout_change에서 수행합니다.
*/
public function test_layout_inheritance_with_override(): void
{
// 고유한 식별자 사용 (다른 테스트와의 캐시 충돌 방지)
// 주의: moduleIdentifier는 vendor-module 형식이어야 isModuleLayoutName() 정규식에 매칭됨
// 정규식: ^[a-z0-9]+-[a-z0-9]+_ (하이픈 하나만 허용)
$uniqueId = uniqid();
$moduleIdentifier = "sirsoft-inherit{$uniqueId}";
$layoutName = "{$moduleIdentifier}_admin_detail";
// 1. 템플릿 베이스 레이아웃 생성 (고유한 이름 사용)
$baseLayoutName = "_admin_base_{$uniqueId}";
$baseLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $baseLayoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $baseLayoutName,
'meta' => [
'title' => 'Admin Base',
],
'data_sources' => [],
'components' => [
[
'component' => 'Container',
'props' => ['class' => 'admin-container'],
'children' => [
['component' => 'Header', 'slot' => 'header'],
['component' => 'MainContent', 'slot' => 'content'],
['component' => 'Footer', 'slot' => 'footer'],
],
],
],
],
'source_type' => LayoutSourceType::Template,
'source_identifier' => $this->adminTemplate->identifier,
]);
// 2. 모듈 레이아웃 (베이스 상속)
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'extends' => $baseLayoutName,
'meta' => [
'title' => 'Module Detail Page',
],
'slots' => [
'content' => [
['component' => 'DetailView', 'props' => ['style' => 'default']],
],
],
],
'extends' => $baseLayoutName,
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 3. 템플릿 오버라이드도 함께 생성 (캐시 생성 전에 모든 데이터 준비)
$overrideLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'extends' => $baseLayoutName,
'meta' => [
'title' => 'Custom Detail Page',
'theme' => 'premium',
],
'slots' => [
'content' => [
['component' => 'CustomDetailView', 'props' => ['style' => 'premium']],
],
],
],
'extends' => $baseLayoutName,
'source_type' => LayoutSourceType::Template,
'source_identifier' => $this->adminTemplate->identifier,
]);
// 4. LayoutResolverService로 먼저 직접 확인 (캐시 없이)
// 새로운 인스턴스 생성하여 캐시 상태 배제
$freshResolverService = app(LayoutResolverService::class);
$resolved = $freshResolverService->resolve($layoutName, $this->adminTemplate->id);
$this->assertNotNull($resolved, 'LayoutResolverService should resolve the layout');
$this->assertEquals($overrideLayout->id, $resolved->id, 'Resolver should return override layout');
$this->assertEquals(LayoutSourceType::Template, $resolved->source_type, 'Source type should be Template');
// 5. API 조회 - 캐시 초기화 후 새 요청
Cache::flush();
app()->forgetInstance(LayoutService::class);
app()->forgetInstance(LayoutResolverService::class);
app()->forgetInstance(\App\Services\TemplateService::class);
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200)
->assertJsonPath('data.meta.title', 'Custom Detail Page')
->assertJsonPath('data.meta.theme', 'premium');
// 병합 후 extends와 slots가 제거되었는지 확인
$this->assertArrayNotHasKey('extends', $response->json('data'));
$this->assertArrayNotHasKey('slots', $response->json('data'));
// 사용되지 않는 변수 경고 방지
$this->assertNotNull($baseLayout);
$this->assertNotNull($moduleLayout);
}
/**
* 테스트: 캐시 무효화 통합 테스트
*
* 모듈 레이아웃 변경 시 캐시가 올바르게 무효화되는지 테스트합니다.
*/
public function test_cache_invalidation_on_layout_change(): void
{
$moduleIdentifier = 'sirsoft-cache-test';
$layoutName = "{$moduleIdentifier}_admin_index";
// 1. 모듈 레이아웃 생성
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'meta' => ['title' => 'Original Title'],
'data_sources' => [],
'components' => [],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 2. 첫 번째 API 조회 (캐시 생성)
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200)
->assertJsonPath('data.meta.title', 'Original Title');
// 3. 레이아웃 content 직접 업데이트
$moduleLayout->update([
'content' => [
'version' => '1.0.0',
'layout_name' => $layoutName,
'meta' => ['title' => 'Updated Title'],
'data_sources' => [],
'components' => [],
],
]);
// 4. 캐시 무효화
$this->clearAllLayoutCaches($this->adminTemplate->identifier, $this->adminTemplate->id, $layoutName);
// 5. 다시 API 조회 - 업데이트된 내용 확인
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200)
->assertJsonPath('data.meta.title', 'Updated Title');
}
/**
* 테스트: 다중 admin 템플릿에 모듈 레이아웃 등록
*
* 모듈 레이아웃이 여러 admin 템플릿에 등록되고
* 각 템플릿에서 독립적으로 동작하는지 테스트합니다.
*/
public function test_module_layout_in_multiple_admin_templates(): void
{
$moduleIdentifier = 'sirsoft-multi-template';
$layoutName = "{$moduleIdentifier}_admin_index";
// 1. 두 번째 admin 템플릿 생성
$secondTemplate = Template::create([
'identifier' => 'sirsoft-admin_premium',
'vendor' => 'sirsoft',
'name' => ['ko' => '프리미엄 관리자 템플릿', 'en' => 'Premium Admin Template'],
'version' => '1.0.0',
'type' => 'admin',
'status' => ExtensionStatus::Active->value,
'description' => ['ko' => '프리미엄 템플릿', 'en' => 'Premium Template'],
]);
// 2. 첫 번째 템플릿에 모듈 레이아웃 등록
TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'meta' => ['title' => 'Basic Template Layout'],
'data_sources' => [],
'components' => [],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 3. 두 번째 템플릿에 모듈 레이아웃 등록
TemplateLayout::create([
'template_id' => $secondTemplate->id,
'name' => $layoutName,
'content' => [
'meta' => ['title' => 'Premium Template Layout'],
'data_sources' => [],
'components' => [],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 4. 각 템플릿에서 API 조회
$response1 = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response1->assertStatus(200)
->assertJsonPath('data.meta.title', 'Basic Template Layout');
$response2 = $this->getJson("/api/layouts/{$secondTemplate->identifier}/{$layoutName}.json");
$response2->assertStatus(200)
->assertJsonPath('data.meta.title', 'Premium Template Layout');
// 5. LayoutResolverService가 각 템플릿을 독립적으로 처리하는지 확인
$resolved1 = $this->resolverService->resolve($layoutName, $this->adminTemplate->id);
$resolved2 = $this->resolverService->resolve($layoutName, $secondTemplate->id);
$this->assertNotEquals($resolved1->id, $resolved2->id);
}
/**
* 테스트: 오버라이드 레이아웃 목록 조회
*
* 특정 템플릿에서 오버라이드된 레이아웃 목록을 조회합니다.
*/
public function test_get_overridden_layouts_list(): void
{
$moduleIdentifier = 'sirsoft-override-list';
// 1. 여러 모듈 레이아웃 생성
for ($i = 1; $i <= 3; $i++) {
$layoutName = "{$moduleIdentifier}_layout_{$i}";
TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => ['meta' => [], 'data_sources' => [], 'components' => []],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
}
// 2. 일부만 오버라이드
TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => "{$moduleIdentifier}_layout_1",
'content' => ['meta' => ['theme' => 'custom'], 'data_sources' => [], 'components' => []],
'source_type' => LayoutSourceType::Template,
'source_identifier' => $this->adminTemplate->identifier,
]);
TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => "{$moduleIdentifier}_layout_3",
'content' => ['meta' => ['theme' => 'premium'], 'data_sources' => [], 'components' => []],
'source_type' => LayoutSourceType::Template,
'source_identifier' => $this->adminTemplate->identifier,
]);
// 3. 오버라이드된 레이아웃 목록 조회
$overriddenLayouts = $this->resolverService->getOverriddenLayouts($this->adminTemplate->id);
$this->assertCount(2, $overriddenLayouts);
$overriddenNames = $overriddenLayouts->pluck('name')->toArray();
$this->assertContains("{$moduleIdentifier}_layout_1", $overriddenNames);
$this->assertContains("{$moduleIdentifier}_layout_3", $overriddenNames);
// layout_2는 오버라이드되지 않음
$this->assertFalse($this->resolverService->isOverridden(
"{$moduleIdentifier}_layout_2",
$this->adminTemplate->id
));
}
/**
* 테스트: 오버라이드 없이 모듈 기본 레이아웃만 있을 때 정상 동작
*
* 템플릿 오버라이드가 없으면 모듈 기본 레이아웃이 사용되어야 합니다.
*/
public function test_fallback_to_module_layout_when_override_removed(): void
{
$moduleIdentifier = 'sirsoft-fallback-test';
$layoutName = "{$moduleIdentifier}_admin_index";
// 1. 모듈 기본 레이아웃만 생성 (오버라이드 없음)
$moduleLayout = TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => $layoutName,
'content' => [
'meta' => ['title' => 'Module Default'],
'data_sources' => [],
'components' => [],
],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
// 2. 모듈 기본 레이아웃 조회 확인
$response = $this->getJson("/api/layouts/{$this->adminTemplate->identifier}/{$layoutName}.json");
$response->assertStatus(200)
->assertJsonPath('data.meta.title', 'Module Default');
// 3. 오버라이드되지 않았는지 확인
$this->assertFalse($this->resolverService->isOverridden($layoutName, $this->adminTemplate->id));
// 4. LayoutResolverService로 직접 확인
$resolved = $this->resolverService->resolve($layoutName, $this->adminTemplate->id);
$this->assertEquals($moduleLayout->id, $resolved->id);
$this->assertEquals(LayoutSourceType::Module, $resolved->source_type);
}
/**
* 테스트: 모듈 레이아웃 소스 타입 필터링
*
* source_type에 따라 레이아웃을 필터링하여 조회할 수 있는지 테스트합니다.
*/
public function test_filter_layouts_by_source_type(): void
{
$moduleIdentifier = 'sirsoft-filter-test';
// 1. 모듈 레이아웃 3개 생성
for ($i = 1; $i <= 3; $i++) {
TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => "{$moduleIdentifier}_module_{$i}",
'content' => ['meta' => [], 'data_sources' => [], 'components' => []],
'source_type' => LayoutSourceType::Module,
'source_identifier' => $moduleIdentifier,
]);
}
// 2. 템플릿 레이아웃 2개 생성
for ($i = 1; $i <= 2; $i++) {
TemplateLayout::create([
'template_id' => $this->adminTemplate->id,
'name' => "template_layout_{$i}",
'content' => ['meta' => [], 'data_sources' => [], 'components' => []],
'source_type' => LayoutSourceType::Template,
'source_identifier' => $this->adminTemplate->identifier,
]);
}
// 3. 모듈 레이아웃만 필터링
$moduleLayouts = TemplateLayout::where('template_id', $this->adminTemplate->id)
->where('source_type', LayoutSourceType::Module)
->get();
$this->assertCount(3, $moduleLayouts);
// 4. 템플릿 레이아웃만 필터링
$templateLayouts = TemplateLayout::where('template_id', $this->adminTemplate->id)
->where('source_type', LayoutSourceType::Template)
->get();
$this->assertCount(2, $templateLayouts);
// 5. 특정 모듈의 레이아웃만 필터링
$specificModuleLayouts = TemplateLayout::where('template_id', $this->adminTemplate->id)
->where('source_type', LayoutSourceType::Module)
->where('source_identifier', $moduleIdentifier)
->get();
$this->assertCount(3, $specificModuleLayouts);
}
}