templateService = app(TemplateService::class); // 이전 테스트의 public/build 디렉토리 정리 (Windows 권한 문제 방지) $this->cleanupPublicBuildDirectory(); } /** * 테스트 종료 후 생성된 템플릿 및 빌드 디렉토리 정리 */ protected function tearDown(): void { // 생성된 모든 템플릿 디렉토리 정리 foreach ($this->createdTemplates as $identifier) { $this->cleanupTemplateDirectory($identifier); } // public/build/template 디렉토리 정리 $this->cleanupPublicBuildDirectory(); parent::tearDown(); } /** * 템플릿 활성화 시 기존 활성 템플릿이 비활성화되는지 테스트 */ public function test_activating_template_deactivates_current_active_template(): void { // Arrange: 같은 타입의 템플릿 2개 생성 $activeTemplate = Template::factory()->create([ 'identifier' => 'test-active', 'type' => 'admin', 'status' => 'active', ]); $newTemplate = Template::factory()->create([ 'identifier' => 'test-new', 'type' => 'admin', 'status' => 'inactive', ]); // 템플릿 디렉토리 생성 (파일 복사 오류 방지) $this->createTemplateDistDirectory($newTemplate->identifier); // Act: 새 템플릿 활성화 $this->templateService->activateTemplate($newTemplate->id); // Assert: 기존 템플릿이 비활성화되었는지 확인 $this->assertDatabaseHas('templates', [ 'id' => $activeTemplate->id, 'status' => 'inactive', ]); // Assert: 새 템플릿이 활성화되었는지 확인 $this->assertDatabaseHas('templates', [ 'id' => $newTemplate->id, 'status' => 'active', ]); } /** * 템플릿 활성화 시 파일이 public/build/template/{type}/로 복사되는지 테스트 */ public function test_activating_template_copies_files_to_public(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-copy', 'type' => 'admin', 'status' => 'inactive', ]); $this->createTemplateDistDirectory($template->identifier); // Act $this->templateService->activateTemplate($template->id); // Assert: 템플릿이 활성화되었는지 확인 (파일 복사는 더 이상 수행하지 않음) $this->assertDatabaseHas('templates', [ 'id' => $template->id, 'status' => 'active', ]); } /** * 파일 복사 실패 시 DB 트랜잭션이 롤백되는지 테스트 * * NOTE: TemplateManager에 위임한 후, 파일 복사 로직은 TemplateManager에서 처리되므로 * 이 테스트는 TemplateManager의 테스트로 이동되어야 합니다. * 현재는 TemplateManager가 dist 디렉토리가 없어도 예외를 던지지 않으므로 skip합니다. */ public function test_file_copy_failure_rolls_back_database_transaction(): void { $this->markTestSkipped('TemplateManager에 위임 후, 이 테스트는 TemplateManager 테스트로 이동 필요'); } /** * 훅이 올바른 순서로 실행되는지 테스트 */ public function test_hooks_are_executed_in_correct_order(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-hooks', 'type' => 'admin', 'status' => 'inactive', ]); $this->createTemplateDistDirectory($template->identifier); $executionOrder = []; // 훅 리스너 등록 HookManager::addAction('core.templates.before_activate', function () use (&$executionOrder) { $executionOrder[] = 'before_activate'; }); HookManager::addAction('core.templates.after_activate', function () use (&$executionOrder) { $executionOrder[] = 'after_activate'; }); // Act $this->templateService->activateTemplate($template->id); // Assert: 훅이 올바른 순서로 실행되었는지 확인 // before_activate와 after_activate가 실행되었는지 확인 $this->assertContains('before_activate', $executionOrder); $this->assertContains('after_activate', $executionOrder); // before_activate가 after_activate보다 먼저 실행되었는지 확인 $beforeIndex = array_search('before_activate', $executionOrder); $afterIndex = array_search('after_activate', $executionOrder); $this->assertLessThan($afterIndex, $beforeIndex, 'before_activate should execute before after_activate'); } /** * 다른 타입의 템플릿은 영향받지 않는지 테스트 */ public function test_different_type_templates_are_not_affected(): void { // Arrange: 다른 타입의 활성 템플릿 $adminTemplate = Template::factory()->create([ 'identifier' => 'test-admin', 'type' => 'admin', 'status' => 'active', ]); $userTemplate = Template::factory()->create([ 'identifier' => 'test-user', 'type' => 'user', 'status' => 'active', ]); $newAdminTemplate = Template::factory()->create([ 'identifier' => 'test-newadmin', 'type' => 'admin', 'status' => 'inactive', ]); $this->createTemplateDistDirectory($newAdminTemplate->identifier); // Act: admin 타입 템플릿 활성화 $this->templateService->activateTemplate($newAdminTemplate->id); // Assert: admin 타입만 변경되고 user 타입은 그대로인지 확인 $this->assertDatabaseHas('templates', [ 'id' => $adminTemplate->id, 'status' => 'inactive', ]); $this->assertDatabaseHas('templates', [ 'id' => $userTemplate->id, 'status' => 'active', // user 타입은 여전히 active ]); $this->assertDatabaseHas('templates', [ 'id' => $newAdminTemplate->id, 'status' => 'active', ]); } /** * assets 디렉토리가 있을 경우 함께 복사되는지 테스트 */ public function test_assets_directory_is_copied_if_exists(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-assets', 'type' => 'admin', 'status' => 'inactive', ]); $this->createTemplateDistDirectory($template->identifier); $this->createTemplateAssetsDirectory($template->identifier); // Act $this->templateService->activateTemplate($template->id); // Assert: 템플릿이 활성화되었는지 확인 (파일 복사는 더 이상 수행하지 않음) $this->assertDatabaseHas('templates', [ 'id' => $template->id, 'status' => 'active', ]); } /** * 템플릿 활성화 시 identifier로 호출 가능한지 테스트 */ public function test_can_activate_template_with_identifier(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-activateidentifier', 'type' => 'admin', 'status' => 'inactive', ]); $this->createTemplateDistDirectory($template->identifier); // Act: identifier로 활성화 $result = $this->templateService->activateTemplate($template->identifier); // Assert: 템플릿이 활성화되었는지 확인 $this->assertDatabaseHas('templates', [ 'identifier' => $template->identifier, 'status' => 'active', ]); // activateTemplate은 배열을 반환함 $this->assertIsArray($result); $this->assertEquals($template->identifier, $result['template_info']['identifier']); $this->assertEquals('active', $result['template_info']['status']); } /** * 템플릿 활성화 시 ID로 호출 가능한지 테스트 */ public function test_can_activate_template_with_id(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-activateid', 'type' => 'admin', 'status' => 'inactive', ]); $this->createTemplateDistDirectory($template->identifier); // Act: ID로 활성화 $result = $this->templateService->activateTemplate($template->id); // Assert: 템플릿이 활성화되었는지 확인 $this->assertDatabaseHas('templates', [ 'id' => $template->id, 'status' => 'active', ]); // activateTemplate은 배열을 반환함 $this->assertIsArray($result); $this->assertEquals($template->identifier, $result['template_info']['identifier']); $this->assertEquals('active', $result['template_info']['status']); } /** * 존재하지 않는 identifier로 활성화 시도 시 예외 발생 테스트 */ public function test_activate_template_with_invalid_identifier_throws_exception(): void { // Arrange & Act & Assert $this->expectException(\Illuminate\Validation\ValidationException::class); $this->templateService->activateTemplate('non-existent-template'); } /** * 존재하지 않는 ID로 활성화 시도 시 예외 발생 테스트 */ public function test_activate_template_with_invalid_id_throws_exception(): void { // Arrange & Act & Assert $this->expectException(\Illuminate\Validation\ValidationException::class); $this->templateService->activateTemplate(99999); } /** * 템플릿 비활성화 시 identifier로 호출 가능한지 테스트 */ public function test_can_deactivate_template_with_identifier(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-deactivateidentifier', 'type' => 'admin', 'status' => 'active', ]); $this->createTemplateDistDirectory($template->identifier); // Act: identifier로 비활성화 $result = $this->templateService->deactivateTemplate($template->identifier); // Assert: 템플릿이 비활성화되었는지 확인 // deactivateTemplate 은 templateInfo 배열을 직접 반환 (activateTemplate 과 달리 template_info 래핑 없음) $this->assertIsArray($result); $this->assertEquals('inactive', $result['status']); $this->assertDatabaseHas('templates', [ 'identifier' => $template->identifier, 'status' => 'inactive', ]); } /** * 템플릿 비활성화 시 ID로 호출 가능한지 테스트 */ public function test_can_deactivate_template_with_id(): void { // Arrange $template = Template::factory()->create([ 'identifier' => 'test-deactivateid', 'type' => 'admin', 'status' => 'active', ]); $this->createTemplateDistDirectory($template->identifier); // Act: ID로 비활성화 $result = $this->templateService->deactivateTemplate($template->id); // Assert: 템플릿이 비활성화되었는지 확인 // deactivateTemplate 은 templateInfo 배열을 직접 반환 (activateTemplate 과 달리 template_info 래핑 없음) $this->assertIsArray($result); $this->assertEquals('inactive', $result['status']); $this->assertDatabaseHas('templates', [ 'id' => $template->id, 'status' => 'inactive', ]); } /** * 존재하지 않는 identifier로 비활성화 시도 시 예외 발생 테스트 */ public function test_deactivate_template_with_invalid_identifier_throws_exception(): void { // Arrange & Act & Assert $this->expectException(\Illuminate\Validation\ValidationException::class); $this->templateService->deactivateTemplate('non-existent-template'); } /** * 존재하지 않는 ID로 비활성화 시도 시 예외 발생 테스트 */ public function test_deactivate_template_with_invalid_id_throws_exception(): void { // Arrange & Act & Assert $this->expectException(\Illuminate\Validation\ValidationException::class); $this->templateService->deactivateTemplate(99999); } /** * 헬퍼 메서드: 템플릿 dist 디렉토리 생성 */ private function createTemplateDistDirectory(string $identifier): void { $templatePath = base_path("templates/{$identifier}"); $distPath = "{$templatePath}/dist"; File::makeDirectory($distPath, 0755, true); File::put("{$distPath}/test.js", 'test content'); // template.json 파일 생성 (TemplateManager가 인식하도록) $this->createTemplateJson($templatePath, $identifier); // TemplateManager의 템플릿 목록 재로드 app(\App\Contracts\Extension\TemplateManagerInterface::class)->loadTemplates(); // 생성된 템플릿 추적 (tearDown에서 자동 정리) $this->createdTemplates[] = $identifier; } /** * 헬퍼 메서드: template.json만 생성 (dist 디렉토리 없음) */ private function createTemplateJsonOnly(string $identifier): void { $templatePath = base_path("templates/{$identifier}"); File::makeDirectory($templatePath, 0755, true); // template.json 파일 생성 $this->createTemplateJson($templatePath, $identifier); // TemplateManager에 템플릿 로드 app(\App\Contracts\Extension\TemplateManagerInterface::class)->loadTemplates(); // 생성된 템플릿 추적 (tearDown에서 자동 정리) $this->createdTemplates[] = $identifier; } /** * 헬퍼 메서드: template.json 파일 생성 */ private function createTemplateJson(string $templatePath, string $identifier): void { $parts = explode('-', $identifier); $vendor = $parts[0] ?? 'unknown'; $name = $parts[1] ?? 'unknown'; $templateJson = [ 'identifier' => $identifier, 'vendor' => $vendor, 'name' => ['ko' => $name, 'en' => $name], 'version' => '1.0.0', 'type' => 'admin', 'description' => ['ko' => 'Test template', 'en' => 'Test template'], ]; File::put("{$templatePath}/template.json", json_encode($templateJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); } /** * 헬퍼 메서드: 템플릿 assets 디렉토리 생성 * * 주의: createTemplateDistDirectory를 먼저 호출해야 함 */ private function createTemplateAssetsDirectory(string $identifier): void { $assetsPath = base_path("templates/{$identifier}/assets"); File::makeDirectory($assetsPath, 0755, true); File::put("{$assetsPath}/test.css", 'test content'); } /** * 헬퍼 메서드: 템플릿 디렉토리 정리 */ private function cleanupTemplateDirectory(string $identifier): void { $templatePath = base_path("templates/{$identifier}"); if (File::isDirectory($templatePath)) { File::deleteDirectory($templatePath); } } /** * 헬퍼 메서드: public/build/template/ 디렉토리 정리 */ private function cleanupPublicBuildDirectory(): void { $buildPath = public_path('build/template'); if (File::isDirectory($buildPath)) { File::deleteDirectory($buildPath); } } }