testBasePath = storage_path('app/test_core_backup'); $this->backupsBasePath = storage_path('app/core_backups'); // 테스트용 디렉토리 구조 생성 File::ensureDirectoryExists($this->testBasePath); } protected function tearDown(): void { if (File::isDirectory($this->testBasePath)) { File::deleteDirectory($this->testBasePath); } if (File::isDirectory($this->backupsBasePath)) { File::deleteDirectory($this->backupsBasePath); } parent::tearDown(); } // ======================================================================== // restoreFromBackup() - 개별 target 실패 시 중단 방지 // ======================================================================== /** * 전체 복원 성공 시 빈 배열을 반환하는지 검증합니다. */ public function test_restore_from_backup_returns_empty_array_on_full_success(): void { // 백업 소스 준비 $backupPath = $this->testBasePath.'/backup'; File::ensureDirectoryExists($backupPath.'/app'); File::put($backupPath.'/app/test.php', 'assertIsArray($failedTargets); $this->assertEmpty($failedTargets); // 원복 File::put($destComposer, $originalComposer); File::delete(base_path('app/test.php')); } /** * 백업에 없는 target은 건너뛰고 성공으로 처리하는지 검증합니다. */ public function test_restore_from_backup_skips_missing_targets(): void { $backupPath = $this->testBasePath.'/backup'; File::ensureDirectoryExists($backupPath); File::put($backupPath.'/composer.json', '{}'); // 'nonexistent_dir'은 백업에 없으므로 건너뜀 $failedTargets = CoreBackupHelper::restoreFromBackup( $backupPath, ['nonexistent_dir', 'also_missing'], null, [] ); $this->assertEmpty($failedTargets); } /** * 개별 target 복원 실패 시 나머지 target은 계속 복원되는지 검증합니다. */ public function test_restore_continues_on_individual_target_failure(): void { $backupPath = $this->testBasePath.'/backup'; $restoreTarget = $this->testBasePath.'/restore_dest'; // 백업 소스: 정상 파일 + 복원 실패할 디렉토리 File::ensureDirectoryExists($backupPath.'/good_dir'); File::put($backupPath.'/good_dir/file.txt', 'good content'); File::ensureDirectoryExists($backupPath.'/another_good'); File::put($backupPath.'/another_good/file.txt', 'another content'); // good_dir, bad_dir, another_good 순서로 복원 // bad_dir은 백업에 없으므로 스킵 (실패가 아님) // 실제 복원 실패를 시뮬레이션하려면 FilePermissionHelper를 모킹해야 하므로 // 여기서는 반환값의 타입과 정상 케이스 동작을 검증 $failedTargets = CoreBackupHelper::restoreFromBackup( $backupPath, ['good_dir', 'another_good'], null, [] ); $this->assertEmpty($failedTargets); // 복원된 파일 확인 (base_path 기준) $this->assertFileExists(base_path('good_dir/file.txt')); $this->assertFileExists(base_path('another_good/file.txt')); // 정리 File::deleteDirectory(base_path('good_dir')); File::deleteDirectory(base_path('another_good')); } /** * 복원 실패한 target이 failedTargets 배열에 포함되는지 검증합니다. * 읽기 전용 디렉토리를 대상으로 실제 복원 실패를 유발합니다. */ public function test_restore_reports_failed_targets(): void { // Windows에서는 퍼미션 기반 실패 유발이 어려우므로 // 존재하지 않는 복원 대상 경로 대신, 잘못된 백업 구조를 활용 // 이 테스트는 반환 타입이 array인지 검증하는 데 초점 $backupPath = $this->testBasePath.'/backup'; File::ensureDirectoryExists($backupPath); $failedTargets = CoreBackupHelper::restoreFromBackup( $backupPath, ['target_that_does_not_exist_in_backup'], null, [] ); // 백업에 없는 target은 skip되므로 failed에 포함되지 않음 $this->assertIsArray($failedTargets); $this->assertEmpty($failedTargets); } // ======================================================================== // createBackup() - vendor 포함 백업 // ======================================================================== /** * targets에 vendor가 포함되면 vendor 디렉토리가 백업되는지 검증합니다. */ public function test_create_backup_includes_vendor_when_in_targets(): void { // vendor 디렉토리는 base_path에 있으므로 실제 존재 여부 확인 $this->assertDirectoryExists(base_path('vendor')); // excludes에서 vendor를 제거한 상태로 백업 (현재 config 반영) $backupPath = CoreBackupHelper::createBackup( ['vendor'], null, [] // vendor를 excludes에서 제거 ); $this->assertDirectoryExists($backupPath.'/vendor'); $this->assertFileExists($backupPath.'/vendor/autoload.php'); // 정리 CoreBackupHelper::deleteBackup($backupPath); } /** * excludes에 vendor가 있으면 다른 target 내 vendor 하위 디렉토리가 제외되는지 검증합니다. * (vendor 자체가 target이면 최상위로 복사됨) */ public function test_create_backup_excludes_nested_vendor_dirs(): void { // 테스트용 디렉토리 생성 $testDir = $this->testBasePath.'/exclude_test'; File::ensureDirectoryExists($testDir.'/src'); File::ensureDirectoryExists($testDir.'/vendor/package'); File::put($testDir.'/src/App.php', 'assertFileExists($backupPath.'/test_dir/src/App.php'); $this->assertFileDoesNotExist($backupPath.'/test_dir/vendor/package/lib.php'); // 정리 File::deleteDirectory($backupPath); } // ======================================================================== // config 설정 검증 // ======================================================================== /** * config targets에 vendor가 포함되지 않고 backup_only에 있는지 검증합니다. */ public function test_config_vendor_in_backup_only_not_targets(): void { $targets = config('app.update.targets', []); $backupOnly = config('app.update.backup_only', []); $this->assertNotContains('vendor', $targets); $this->assertContains('vendor', $backupOnly); } /** * config excludes에서 vendor가 제거되었는지 검증합니다. */ public function test_config_excludes_does_not_contain_vendor(): void { $excludes = config('app.update.excludes', []); $this->assertNotContains('vendor', $excludes); } // ======================================================================== // deleteBackup() / listBackups() // ======================================================================== /** * 백업 삭제가 정상 동작하는지 검증합니다. */ public function test_delete_backup_removes_directory(): void { $backupPath = $this->backupsBasePath.'/core_test_delete'; File::ensureDirectoryExists($backupPath); File::put($backupPath.'/test.txt', 'test'); CoreBackupHelper::deleteBackup($backupPath); $this->assertDirectoryDoesNotExist($backupPath); } /** * 백업 목록이 올바르게 반환되는지 검증합니다. */ public function test_list_backups_returns_correct_structure(): void { File::ensureDirectoryExists($this->backupsBasePath.'/core_20260101_120000'); File::ensureDirectoryExists($this->backupsBasePath.'/core_20260102_120000'); $backups = CoreBackupHelper::listBackups(); $this->assertCount(2, $backups); $this->assertArrayHasKey('path', $backups[0]); $this->assertArrayHasKey('name', $backups[0]); $this->assertArrayHasKey('created_at', $backups[0]); } }