testPrefix = 'test_' . uniqid() . '_'; // HookManager 및 Laravel Event 초기화 (중복 등록 방지) HookManager::resetAll(); Event::forget('hook.core.activity.log'); Event::forget('hook.core.activity.admin'); Event::forget('hook.core.activity.user'); Event::forget('hook.core.activity.system'); // 설정 초기화 config(['activity_log.enabled' => true]); config(['activity_log.driver' => 'database']); // ActivityLogManager 인스턴스 초기화 $this->app->forgetInstance(ActivityLogManager::class); // 리스너 등록 - static 배열에만 등록 (addAction은 Event::listen도 호출하여 중복 실행됨) $listener = app(ActivityLogListener::class); $this->registerHookWithoutEvent('core.activity.log', [$listener, 'processLog']); $this->registerHookWithoutEvent('core.activity.admin', [$listener, 'handleAdmin']); $this->registerHookWithoutEvent('core.activity.user', [$listener, 'handleUser']); $this->registerHookWithoutEvent('core.activity.system', [$listener, 'handleSystem']); $this->activityLogService = app(ActivityLogService::class); } /** * HookManager의 static 배열에만 훅을 등록합니다. * addAction은 Event::listen도 호출하여 중복 실행되므로, static 배열에만 등록합니다. */ private function registerHookWithoutEvent(string $hookName, callable $callback, int $priority = 10): void { $reflection = new \ReflectionClass(HookManager::class); $property = $reflection->getProperty('hooks'); $hooks = $property->getValue(); if (! isset($hooks[$hookName])) { $hooks[$hookName] = []; } if (! isset($hooks[$hookName][$priority])) { $hooks[$hookName][$priority] = []; } $hooks[$hookName][$priority][] = $callback; $property->setValue(null, $hooks); } // ======================================================================== // log() - 기본 로그 기록 테스트 (훅 기반) // ======================================================================== /** * 기본 로그 기록이 훅을 통해 데이터베이스에 저장되는지 확인 */ public function test_log_creates_activity_log_via_hook(): void { $action = $this->testPrefix . 'action'; $this->activityLogService->log( ActivityLogType::Admin, $action, 'Test action description' ); $this->assertDatabaseHas('activity_logs', [ 'log_type' => ActivityLogType::Admin->value, 'action' => $action, 'description' => 'Test action description', ]); } /** * 로그에 대상 모델이 연결되는지 확인 */ public function test_log_with_loggable_model(): void { $user = User::factory()->create(); $action = $this->testPrefix . 'user.update'; $this->activityLogService->log( ActivityLogType::Admin, $action, 'User updated', $user ); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals($user->getMorphClass(), $log->loggable_type); $this->assertEquals($user->id, $log->loggable_id); } /** * 로그에 추가 속성이 저장되는지 확인 */ public function test_log_with_properties(): void { $properties = ['old_name' => 'John', 'new_name' => 'Jane']; $action = $this->testPrefix . 'user.update.props'; $this->activityLogService->log( ActivityLogType::Admin, $action, 'User name updated', null, $properties ); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals($properties, $log->properties); } /** * 인증된 사용자가 로그에 기록되는지 확인 */ public function test_log_records_authenticated_user(): void { $user = User::factory()->create(); $this->actingAs($user); $action = $this->testPrefix . 'auth.action'; $this->activityLogService->log( ActivityLogType::Admin, $action, 'Test action' ); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals($user->id, $log->user_id); } // ======================================================================== // logAdmin() / logUser() / logSystem() - 헬퍼 메서드 테스트 // ======================================================================== /** * logAdmin이 관리자 유형으로 로그를 생성하는지 확인 */ public function test_log_admin_creates_admin_type_log(): void { $action = $this->testPrefix . 'admin.action'; $this->activityLogService->logAdmin($action, 'Admin action'); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals(ActivityLogType::Admin, $log->log_type); } /** * logUser가 사용자 유형으로 로그를 생성하는지 확인 */ public function test_log_user_creates_user_type_log(): void { $action = $this->testPrefix . 'user.action'; $this->activityLogService->logUser($action, 'User action'); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals(ActivityLogType::User, $log->log_type); } /** * logSystem이 시스템 유형으로 로그를 생성하는지 확인 */ public function test_log_system_creates_system_type_log(): void { $action = $this->testPrefix . 'system.action'; $this->activityLogService->logSystem($action, 'System action'); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals(ActivityLogType::System, $log->log_type); } // ======================================================================== // getLogsForModel() - 모델별 로그 조회 테스트 // ======================================================================== /** * 특정 모델의 로그만 조회되는지 확인 */ public function test_get_logs_for_model_returns_only_model_logs(): void { $user1 = User::factory()->create(); $user2 = User::factory()->create(); $actionPrefix = $this->testPrefix . 'model_'; // user1에 대한 로그 2개 생성 $this->activityLogService->logAdmin($actionPrefix . 'view1', 'Viewed user 1', $user1); $this->activityLogService->logAdmin($actionPrefix . 'update1', 'Updated user 1', $user1); // user2에 대한 로그 1개 생성 $this->activityLogService->logAdmin($actionPrefix . 'view2', 'Viewed user 2', $user2); $user1Logs = $this->activityLogService->getLogsForModel($user1); // 이 테스트에서 생성한 로그만 필터링 $filteredLogs = collect($user1Logs->items())->filter(fn ($log) => str_starts_with($log->action, $actionPrefix)); $this->assertCount(2, $filteredLogs); foreach ($filteredLogs as $log) { $this->assertEquals($user1->id, $log->loggable_id); } } // ======================================================================== // getList() - 전체 로그 조회 테스트 // ======================================================================== /** * 로그 목록 조회가 페이지네이션을 반환하는지 확인 */ public function test_get_list_returns_paginated_logs(): void { $actionPrefix = $this->testPrefix . 'paginate_'; // 로그 5개 생성 for ($i = 0; $i < 5; $i++) { $this->activityLogService->logAdmin($actionPrefix . $i, "Action {$i}"); } // 전체 로그 중 이 테스트에서 생성한 것만 확인 $allLogs = ActivityLog::where('action', 'like', $actionPrefix . '%')->get(); $this->assertCount(5, $allLogs); } /** * 로그 유형 필터가 동작하는지 확인 */ public function test_get_list_filters_by_log_type(): void { $actionPrefix = $this->testPrefix . 'type_'; $this->activityLogService->logAdmin($actionPrefix . 'admin', 'Admin action'); $this->activityLogService->logUser($actionPrefix . 'user', 'User action'); $this->activityLogService->logSystem($actionPrefix . 'system', 'System action'); $adminLogs = $this->activityLogService->getList(['log_type' => ActivityLogType::Admin]); // 이 테스트에서 생성한 admin 로그만 필터링 $filteredLogs = collect($adminLogs->items())->filter(fn ($log) => str_starts_with($log->action, $actionPrefix)); $this->assertCount(1, $filteredLogs); $this->assertEquals(ActivityLogType::Admin, $filteredLogs->first()->log_type); } /** * 액션 필터가 동작하는지 확인 */ public function test_get_list_filters_by_action(): void { $action = $this->testPrefix . 'filter.update'; $this->activityLogService->logAdmin($this->testPrefix . 'filter.create', 'User created'); $this->activityLogService->logAdmin($action, 'User updated'); $this->activityLogService->logAdmin($this->testPrefix . 'filter.delete', 'User deleted'); $logs = $this->activityLogService->getList(['action' => $action]); $this->assertCount(1, $logs); $this->assertEquals($action, $logs->first()->action); } /** * 사용자 필터가 동작하는지 확인 */ public function test_get_list_filters_by_user_id(): void { $user1 = User::factory()->create(); $user2 = User::factory()->create(); $action1 = $this->testPrefix . 'user1_action'; $action2 = $this->testPrefix . 'user2_action'; $this->actingAs($user1); $this->activityLogService->logAdmin($action1, 'Action by user 1'); $this->actingAs($user2); $this->activityLogService->logAdmin($action2, 'Action by user 2'); $user1Logs = $this->activityLogService->getList(['user_id' => $user1->id, 'action' => $action1]); $this->assertCount(1, $user1Logs); $this->assertEquals($user1->id, $user1Logs->first()->user_id); } // ======================================================================== // Model Accessors - 모델 속성 테스트 // ======================================================================== /** * log_type_label 접근자가 동작하는지 확인 */ public function test_log_type_label_accessor(): void { $action = $this->testPrefix . 'label.action'; $this->activityLogService->logAdmin($action, 'Test'); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertNotEmpty($log->log_type_label); } /** * actor_name 접근자가 사용자 이름을 반환하는지 확인 */ public function test_actor_name_accessor_with_user(): void { $user = User::factory()->create(['name' => 'Test User']); $this->actingAs($user); $action = $this->testPrefix . 'actor.action'; $this->activityLogService->logAdmin($action, 'Test'); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertEquals('Test User', $log->actor_name); } /** * actor_name 접근자가 사용자 없을 때 시스템을 반환하는지 확인 */ public function test_actor_name_accessor_without_user(): void { $action = $this->testPrefix . 'system.actor'; $this->activityLogService->logSystem($action, 'Test'); $log = ActivityLog::where('action', $action)->first(); $this->assertNotNull($log); $this->assertNotEmpty($log->actor_name); } // ======================================================================== // 훅 통합 테스트 // ======================================================================== /** * 훅이 비활성화되면 로그가 저장되지 않는지 확인 */ public function test_log_not_saved_when_disabled(): void { config(['activity_log.enabled' => false]); $action = $this->testPrefix . 'disabled.action'; $this->activityLogService->logAdmin($action, 'Test'); $this->assertDatabaseMissing('activity_logs', [ 'action' => $action, ]); } /** * null 드라이버 사용 시 로그가 저장되지 않는지 확인 */ public function test_log_not_saved_with_null_driver(): void { // null 드라이버 설정 config(['activity_log.driver' => 'null']); // HookManager 및 Event, 매니저 초기화 HookManager::resetAll(); Event::forget('hook.core.activity.admin'); $this->app->forgetInstance(ActivityLogManager::class); // null 드라이버로 리스너 다시 등록 (static 배열에만) $listener = app(ActivityLogListener::class); $this->registerHookWithoutEvent('core.activity.admin', [$listener, 'handleAdmin']); $action = $this->testPrefix . 'null_driver.action'; $this->activityLogService->logAdmin($action, 'Test'); $this->assertDatabaseMissing('activity_logs', [ 'action' => $action, ]); } }