adminUser = $this->createAdminUser([]); // Unit 테스트에서는 HTTP 요청 없이 Auth::id()를 사용하므로 // web guard와 sanctum guard 모두에 사용자를 설정 Auth::setUser($this->adminUser); $this->actingAs($this->adminUser); $this->service = app(PageService::class); } // ─── createPage ──────────────────────────────────── /** * 페이지를 생성할 수 있는지 확인 */ public function test_create_page_returns_page_model(): void { $data = [ 'slug' => 'test-svc-create', 'title' => ['ko' => '서비스 테스트', 'en' => 'Service Test'], 'content' => ['ko' => '본문', 'en' => 'Content'], 'content_mode' => 'html', ]; $page = $this->service->createPage($data); $this->assertInstanceOf(Page::class, $page); $this->assertEquals('test-svc-create', $page->slug); $this->assertEquals(1, $page->current_version); } /** * 페이지 생성 시 버전 1 스냅샷이 자동 생성되는지 확인 */ public function test_create_page_creates_version_one(): void { $page = $this->service->createPage([ 'slug' => 'test-svc-version', 'title' => ['ko' => '버전 테스트', 'en' => 'Version'], 'content' => ['ko' => '본문', 'en' => 'Body'], 'content_mode' => 'html', ]); $versions = PageVersion::where('page_id', $page->id)->get(); $this->assertCount(1, $versions); $this->assertEquals(1, $versions->first()->version); } /** * 발행 상태로 생성 시 published_at이 설정되는지 확인 */ public function test_create_published_page_sets_published_at(): void { $page = $this->service->createPage([ 'slug' => 'test-svc-pub', 'title' => ['ko' => '발행', 'en' => 'Published'], 'content' => ['ko' => '본문', 'en' => 'Body'], 'content_mode' => 'html', 'published' => true, ]); $this->assertTrue($page->published); $this->assertNotNull($page->published_at); } // ─── updatePage ──────────────────────────────────── /** * 페이지를 수정할 수 있는지 확인 */ public function test_update_page_changes_title(): void { $page = $this->createTestPage('test-svc-update'); $updated = $this->service->updatePage($page, [ 'title' => ['ko' => '수정됨', 'en' => 'Updated'], ]); $this->assertEquals('수정됨', $updated->title['ko']); } /** * 수정 시 버전 번호가 증가하는지 확인 */ public function test_update_page_increments_version(): void { $page = $this->createTestPage('test-svc-ver-inc'); $originalVersion = $page->current_version; $updated = $this->service->updatePage($page, [ 'title' => ['ko' => '버전 증가', 'en' => 'Version Up'], ]); $this->assertEquals($originalVersion + 1, $updated->current_version); } /** * 수정 시 새 버전 스냅샷이 생성되는지 확인 */ public function test_update_page_creates_new_version_snapshot(): void { $page = $this->createTestPage('test-svc-snap'); $this->service->updatePage($page, [ 'title' => ['ko' => '스냅샷', 'en' => 'Snapshot'], ]); $versionCount = PageVersion::where('page_id', $page->id)->count(); $this->assertEquals(2, $versionCount); // 생성 시 1 + 수정 시 1 } // ─── deletePage ──────────────────────────────────── /** * 페이지를 소프트 삭제할 수 있는지 확인 */ public function test_delete_page_soft_deletes(): void { $page = $this->createTestPage('test-svc-delete'); $result = $this->service->deletePage($page); $this->assertTrue($result); $this->assertSoftDeleted('pages', ['id' => $page->id]); } // ─── changePublishStatus ─────────────────────────── /** * 발행 상태를 변경할 수 있는지 확인 */ public function test_change_publish_status_to_published(): void { $page = $this->createTestPage('test-svc-publish'); $published = $this->service->changePublishStatus($page, true); $this->assertTrue($published->published); $this->assertNotNull($published->published_at); } /** * 미발행 상태로 변경할 수 있는지 확인 */ public function test_change_publish_status_to_unpublished(): void { $page = Page::factory()->published()->create([ 'slug' => 'test-svc-unpub', 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $unpublished = $this->service->changePublishStatus($page, false); $this->assertFalse($unpublished->published); } // ─── bulkChangePublishStatus ─────────────────────── /** * 여러 페이지를 일괄 발행할 수 있는지 확인 */ public function test_bulk_change_publish_status(): void { $pages = Page::factory()->count(3)->create([ 'published' => false, 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $count = $this->service->bulkChangePublishStatus( $pages->pluck('id')->toArray(), true ); $this->assertEquals(3, $count); } // ─── restoreVersion ──────────────────────────────── /** * 특정 버전으로 복원할 수 있는지 확인 */ public function test_restore_version_reverts_content(): void { $page = $this->createTestPage('test-svc-restore'); $originalTitle = $page->title; // 수정하여 버전 2 생성 $page = $this->service->updatePage($page, [ 'title' => ['ko' => '변경된 제목', 'en' => 'Changed Title'], ]); // 버전 1 가져오기 $version1 = PageVersion::where('page_id', $page->id) ->where('version', 1) ->first(); // 버전 1로 복원 $restored = $this->service->restoreVersion($page, $version1->id); $this->assertEquals($originalTitle['ko'], $restored->title['ko']); $this->assertEquals(3, $restored->current_version); // 복원 후 버전 증가 } // ─── slugExists ──────────────────────────────────── /** * 슬러그 존재 여부를 확인할 수 있는지 검증 */ public function test_slug_exists_returns_true_for_existing_slug(): void { $this->createTestPage('test-svc-slug-exist'); $this->assertTrue($this->service->slugExists('test-svc-slug-exist')); } /** * 존재하지 않는 슬러그는 false를 반환하는지 확인 */ public function test_slug_exists_returns_false_for_nonexistent(): void { $this->assertFalse($this->service->slugExists('test-svc-no-slug')); } /** * excludeId로 자기 자신을 제외할 수 있는지 확인 */ public function test_slug_exists_excludes_own_id(): void { $page = $this->createTestPage('test-svc-exclude'); $this->assertFalse($this->service->slugExists('test-svc-exclude', $page->id)); } // ─── getPublishedPageBySlug ──────────────────────── /** * 발행된 페이지를 슬러그로 조회할 수 있는지 확인 */ public function test_get_published_page_by_slug(): void { Page::factory()->published()->create([ 'slug' => 'test-svc-pub-slug', 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $page = $this->service->getPublishedPageBySlug('test-svc-pub-slug'); $this->assertNotNull($page); $this->assertEquals('test-svc-pub-slug', $page->slug); } /** * 미발행 페이지는 슬러그로 조회할 수 없는지 확인 */ public function test_get_published_page_by_slug_returns_null_for_draft(): void { Page::factory()->create([ 'slug' => 'test-svc-draft-slug', 'published' => false, 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $page = $this->service->getPublishedPageBySlug('test-svc-draft-slug'); $this->assertNull($page); } // ─── scope 검증 (AccessDeniedHttpException) ──── /** * getPage()에서 scope=self일 때 타인의 페이지 조회 시 AccessDeniedHttpException을 던지는지 확인 */ public function test_get_page_throws_exception_for_scope_denied(): void { // scope=self 사용자 설정 $scopeUser = $this->setupScopeUser(ScopeType::Self); Auth::setUser($scopeUser); $this->actingAs($scopeUser); // 다른 사용자가 만든 페이지 $page = Page::factory()->create([ 'slug' => 'test-svc-scope-show', 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $this->expectException(AccessDeniedHttpException::class); $this->service->getPage($page->id); } /** * getPage()에서 scope=self일 때 자기 페이지는 정상 조회되는지 확인 */ public function test_get_page_allows_own_page_with_scope_self(): void { $scopeUser = $this->setupScopeUser(ScopeType::Self); Auth::setUser($scopeUser); $this->actingAs($scopeUser); $page = Page::factory()->create([ 'slug' => 'test-svc-scope-show-own', 'created_by' => $scopeUser->id, 'updated_by' => $scopeUser->id, ]); $result = $this->service->getPage($page->id); $this->assertEquals($page->id, $result->id); } /** * updatePage()에서 scope=self일 때 타인의 페이지 수정 시 AccessDeniedHttpException을 던지는지 확인 */ public function test_update_page_throws_exception_for_scope_denied(): void { $scopeUser = $this->setupScopeUser(ScopeType::Self); Auth::setUser($scopeUser); $this->actingAs($scopeUser); $page = Page::factory()->create([ 'slug' => 'test-svc-scope-update', 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $this->expectException(AccessDeniedHttpException::class); $this->service->updatePage($page, [ 'title' => ['ko' => '수정 시도', 'en' => 'Attempt'], ]); } /** * deletePage()에서 scope=self일 때 타인의 페이지 삭제 시 AccessDeniedHttpException을 던지는지 확인 */ public function test_delete_page_throws_exception_for_scope_denied(): void { $scopeUser = $this->setupScopeUser(ScopeType::Self); Auth::setUser($scopeUser); $this->actingAs($scopeUser); $page = Page::factory()->create([ 'slug' => 'test-svc-scope-delete', 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $this->expectException(AccessDeniedHttpException::class); $this->service->deletePage($page); } /** * changePublishStatus()에서 scope=self일 때 타인의 페이지 발행 변경 시 AccessDeniedHttpException을 던지는지 확인 */ public function test_change_publish_status_throws_exception_for_scope_denied(): void { $scopeUser = $this->setupScopeUser(ScopeType::Self); Auth::setUser($scopeUser); $this->actingAs($scopeUser); $page = Page::factory()->create([ 'slug' => 'test-svc-scope-publish', 'published' => false, 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $this->expectException(AccessDeniedHttpException::class); $this->service->changePublishStatus($page, true); } /** * restoreVersion()에서 scope=self일 때 타인의 페이지 버전 복원 시 AccessDeniedHttpException을 던지는지 확인 */ public function test_restore_version_throws_exception_for_scope_denied(): void { $scopeUser = $this->setupScopeUser(ScopeType::Self); Auth::setUser($scopeUser); $this->actingAs($scopeUser); $page = Page::factory()->create([ 'slug' => 'test-svc-scope-restore', 'current_version' => 1, 'created_by' => $this->adminUser->id, 'updated_by' => $this->adminUser->id, ]); $version = PageVersion::create([ 'page_id' => $page->id, 'version' => 1, 'title' => $page->title, 'content' => $page->content ?? ['ko' => '', 'en' => ''], 'content_mode' => 'html', 'created_by' => $this->adminUser->id, ]); $this->expectException(AccessDeniedHttpException::class); $this->service->restoreVersion($page, $version->id); } // ─── 헬퍼 ──────────────────────────────────────── /** * 테스트용 페이지를 생성합니다. */ private function createTestPage(string $slug): Page { return $this->service->createPage([ 'slug' => $slug, 'title' => ['ko' => '테스트 페이지', 'en' => 'Test Page'], 'content' => ['ko' => '테스트 본문', 'en' => 'Test Content'], 'content_mode' => 'html', ]); } /** * scope 권한이 설정된 사용자를 생성합니다. * * @param ScopeType $scopeType 스코프 타입 * @return User 생성된 사용자 */ private function setupScopeUser(ScopeType $scopeType): User { $user = User::factory()->create(); $role = Role::firstOrCreate( ['identifier' => 'test_page_svc_scope'], [ 'name' => ['ko' => '페이지 서비스 스코프', 'en' => 'Page Service Scope'], 'is_active' => true, ] ); $user->roles()->attach($role->id); $permissionIds = [ 'sirsoft-page.pages.read', 'sirsoft-page.pages.create', 'sirsoft-page.pages.update', 'sirsoft-page.pages.delete', ]; foreach ($permissionIds as $identifier) { $permission = Permission::firstOrCreate( ['identifier' => $identifier], [ 'name' => ['ko' => $identifier, 'en' => $identifier], 'type' => PermissionType::Admin, ] ); $permission->update([ 'resource_route_key' => 'page', 'owner_key' => 'created_by', ]); $role->permissions()->syncWithoutDetaching([ $permission->id => ['scope_type' => $scopeType], ]); } // PermissionHelper 캐시 초기화 $reflection = new \ReflectionClass(PermissionHelper::class); $prop = $reflection->getProperty('permissionCache'); $prop->setAccessible(true); $prop->setValue(null, []); return $user; } }