Easy_solution/tests/Service/LoggerServiceTest.php

295 lines
9.4 KiB
PHP

<?php
namespace App\Tests\Service;
use App\Service\LoggerService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class LoggerServiceTest extends TestCase
{
private LoggerService $service;
// Mocks for all the channels
private MockObject|LoggerInterface $userManagementLogger;
private MockObject|LoggerInterface $organizationManagementLogger;
private MockObject|LoggerInterface $accessControlLogger;
private MockObject|LoggerInterface $emailNotificationLogger;
private MockObject|LoggerInterface $adminActionsLogger;
private MockObject|LoggerInterface $securityLogger;
private MockObject|LoggerInterface $errorLogger;
private MockObject|LoggerInterface $awsLogger;
private MockObject|RequestStack $requestStack;
protected function setUp(): void
{
// Create mocks for all dependencies
$this->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');
}
}