From 14366b5ed47b6cc78726e7037539bad9d97ac1f6 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 9 Dec 2025 16:48:42 +0100 Subject: [PATCH] Test for Logger Service --- tests/Service/LoggerServiceTest.php | 295 ++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 tests/Service/LoggerServiceTest.php diff --git a/tests/Service/LoggerServiceTest.php b/tests/Service/LoggerServiceTest.php new file mode 100644 index 0000000..519e6e1 --- /dev/null +++ b/tests/Service/LoggerServiceTest.php @@ -0,0 +1,295 @@ +userManagementLogger = $this->createMock(LoggerInterface::class); + $this->organizationManagementLogger = $this->createMock(LoggerInterface::class); + $this->accessControlLogger = $this->createMock(LoggerInterface::class); + $this->emailNotificationLogger = $this->createMock(LoggerInterface::class); + $this->adminActionsLogger = $this->createMock(LoggerInterface::class); + $this->securityLogger = $this->createMock(LoggerInterface::class); + $this->errorLogger = $this->createMock(LoggerInterface::class); + $this->awsLogger = $this->createMock(LoggerInterface::class); + $this->requestStack = $this->createMock(RequestStack::class); + + $this->service = new LoggerService( + $this->userManagementLogger, + $this->organizationManagementLogger, + $this->accessControlLogger, + $this->emailNotificationLogger, + $this->adminActionsLogger, + $this->securityLogger, + $this->errorLogger, + $this->awsLogger, + $this->requestStack + ); + } + + /** + * Helper to simulate a request with a specific IP. + */ + private function mockRequestIp(?string $ip): void + { + if ($ip === null) { + $this->requestStack->method('getCurrentRequest')->willReturn(null); + } else { + $request = $this->createMock(Request::class); + $request->method('getClientIp')->willReturn($ip); + $this->requestStack->method('getCurrentRequest')->willReturn($request); + } + } + + /** + * Helper assertion to check context contains basic fields + specific data. + */ + private function assertContextContains(array $expectedSubset): \PHPUnit\Framework\Constraint\Callback + { + return $this->callback(function (array $context) use ($expectedSubset) { + // Check Timestamp exists (we can't check exact value easily) + if (!isset($context['timestamp'])) { + return false; + } + + // Check IP exists + if (!isset($context['ip'])) { + return false; + } + + // Check specific keys + foreach ($expectedSubset as $key => $value) { + if (!array_key_exists($key, $context) || $context[$key] !== $value) { + return false; + } + } + + return true; + }); + } + + // ========================================== + // TESTS FOR USER MANAGEMENT LOGS + // ========================================== + + public function testLogUserCreated(): void + { + $this->mockRequestIp('127.0.0.1'); + + $this->userManagementLogger->expects($this->once()) + ->method('notice') + ->with( + "New user created: 10", + $this->assertContextContains([ + 'target_user_id' => 10, + 'acting_user_id' => 99, + 'ip' => '127.0.0.1' + ]) + ); + + $this->service->logUserCreated(10, 99); + } + + public function testLogCGUAcceptanceLogsToTwoChannels(): void + { + $this->mockRequestIp('192.168.1.1'); + $userId = 55; + + // Expect call on User Logger + $this->userManagementLogger->expects($this->once()) + ->method('info') + ->with("User accepted CGU", $this->assertContextContains(['user_id' => $userId])); + + // Expect call on Security Logger + $this->securityLogger->expects($this->once()) + ->method('info') + ->with("User accepted CGU", $this->assertContextContains(['user_id' => $userId])); + + $this->service->logCGUAcceptance($userId); + } + + // ========================================== + // TESTS FOR ORGANIZATION LOGS + // ========================================== + + public function testLogUserOrganizationLinkCreated(): void + { + $this->mockRequestIp('10.0.0.1'); + + $this->organizationManagementLogger->expects($this->once()) + ->method('notice') + ->with( + 'User-Organization link created', + $this->assertContextContains([ + 'target_user_id' => 1, + 'organization_id' => 2, + 'acting_user_id' => 3, + 'uo_id' => 4 + ]) + ); + + $this->service->logUserOrganizationLinkCreated(1, 2, 3, 4); + } + + // ========================================== + // TESTS FOR ERROR LOGS + // ========================================== + + public function testLogError(): void + { + $this->mockRequestIp('127.0.0.1'); + + $this->errorLogger->expects($this->once()) + ->method('error') + ->with( + 'Something failed', + $this->assertContextContains(['details' => 'foo']) + ); + + $this->service->logError('Something failed', ['details' => 'foo']); + } + + public function testLogEntityNotFoundHandlesGlobals(): void + { + $this->mockRequestIp('127.0.0.1'); + + // Simulate global server variable for REQUEST_URI + $_SERVER['REQUEST_URI'] = '/some/path'; + + $this->errorLogger->expects($this->once()) + ->method('error') + ->with( + 'Entity not found', + $this->assertContextContains([ + 'entity_type' => 'User', + 'id' => 123, + 'page_accessed' => '/some/path' + ]) + ); + + $this->service->logEntityNotFound('User', ['id' => 123], 1); + + // Cleanup global + unset($_SERVER['REQUEST_URI']); + } + + // ========================================== + // TESTS FOR SECURITY LOGS + // ========================================== + + public function testLogAccessDenied(): void + { + $this->mockRequestIp('10.10.10.10'); + + $this->securityLogger->expects($this->once()) + ->method('warning') + ->with( + 'Access denied', + $this->assertContextContains(['acting_user_id' => 5]) + ); + + $this->service->logAccessDenied(5); + } + + public function testLogTokenRevocation(): void + { + $this->mockRequestIp(null); // Test with NO REQUEST (e.g. CLI) + + $this->securityLogger->expects($this->once()) + ->method('warning') + ->with( + 'Token revoked', + $this->callback(function($context) { + return $context['ip'] === 'unknown' && $context['reason'] === 'expired'; + }) + ); + + $this->service->logTokenRevocation('Token revoked', ['reason' => 'expired']); + } + + // ========================================== + // TESTS FOR ADMIN ACTIONS + // ========================================== + + public function testLogSuperAdmin(): void + { + $this->mockRequestIp('1.2.3.4'); + + $this->adminActionsLogger->expects($this->once()) + ->method('notice') + ->with( + 'Global reset', + $this->assertContextContains([ + 'target_user_id' => 10, + 'acting_user_id' => 1, + 'organization_id' => null + ]) + ); + + $this->service->logSuperAdmin(10, 1, 'Global reset'); + } + + // ========================================== + // TESTS FOR AWS LOGS + // ========================================== + + public function testLogAWSAction(): void + { + $this->mockRequestIp('8.8.8.8'); + + $this->awsLogger->expects($this->once()) + ->method('info') + ->with( + 'AWS action performed: Upload', + $this->assertContextContains(['bucket' => 'my-bucket']) + ); + + $this->service->logAWSAction('Upload', ['bucket' => 'my-bucket']); + } + + // ========================================== + // TESTS FOR ACCESS CONTROL + // ========================================== + + public function testLogRoleEntityAssignment(): void + { + $this->mockRequestIp('127.0.0.1'); + + $this->accessControlLogger->expects($this->once()) + ->method('info') + ->with( + 'Role Assigned', + $this->assertContextContains([ + 'target_user_id' => 2, + 'organization_id' => 3, + 'role_id' => 4, + 'acting_user_id' => 1 + ]) + ); + + $this->service->logRoleEntityAssignment(2, 3, 4, 1, 'Role Assigned'); + } +} \ No newline at end of file