Files
Gnuboard7/tests/Unit/Services/SettingsServiceAppConfigTest.php
T
HeuJung 8b9bfd0c91 refactor(ecommerce,pay): 간편결제 브랜드 마크 카탈로그 편입 + 애플페이 iOS 게이팅 이관
3사 PG 플러그인의 체크아웃 DOM 인젝터가 렌더 후 브랜드 마크를 주입하고
injector끼리 같은 버튼을 두고 경합하던 구조를 제거한다. 시각정보(색 배지/
SVG 로고)를 결제수단 카탈로그(_cached_brand_mark)로 편입해 코어 체크아웃
레이아웃의 신규 BrandMark 컴포넌트가 데이터 기반으로 직접 렌더한다.
reconcileKginicisPatchedDuplicates(중복 방어)도 함께 제거했다.

애플페이 iOS 게이팅도 injector 밖으로 이관: 이커머스 모듈 DetectDevice
미들웨어가 UA로 기기를 판정해 코어 core.frontend.filter_app_config 훅으로
appConfig.isIos를 주입하고(1차), 템플릿 부트스트랩이 iPadOS 데스크탑 UA를
클라 신호로 보정한다(2차). 코어는 훅만 제공하고 감지 로직은 모듈이 소유한다.

미들웨어는 Router::pushMiddlewareToGroup 이 아닌 HTTP Kernel::appendMiddlewareToGroup
으로 web 그룹에 등록한다(전자는 Router 레지스트리만 갱신해 SSR 셸 요청 파이프라인에
미반영 → isIos 항상 false). sirsoft-gdpr 선례와 동일.

코어 최소 요구 버전 7.0.5 상향(appConfig 필터 훅 도입).
2026-07-15 17:50:39 +09:00

205 lines
7.1 KiB
PHP

<?php
namespace Tests\Unit\Services;
use App\Extension\HookManager;
use App\Services\SettingsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* SettingsService의 getAppConfigForFrontend() 메서드 테스트
*
* config/frontend.php 스키마를 기반으로 config() 값을 프론트엔드에 노출하는
* 기능을 검증합니다.
*/
class SettingsServiceAppConfigTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed();
}
/**
* getAppConfigForFrontend()가 supportedTimezones를 올바른 Select 옵션 형식으로 반환하는지 테스트합니다.
*
* 전 IANA 타임존 지원 + 오프셋 라벨 형식
*/
public function test_get_app_config_returns_supported_timezones(): void
{
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertArrayHasKey('supportedTimezones', $result);
$this->assertIsArray($result['supportedTimezones']);
$this->assertNotEmpty($result['supportedTimezones']);
// 각 항목이 {value, label} 형식인지 확인
foreach ($result['supportedTimezones'] as $item) {
$this->assertIsArray($item);
$this->assertArrayHasKey('value', $item);
$this->assertArrayHasKey('label', $item);
$this->assertMatchesRegularExpression(
'/^\(UTC[+-]\d{2}:\d{2}\) .+/',
$item['label']
);
}
// Asia/Seoul, UTC가 포함되는지 확인 (value 기준)
$values = array_column($result['supportedTimezones'], 'value');
$this->assertContains('Asia/Seoul', $values);
$this->assertContains('UTC', $values);
}
/**
* supportedTimezones가 IANA 전체 목록 크기와 일치하는지 테스트합니다.
*/
public function test_supported_timezones_contains_full_iana_list(): void
{
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertCount(
count(\DateTimeZone::listIdentifiers()),
$result['supportedTimezones']
);
}
/**
* supportedTimezones가 오프셋 오름차순으로 정렬되는지 테스트합니다.
*/
public function test_supported_timezones_sorted_by_offset_ascending(): void
{
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$now = new \DateTime('now', new \DateTimeZone('UTC'));
$prevOffset = null;
foreach ($result['supportedTimezones'] as $item) {
$offset = (new \DateTimeZone($item['value']))->getOffset($now);
if ($prevOffset !== null) {
$this->assertGreaterThanOrEqual($prevOffset, $offset);
}
$prevOffset = $offset;
}
}
/**
* getAppConfigForFrontend()가 supportedLocales를 올바르게 반환하는지 테스트합니다.
*/
public function test_get_app_config_returns_supported_locales(): void
{
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertArrayHasKey('supportedLocales', $result);
$this->assertIsArray($result['supportedLocales']);
$this->assertContains('ko', $result['supportedLocales']);
$this->assertContains('en', $result['supportedLocales']);
}
/**
* getAppConfigForFrontend()가 core.frontend.filter_app_config 훅으로 확장이 주입한 값을
* appConfig 에 반영하는지 테스트합니다. (예: 이커머스 모듈이 요청 기기 유형 isIos 를 주입)
*/
public function test_get_app_config_applies_frontend_filter_hook(): void
{
// 우선순위 100 — 이커머스 모듈이 등록한 InjectAppConfigDeviceListener(priority 20)보다
// 뒤에 실행되어, 전체 스위트에서 그 리스너가 함께 등록돼 있어도 이 테스트의 주입값이
// 최종적으로 반영됨을 검증한다(훅 적용 메커니즘 자체 검증, 스위트 순서 비의존).
HookManager::addFilter(
'core.frontend.filter_app_config',
function (array $appConfig): array {
$appConfig['isIos'] = true;
$appConfig['__testHookMarker'] = 'applied';
return $appConfig;
},
100
);
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertTrue($result['isIos']);
// 훅이 실제로 적용됐음을 별도 마커로도 확인(다른 리스너 간섭과 무관).
$this->assertSame('applied', $result['__testHookMarker'] ?? null);
// 기존 정적 값은 그대로 유지된다.
$this->assertArrayHasKey('supportedLocales', $result);
}
/**
* config/frontend.php의 app_config 스키마가 비면 config 파생 키가 없는지 테스트합니다.
*
* 참고: core.frontend.filter_app_config 훅으로 확장(예: 이커머스 isIos)이 값을 주입할 수
* 있으므로 결과가 완전히 비어 있음을 단정하지 않고, config 스키마 파생 키의 부재만 확인한다.
*/
public function test_get_app_config_has_no_config_derived_keys_when_schema_empty(): void
{
// config 스키마를 비움
config(['frontend.app_config' => []]);
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertIsArray($result);
// 스키마 파생 키(config_key 기반)는 없어야 한다.
$this->assertArrayNotHasKey('supportedTimezones', $result);
$this->assertArrayNotHasKey('supportedLocales', $result);
$this->assertArrayNotHasKey('version', $result);
}
/**
* config_key가 없는 필드 스키마는 무시되는지 테스트합니다.
*/
public function test_get_app_config_skips_fields_without_config_key(): void
{
config(['frontend.app_config' => [
'validKey' => [
'config_key' => 'app.supported_timezones',
'type' => 'array',
],
'invalidKey' => [
'type' => 'string',
// config_key 누락
],
]]);
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertArrayHasKey('validKey', $result);
$this->assertArrayNotHasKey('invalidKey', $result);
}
/**
* castValue가 올바른 타입으로 캐스팅하는지 테스트합니다.
*/
public function test_get_app_config_casts_values_by_type(): void
{
config(['frontend.app_config' => [
'arrayValue' => [
'config_key' => 'app.supported_timezones',
'type' => 'array',
],
]]);
$service = app(SettingsService::class);
$result = $service->getAppConfigForFrontend();
$this->assertIsArray($result['arrayValue']);
}
}