Files
Gnuboard7/tests/Unit/Services/LayoutServiceSeoMergeTest.php
T
2026-04-01 10:30:52 +09:00

572 lines
18 KiB
PHP

<?php
namespace Tests\Unit\Services;
use App\Services\LayoutService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class LayoutServiceSeoMergeTest extends TestCase
{
use RefreshDatabase;
private LayoutService $layoutService;
/**
* 테스트 초기화 - LayoutService 인스턴스를 준비합니다.
*/
protected function setUp(): void
{
parent::setUp();
$this->layoutService = app(LayoutService::class);
}
/**
* 부모만 meta.seo가 있을 때 부모의 seo가 사용됩니다.
*/
public function test_parent_only_seo_is_used(): void
{
$parentSeo = [
'enabled' => true,
'priority' => 'high',
'og' => ['type' => 'website'],
];
$parent = $this->buildLayout(seo: $parentSeo);
$child = $this->buildLayout();
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertTrue($result['meta']['seo']['enabled']);
$this->assertSame('high', $result['meta']['seo']['priority']);
$this->assertSame('website', $result['meta']['seo']['og']['type']);
}
/**
* 자식만 meta.seo가 있을 때 자식의 seo가 사용됩니다.
*/
public function test_child_only_seo_is_used(): void
{
$childSeo = [
'enabled' => true,
'priority' => 'low',
'og' => ['title' => '자식 페이지'],
];
$parent = $this->buildLayout();
$child = $this->buildLayout(seo: $childSeo);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertTrue($result['meta']['seo']['enabled']);
$this->assertSame('low', $result['meta']['seo']['priority']);
$this->assertSame('자식 페이지', $result['meta']['seo']['og']['title']);
}
/**
* 부모와 자식 모두 meta.seo가 있을 때 자식의 priority가 우선됩니다.
*/
public function test_child_overrides_priority_parent_rest_preserved(): void
{
$parentSeo = [
'enabled' => true,
'priority' => 'high',
'title_template' => '{{title}} - 사이트명',
'description' => '기본 설명',
];
$childSeo = [
'priority' => 'low',
];
$parent = $this->buildLayout(seo: $parentSeo);
$child = $this->buildLayout(seo: $childSeo);
$result = $this->layoutService->mergeLayouts($parent, $child);
// 자식이 오버라이드한 값
$this->assertSame('low', $result['meta']['seo']['priority']);
// 부모에서 보존된 값
$this->assertTrue($result['meta']['seo']['enabled']);
$this->assertSame('{{title}} - 사이트명', $result['meta']['seo']['title_template']);
$this->assertSame('기본 설명', $result['meta']['seo']['description']);
}
/**
* 자식이 og를 추가하면 부모의 기본값과 함께 병합됩니다.
*/
public function test_child_adds_og_merged_with_parent_defaults(): void
{
$parentSeo = [
'enabled' => true,
'priority' => 'medium',
];
$childSeo = [
'og' => [
'title' => '상품 상세',
'type' => 'product',
],
];
$parent = $this->buildLayout(seo: $parentSeo);
$child = $this->buildLayout(seo: $childSeo);
$result = $this->layoutService->mergeLayouts($parent, $child);
// 부모 기본값 보존
$this->assertTrue($result['meta']['seo']['enabled']);
$this->assertSame('medium', $result['meta']['seo']['priority']);
// 자식이 추가한 og
$this->assertSame('상품 상세', $result['meta']['seo']['og']['title']);
$this->assertSame('product', $result['meta']['seo']['og']['type']);
}
/**
* 자식이 enabled=false로 설정하면 최종 결과에서 enabled=false가 됩니다.
*/
public function test_child_sets_enabled_false(): void
{
$parentSeo = [
'enabled' => true,
'priority' => 'high',
];
$childSeo = [
'enabled' => false,
];
$parent = $this->buildLayout(seo: $parentSeo);
$child = $this->buildLayout(seo: $childSeo);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertFalse($result['meta']['seo']['enabled']);
// 부모 나머지 보존
$this->assertSame('high', $result['meta']['seo']['priority']);
}
/**
* 부모 seo.og.type + 자식 seo.og.title → og가 deep merge됩니다.
*/
public function test_og_deep_merge_parent_type_child_title(): void
{
$parentSeo = [
'enabled' => true,
'og' => [
'type' => 'website',
'site_name' => 'G7 Store',
],
];
$childSeo = [
'og' => [
'title' => '카테고리 페이지',
],
];
$parent = $this->buildLayout(seo: $parentSeo);
$child = $this->buildLayout(seo: $childSeo);
$result = $this->layoutService->mergeLayouts($parent, $child);
// 부모 og 보존
$this->assertSame('website', $result['meta']['seo']['og']['type']);
$this->assertSame('G7 Store', $result['meta']['seo']['og']['site_name']);
// 자식 og 추가
$this->assertSame('카테고리 페이지', $result['meta']['seo']['og']['title']);
}
/**
* seo 이외의 meta 키(title, description)는 기존 얕은 병합이 유지됩니다.
*/
public function test_non_seo_meta_keys_use_shallow_merge(): void
{
$parent = [
'meta' => [
'title' => 'Parent Title',
'description' => 'Parent Description',
'seo' => ['enabled' => true, 'priority' => 'high'],
],
'data_sources' => [],
'components' => [],
];
$child = [
'meta' => [
'title' => 'Child Title',
'seo' => ['priority' => 'low'],
],
'data_sources' => [],
'components' => [],
];
$result = $this->layoutService->mergeLayouts($parent, $child);
// 얕은 병합: 자식 title 우선
$this->assertSame('Child Title', $result['meta']['title']);
// 얕은 병합: 자식에 없는 description 보존
$this->assertSame('Parent Description', $result['meta']['description']);
// seo는 deep merge
$this->assertSame('low', $result['meta']['seo']['priority']);
$this->assertTrue($result['meta']['seo']['enabled']);
}
/**
* 3단계 상속(mergeLayouts 2회 호출)에서 모든 레벨의 seo가 누적됩니다.
*/
public function test_three_level_inheritance_accumulates_seo(): void
{
// Level 1: _base (최상위 부모)
$base = [
'meta' => [
'seo' => [
'enabled' => true,
'title_template' => '{{title}} - G7',
'og' => [
'type' => 'website',
'site_name' => 'G7',
],
],
],
'data_sources' => [],
'components' => [
['component' => 'Div', 'slot' => 'content'],
],
];
// Level 2: shop 기본 (중간 레벨)
$shopBase = [
'meta' => [
'seo' => [
'priority' => 'medium',
'og' => [
'type' => 'product.group',
],
'structured_data' => [
'type' => 'ItemList',
],
],
],
'data_sources' => [],
'slots' => [
'content' => [
'component' => 'Div',
'slot' => 'shop_content',
],
],
];
// Level 3: 상품 상세 (최하위)
$shopShow = [
'meta' => [
'seo' => [
'priority' => 'high',
'og' => [
'title' => '{{product.name}}',
'image' => '{{product.image}}',
],
'data_sources' => ['product'],
],
],
'data_sources' => [],
'slots' => [
'shop_content' => [
'component' => 'Div',
'props' => ['text' => '상품 상세'],
],
],
];
// 3단계 병합: _base + shop_base → 중간 결과 + shop_show
$merged1 = $this->layoutService->mergeLayouts($base, $shopBase);
$result = $this->layoutService->mergeLayouts($merged1, $shopShow);
$seo = $result['meta']['seo'];
// _base에서 상속
$this->assertTrue($seo['enabled']);
$this->assertSame('{{title}} - G7', $seo['title_template']);
// shop_base에서 상속 (deep merge)
$this->assertSame('ItemList', $seo['structured_data']['type']);
// shop_show에서 오버라이드
$this->assertSame('high', $seo['priority']);
// data_sources는 합집합 병합 (Level 3의 'product'만 존재 — Level 1,2는 미정의)
$this->assertSame(['product'], $seo['data_sources']);
// og deep merge: _base의 site_name + shop_base의 type 오버라이드 + shop_show의 title, image
$this->assertSame('G7', $seo['og']['site_name']);
$this->assertSame('product.group', $seo['og']['type']);
$this->assertSame('{{product.name}}', $seo['og']['title']);
$this->assertSame('{{product.image}}', $seo['og']['image']);
}
// ─── data_sources 합집합 병합 테스트 ──────────────────────────────
/**
* 부모와 자식의 data_sources가 합집합으로 병합됩니다.
*/
public function test_data_sources_union_merge_parent_and_child(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'data_sources' => ['stats', 'menu'],
]);
$child = $this->buildLayout(seo: [
'data_sources' => ['product'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame(['stats', 'menu', 'product'], $result['meta']['seo']['data_sources']);
}
/**
* 부모와 자식의 data_sources 중복이 제거됩니다.
*/
public function test_data_sources_union_dedup(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'data_sources' => ['products', 'categories'],
]);
$child = $this->buildLayout(seo: [
'data_sources' => ['products', 'reviews'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame(['products', 'categories', 'reviews'], $result['meta']['seo']['data_sources']);
}
/**
* 자식만 data_sources를 정의하면 그대로 사용됩니다.
*/
public function test_data_sources_child_only(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
]);
$child = $this->buildLayout(seo: [
'data_sources' => ['product'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame(['product'], $result['meta']['seo']['data_sources']);
}
/**
* 부모만 data_sources를 정의하면 자식이 상속합니다.
*/
public function test_data_sources_parent_only(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'data_sources' => ['stats'],
]);
$child = $this->buildLayout(seo: [
'priority' => 0.8,
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame(['stats'], $result['meta']['seo']['data_sources']);
}
/**
* 양쪽 모두 data_sources 미정의 시 키가 없습니다.
*/
public function test_data_sources_both_empty(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
]);
$child = $this->buildLayout(seo: [
'priority' => 0.5,
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertArrayNotHasKey('data_sources', $result['meta']['seo']);
}
// ─── vars deep merge 테스트 ──────────────────────────────────────
/**
* 부모와 자식의 vars가 deep merge되어 양쪽 모두 보존됩니다.
*/
public function test_vars_deep_merge_parent_child(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'vars' => ['site_name' => '$core_settings:general.site_name'],
]);
$child = $this->buildLayout(seo: [
'vars' => ['product_name' => '{{product.data.name}}'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$vars = $result['meta']['seo']['vars'];
$this->assertSame('$core_settings:general.site_name', $vars['site_name']);
$this->assertSame('{{product.data.name}}', $vars['product_name']);
}
/**
* 자식의 vars가 부모의 동일 키를 오버라이드합니다.
*/
public function test_vars_child_overrides_parent_key(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'vars' => ['name' => 'Parent Name'],
]);
$child = $this->buildLayout(seo: [
'vars' => ['name' => 'Child Name'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame('Child Name', $result['meta']['seo']['vars']['name']);
}
// ─── base enabled 상속 테스트 ────────────────────────────────────
/**
* 부모 enabled=false + 자식 enabled=true → 자식 우선으로 활성화됩니다.
*/
public function test_base_seo_disabled_child_enables(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => false,
'og' => ['type' => 'website'],
]);
$child = $this->buildLayout(seo: [
'enabled' => true,
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertTrue($result['meta']['seo']['enabled']);
$this->assertSame('website', $result['meta']['seo']['og']['type']);
}
/**
* 부모의 og.type을 자식이 오버라이드합니다.
*/
public function test_base_og_type_inherited_child_overrides(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'og' => ['type' => 'website'],
]);
$child = $this->buildLayout(seo: [
'og' => ['type' => 'product'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame('product', $result['meta']['seo']['og']['type']);
}
/**
* 부모의 og.type이 보존되면서 자식이 og.title을 추가합니다.
*/
public function test_base_og_type_inherited_child_adds_title(): void
{
$parent = $this->buildLayout(seo: [
'enabled' => true,
'og' => ['type' => 'website'],
]);
$child = $this->buildLayout(seo: [
'og' => ['title' => '상품 페이지'],
]);
$result = $this->layoutService->mergeLayouts($parent, $child);
$this->assertSame('website', $result['meta']['seo']['og']['type']);
$this->assertSame('상품 페이지', $result['meta']['seo']['og']['title']);
}
// ─── 3단계 상속 data_sources 누적 테스트 ─────────────────────────
/**
* 3단계 상속에서 각 레벨의 data_sources가 합집합으로 누적됩니다.
*/
public function test_three_level_data_sources_accumulate(): void
{
$base = [
'meta' => [
'seo' => [
'enabled' => true,
'data_sources' => ['stats'],
],
],
'data_sources' => [],
'components' => [
['component' => 'Div', 'slot' => 'content'],
],
];
$mid = [
'meta' => [
'seo' => [
'data_sources' => ['categories'],
],
],
'data_sources' => [],
'slots' => [
'content' => [
'component' => 'Div',
'slot' => 'inner',
],
],
];
$leaf = [
'meta' => [
'seo' => [
'data_sources' => ['product', 'stats'],
],
],
'data_sources' => [],
'slots' => [
'inner' => [
'component' => 'Div',
'props' => ['text' => 'leaf'],
],
],
];
$merged1 = $this->layoutService->mergeLayouts($base, $mid);
$result = $this->layoutService->mergeLayouts($merged1, $leaf);
// stats(base) + categories(mid) + product(leaf) — stats 중복 제거
$this->assertSame(['stats', 'categories', 'product'], $result['meta']['seo']['data_sources']);
$this->assertTrue($result['meta']['seo']['enabled']);
}
/**
* 테스트용 레이아웃 배열을 빌드합니다.
*
* @param array|null $seo SEO 설정 배열 (null이면 seo 키 없음)
* @return array 레이아웃 배열
*/
private function buildLayout(?array $seo = null): array
{
$meta = [];
if ($seo !== null) {
$meta['seo'] = $seo;
}
return [
'meta' => $meta,
'data_sources' => [],
'components' => [],
];
}
}