createMock(ModuleManagerInterface::class); $moduleManager->method('getModule')->willReturn(null); $pluginManager = $this->createMock(PluginManagerInterface::class); $pluginManager->method('getPlugin')->willReturn(null); return new SeoOgPreviewService( app(SeoMetaResolver::class), $moduleManager, $pluginManager, ); } /** og 배열에서 특정 key 행을 찾는다. */ private function ogRow(array $preview, string $key): ?array { foreach ($preview['og'] as $row) { if ($row['key'] === $key) { return $row; } } return null; } public function test_병합본에만_있고_own_에_없는_og_키는_상속_출처로_분류된다(): void { // 병합본 = base(type=website) + 자식(자체 og 없음). own = 자식 직접 선언분(og 비움). $merged = ['og' => ['type' => 'website']]; $own = ['og' => []]; // 자식은 og.type 을 직접 선언하지 않음 → 상속. $preview = $this->service()->preview($merged, [], [], $own); $typeRow = $this->ogRow($preview, 'type'); $this->assertNotNull($typeRow); $this->assertSame('inherited', $typeRow['source'], 'base 상속 키는 source=inherited'); $this->assertTrue($typeRow['inheritedFromBase']); $this->assertFalse($typeRow['overriddenByLayout'], '상속은 자체 override 아님'); } public function test_자식이_직접_채운_og_키는_layout_출처로_분류된다(): void { // 병합본 = 자식이 type=product 로 덮음. own 에도 동일하게 존재 → 자체 override. $merged = ['og' => ['type' => 'product']]; $own = ['og' => ['type' => 'product']]; $preview = $this->service()->preview($merged, [], [], $own); $typeRow = $this->ogRow($preview, 'type'); $this->assertNotNull($typeRow); $this->assertSame('layout', $typeRow['source'], '자식 직접 입력은 source=layout'); $this->assertFalse($typeRow['inheritedFromBase']); $this->assertTrue($typeRow['overriddenByLayout']); } public function test_own_미전달이면_상속_구분_없이_종전대로_layout(): void { // own_seo 미전달(null) → 상속/자체 구분 안 함(하위호환). $merged = ['og' => ['type' => 'website']]; $preview = $this->service()->preview($merged, [], []); $typeRow = $this->ogRow($preview, 'type'); $this->assertNotNull($typeRow); $this->assertSame('layout', $typeRow['source']); $this->assertFalse($typeRow['inheritedFromBase']); } }