markTestSkipped(self::$skipReason); } } /** * vendor 가 없는 신규 설치: 판정 불가로 두고, 마법사가 자동 설치한다는 안내만 낸다. * * @effects installer_env_check_shows_dev_vendor_card_without_gating */ #[Test] public function vendor_가_없으면_자동_설치_안내로_보고한다(): void { $result = $this->invokeCheck(); $this->assertFalse($result['required'], 'Composer 의존성 구성은 선택 항목이다'); $this->assertFalse($result['vendor_exists']); $this->assertNull($result['dev'], 'vendor 가 없으면 판정 불가다'); $this->assertSame([], $result['packages']); $this->assertNotSame('', trim($result['message'])); } /** * dev vendor 를 감지하면 목록과 함께 보고하되 `required` 는 그대로 false 다. */ #[Test] public function dev_vendor_를_감지해도_필수_항목으로_승격되지_않는다(): void { $this->writeVendor(['packages' => [], 'dev' => true, 'dev-package-names' => ['laravel/boost', 'laravel/mcp']]); $result = $this->invokeCheck(); $this->assertFalse($result['required'], 'dev 감지는 설치를 차단하지 않는다 (D1)'); $this->assertTrue($result['vendor_exists']); $this->assertTrue($result['dev']); $this->assertSame(['laravel/boost', 'laravel/mcp'], $result['packages']); } /** * `--no-dev` vendor 는 통과로 보고한다. */ #[Test] public function 운영용_vendor_는_통과로_보고한다(): void { $this->writeVendor(['packages' => [], 'dev' => false, 'dev-package-names' => []]); $result = $this->invokeCheck(); $this->assertFalse($result['required']); $this->assertTrue($result['vendor_exists']); $this->assertFalse($result['dev']); $this->assertSame([], $result['packages']); } /** * 게이트(`isAllRequiredPassed`)가 이 항목을 보지 않는다 — 소스 계약. * * 반환값 검사만으로는 부족하다: 게이트가 나중에 이 키를 참조하도록 바뀌어도 * `required=false` 자체는 그대로라 행위 테스트가 통과한다. */ #[Test] public function 필수_통과_판정이_이_항목을_참조하지_않는다(): void { $source = (string) file_get_contents(dirname(__DIR__, 3).'/public/install/api/check-configuration.php'); $start = strpos($source, 'private function isAllRequiredPassed('); $this->assertNotFalse($start, '전제: 게이트 메서드가 존재한다'); $end = strpos($source, "\n }", $start); $this->assertNotFalse($end, '전제: 게이트 메서드 본문을 잘라낼 수 있다'); $body = substr($source, $start, $end - $start); $this->assertStringNotContainsString( 'vendor_dev_packages', $body, '선택 항목이 필수 통과 판정에 들어가면 dev vendor 로 준비한 설치가 시작조차 되지 않는다' ); } /** * 화면 카드가 존재하고 실패 목록에 넣지 않는다 — 소스 계약. */ #[Test] public function 설치_화면_카드가_실패_목록에_넣지_않는다(): void { $js = (string) file_get_contents(dirname(__DIR__, 3).'/public/install/assets/js/installer.js'); $start = strpos($js, 'data.vendor_dev_packages && data.vendor_dev_packages.vendor_exists'); $this->assertNotFalse($start, 'vendor_dev_packages 카드 렌더 블록이 있어야 한다'); $end = strpos($js, '// 10. 자산 URL 방식 카드', $start); $this->assertNotFalse($end, '전제: 다음 카드 블록이 경계를 이룬다'); $block = substr($js, $start, $end - $start); $this->assertStringContainsString("lang('vendor_dev_packages')", $block, '카드 제목을 언어 키로 그려야 한다'); $this->assertStringNotContainsString('failedRequirements.push', $block, '선택 항목은 실패 목록에 넣지 않는다'); } /** * `checkVendorDevPackages()` 를 리플렉션으로 호출합니다. * * @return array 판정 결과 */ private function invokeCheck(): array { $api = new \ValidationApi; $method = new \ReflectionMethod(\ValidationApi::class, 'checkVendorDevPackages'); $method->setAccessible(true); /** @var array $result */ $result = $method->invoke($api); return $result; } /** * 임시 BASE_PATH 아래에 vendor 픽스처를 만듭니다. * * @param array $installedJson installed.json 내용 */ private function writeVendor(array $installedJson): void { $vendor = self::$sharedBase.'/vendor'; if (! is_dir($vendor.'/composer')) { mkdir($vendor.'/composer', 0755, true); } file_put_contents($vendor.'/autoload.php', "