$templateType, 'coreEngineSrc' => '/build/core/template-engine.min.js', 'componentsSrc' => '/api/templates/assets/sirsoft-basic/js/components.iife.js', 'initConfig' => ['templateId' => 'sirsoft-basic', 'locale' => 'ko'], ])->render(); } public function test_두_진입점_모두_사유별_분기를_렌더한다(): void { foreach (['user', 'admin'] as $templateType) { $html = $this->render($templateType); $this->assertStringContainsString( "? 'incompatible' : 'corrupt'", $html, "[{$templateType}] 전역 부재 경로가 SyntaxError 관측 결과로 사유를 가르지 않는다" ); $this->assertStringContainsString( "renderFallback(blocked ? 'blocked' : 'network')", $html, "[{$templateType}] 재시도 소진 경로가 차단/네트워크 사유를 가르지 않는다" ); $this->assertStringContainsString( 'data-g7-bootstrap-fallback', $html, "[{$templateType}] 폴백 사유 마커가 없다" ); } } /** * HTTPS 페이지가 http 자산을 요청해 차단된 경우를 별도 사유로 가른다 (#124). * * 이 실패는 회선이 멀쩡하고 새로고침으로도 낫지 않으므로, 네트워크 문구 + 새로고침 * 버튼은 두 겹의 거짓 안내가 된다. * * @effects blocked_branch_is_emitted_with_https_http_judgment */ public function test_혼합_콘텐츠_차단을_별도_사유로_가른다(): void { foreach (['user', 'admin'] as $templateType) { $html = $this->render($templateType); $this->assertStringContainsString( "location.protocol === 'https:'", $html, "[{$templateType}] 차단 판정이 페이지 스킴을 보지 않는다" ); $this->assertStringContainsString( "indexOf('http://') === 0", $html, "[{$templateType}] 차단 판정이 자산 스킴을 보지 않는다" ); $this->assertStringContainsString( "blocked ? 'blocked' : 'network'", $html, "[{$templateType}] 폴백 마커가 blocked 값을 낼 수 없다" ); $this->assertStringContainsString( '(incompatible || blocked)', $html, "[{$templateType}] 차단 경로에서 새로고침 버튼이 생략되지 않는다" ); } } /** * 차단 안내는 화면(방문자)과 콘솔(운영자)로 대상을 나눈다 (#124). * * 화면 문구에 서버 설정 지시를 쓰면 방문자에게 실행 불가능한 지시를 주는 셈이고, * 원인을 통째로 감추면 운영자가 도달할 통로가 사라진다. * * @effects blocked_notice_splits_visitor_screen_and_operator_console */ public function test_차단_안내는_화면과_콘솔로_대상을_나눈다(): void { $html = $this->render(); // 콘솔에는 운영자 기준 원인·조치가 영문으로 남는다 (기존 로그와 같은 계열). $this->assertStringContainsString('Blocked as mixed content', $html); $this->assertStringContainsString('TRUSTED_PROXIES', $html); $this->assertStringContainsString('docs/backend/reverse-proxy.md', $html); $original = app()->getLocale(); try { foreach (['ko', 'en'] as $locale) { app()->setLocale($locale); foreach (['blocked_title', 'blocked_message'] as $key) { $value = __("errors.bootstrap.{$key}"); $this->assertNotSame("errors.bootstrap.{$key}", $value, "[{$locale}] errors.bootstrap.{$key} 미정의"); $this->assertNotSame('', trim($value), "[{$locale}] errors.bootstrap.{$key} 가 비어 있다"); } // 사유가 다르면 문구도 달라야 한다 — 같으면 분기가 무의미하다. $this->assertNotSame( __('errors.bootstrap.message'), __('errors.bootstrap.blocked_message'), "[{$locale}] 차단 문구가 네트워크 문구와 동일하다" ); // 화면 문구는 방문자 기준 — 서버 설정 지시를 담지 않는다. $this->assertStringNotContainsString( 'TRUSTED_PROXIES', __('errors.bootstrap.blocked_message'), "[{$locale}] 방문자 화면에 서버 설정 지시가 노출된다" ); $this->assertStringNotContainsString( '.env', __('errors.bootstrap.blocked_message'), "[{$locale}] 방문자 화면에 서버 설정 지시가 노출된다" ); } } finally { app()->setLocale($original); } } public function test_파싱_오류_관측_리스너가_설치된다(): void { $html = $this->render(); // 파싱 실패는