Files
Gnuboard7/tests/Unit/Seo/SeoCandidateServiceTest.php
2026-07-01 10:30:32 +09:00

141 lines
6.1 KiB
PHP

<?php
namespace Tests\Unit\Seo;
use App\Seo\Editor\SeoCandidateService;
use ReflectionMethod;
use Tests\TestCase;
/**
* SeoCandidateService — 확장 후보 라벨 해석 단위.
*
* extensions 후보의 친화 라벨(localizeName)은 확장 getName()(string|array 다국어)을 로케일로
* 해석한다. Endpoint 테스트는 RefreshDatabase 환경에서 활성 확장이 0건이라 폴백 경로를 직접
* 보증하기 어려우므로(빈 배열 허용), 라벨 해석 규칙은 본 단위 테스트가 SSoT.
*/
class SeoCandidateServiceTest extends TestCase
{
private SeoCandidateService $service;
private ReflectionMethod $localize;
protected function setUp(): void
{
parent::setUp();
$this->service = app(SeoCandidateService::class);
$this->localize = new ReflectionMethod(SeoCandidateService::class, 'localizeName');
$this->localize->setAccessible(true);
}
private function localize(mixed $name, string $locale, string $fallbackId): string
{
return $this->localize->invoke($this->service, $name, $locale, $fallbackId);
}
public function test_string_name_returned_as_is(): void
{
$this->assertSame('이커머스', $this->localize('이커머스', 'ko', 'sirsoft-ecommerce'));
}
public function test_array_name_resolves_requested_locale(): void
{
$name = ['ko' => '이커머스', 'en' => 'Ecommerce'];
$this->assertSame('이커머스', $this->localize($name, 'ko', 'sirsoft-ecommerce'));
$this->assertSame('Ecommerce', $this->localize($name, 'en', 'sirsoft-ecommerce'));
}
public function test_array_name_falls_back_to_app_locale_then_first_value(): void
{
config(['app.locale' => 'en']);
// 요청 로케일(ja) 부재 → app.locale(en) 폴백.
$name = ['ko' => '이커머스', 'en' => 'Ecommerce'];
$this->assertSame('Ecommerce', $this->localize($name, 'ja', 'sirsoft-ecommerce'));
// 요청·app.locale 둘 다 부재 → 첫 값 폴백.
config(['app.locale' => 'fr']);
$this->assertSame('이커머스', $this->localize($name, 'ja', 'sirsoft-ecommerce'));
}
public function test_empty_or_invalid_name_falls_back_to_identifier(): void
{
$this->assertSame('sirsoft-ecommerce', $this->localize('', 'ko', 'sirsoft-ecommerce'));
$this->assertSame('sirsoft-ecommerce', $this->localize([], 'ko', 'sirsoft-ecommerce'));
$this->assertSame('sirsoft-ecommerce', $this->localize(null, 'ko', 'sirsoft-ecommerce'));
// 비문자열 값만 든 배열 → 폴백.
$this->assertSame('sirsoft-ecommerce', $this->localize(['ko' => 123], 'ko', 'sirsoft-ecommerce'));
}
/**
* collectVars 를 stub 확장으로 직접 호출(reflection). 게이팅 SSoT 검증 —
* 선언 확장(declaredExtensions)에 있는 확장의 seoVariables()[_common]+[pageType] 만 수집.
*
* @param array $activeExtensions [{type,id,instance}]
* @param array $declaredExtensions [{type,id}]
*/
private function collectVars(array $activeExtensions, array $declaredExtensions, ?string $pageType): array
{
$m = new ReflectionMethod(SeoCandidateService::class, 'collectVars');
$m->setAccessible(true);
return $m->invoke($this->service, $activeExtensions, $declaredExtensions, $pageType);
}
private function stubExtensionWithVars(): object
{
// seoVariables() 가 _common + product 그룹을 반환하는 stub 확장.
return new class
{
public function seoVariables(): array
{
return [
'_common' => ['commerce_name' => ['source' => 'setting', 'description' => '상점명']],
'product' => [
'product_name' => ['source' => 'data', 'required' => true, 'description' => '상품명'],
'product_description' => ['source' => 'data', 'description' => '상품 설명'],
],
];
}
};
}
public function test_collect_vars_gated_by_declared_extensions(): void
{
$active = [['type' => 'module', 'id' => 'sirsoft-ecommerce', 'instance' => $this->stubExtensionWithVars()]];
// 선언 확장에 ecommerce 가 있으면 _common + product 그룹 vars 수집.
$declared = [['type' => 'module', 'id' => 'sirsoft-ecommerce']];
$vars = $this->collectVars($active, $declared, 'product');
$names = array_column($vars, 'name');
$this->assertContains('commerce_name', $names, '_common vars 수집');
$this->assertContains('product_name', $names, 'pageType(product) vars 수집');
$this->assertContains('product_description', $names);
// source/required 메타 보존.
$byName = [];
foreach ($vars as $v) {
$byName[$v['name']] = $v;
}
$this->assertSame('setting', $byName['commerce_name']['source']);
$this->assertSame('data', $byName['product_name']['source']);
$this->assertTrue($byName['product_name']['required'] ?? false);
}
public function test_collect_vars_empty_when_extension_not_declared(): void
{
// 같은 active 확장이라도 declaredExtensions 에 없으면 게이팅으로 0건(프론트가 extensions 미전송 시 재현).
$active = [['type' => 'module', 'id' => 'sirsoft-ecommerce', 'instance' => $this->stubExtensionWithVars()]];
$vars = $this->collectVars($active, [], 'product');
$this->assertSame([], $vars, '선언 확장 0 → vars 게이팅 전량 제외');
}
public function test_collect_vars_common_only_when_page_type_null(): void
{
$active = [['type' => 'module', 'id' => 'sirsoft-ecommerce', 'instance' => $this->stubExtensionWithVars()]];
$declared = [['type' => 'module', 'id' => 'sirsoft-ecommerce']];
$vars = $this->collectVars($active, $declared, null);
$names = array_column($vars, 'name');
$this->assertContains('commerce_name', $names, 'page_type 미선택 → _common 만');
$this->assertNotContains('product_name', $names);
}
}