385 lines
14 KiB
PHP
385 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Service;
|
|
|
|
use App\Entity\Apps;
|
|
use App\Entity\Organizations;
|
|
use App\Entity\Roles;
|
|
use App\Entity\User;
|
|
use App\Entity\UserOrganizatonApp;
|
|
use App\Entity\UsersOrganizations;
|
|
use App\Repository\UsersOrganizationsRepository;
|
|
use App\Service\AwsService;
|
|
use App\Service\LoggerService;
|
|
use App\Service\NotificationService;
|
|
use App\Service\OrganizationsService;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\EntityRepository;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
|
|
class OrganizationsServiceTest extends TestCase
|
|
{
|
|
private OrganizationsService $service;
|
|
|
|
// Mocks
|
|
private MockObject|AwsService $awsService;
|
|
private MockObject|EntityManagerInterface $entityManager;
|
|
private MockObject|UsersOrganizationsRepository $uoRepository;
|
|
private MockObject|NotificationService $notificationService;
|
|
private MockObject|LoggerInterface $emailNotificationLogger;
|
|
private MockObject|LoggerService $loggerService;
|
|
|
|
private string $logoDirectory = '/tmp/logos';
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->awsService = $this->createMock(AwsService::class);
|
|
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
|
$this->uoRepository = $this->createMock(UsersOrganizationsRepository::class);
|
|
$this->notificationService = $this->createMock(NotificationService::class);
|
|
$this->emailNotificationLogger = $this->createMock(LoggerInterface::class);
|
|
$this->loggerService = $this->createMock(LoggerService::class);
|
|
|
|
// Set the ENV variable used in the service
|
|
$_ENV['S3_PORTAL_BUCKET'] = 'test-bucket';
|
|
|
|
$this->service = new OrganizationsService(
|
|
$this->logoDirectory,
|
|
$this->awsService,
|
|
$this->entityManager,
|
|
$this->uoRepository,
|
|
$this->notificationService,
|
|
$this->emailNotificationLogger,
|
|
$this->loggerService
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Helper to set private ID property via Reflection
|
|
*/
|
|
private function setEntityId(object $entity, int $id): void
|
|
{
|
|
$reflection = new \ReflectionClass($entity);
|
|
if ($reflection->hasProperty('id')) {
|
|
$property = $reflection->getProperty('id');
|
|
// $property->setAccessible(true); // PHP < 8.1
|
|
$property->setValue($entity, $id);
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// TEST: handleLogo
|
|
// ==========================================
|
|
|
|
public function testHandleLogoSuccess(): void
|
|
{
|
|
$org = new Organizations();
|
|
$this->setEntityId($org, 1);
|
|
$org->setName('MyOrg');
|
|
|
|
$file = $this->createMock(UploadedFile::class);
|
|
$file->method('guessExtension')->willReturn('png');
|
|
|
|
|
|
$this->service->handleLogo($org, $file);
|
|
|
|
// Assert URL is set on entity
|
|
$this->assertStringContainsString('uploads/organization_logos/MyOrg_', $org->getLogoUrl());
|
|
}
|
|
|
|
public function testHandleLogoThrowsException(): void
|
|
{
|
|
// 1. Setup the Entity
|
|
$org = new Organizations();
|
|
$this->setEntityId($org, 1); // Assuming you have a helper for reflection ID setting
|
|
$org->setName('MyOrg');
|
|
|
|
// 2. Setup the File Mock
|
|
$file = $this->createMock(UploadedFile::class);
|
|
$file->method('guessExtension')->willReturn('png');
|
|
|
|
// --- CRITICAL PART ---
|
|
// We tell the mock: "When move() is called, crash with an exception."
|
|
// We use a generic Exception here because your try/catch block catches \Exception
|
|
$file->method('move')
|
|
->willThrowException(new \Exception('Disk full or permission denied'));
|
|
|
|
// 3. Expect the Logger Call
|
|
$this->loggerService->expects($this->once())
|
|
->method('logError')
|
|
->with(
|
|
// This string MUST match the first argument in your actual code
|
|
'File upload failed',
|
|
// We use a callback to validate the context array contains the right ID
|
|
$this->callback(function($context) use ($org) {
|
|
return $context['target_organization_id'] === $org->getId()
|
|
&& $context['message'] === 'Disk full or permission denied';
|
|
})
|
|
);
|
|
|
|
// 4. Expect the final exception re-thrown by your service
|
|
$this->expectException(FileException::class);
|
|
$this->expectExceptionMessage('File upload failed.');
|
|
|
|
// 5. Run the method
|
|
$this->service->handleLogo($org, $file);
|
|
}
|
|
|
|
// ==========================================
|
|
// TEST: appsAccess
|
|
// ==========================================
|
|
|
|
public function testAppsAccess(): void
|
|
{
|
|
$app1 = new Apps(); $this->setEntityId($app1, 10);
|
|
$app2 = new Apps(); $this->setEntityId($app2, 20);
|
|
$app3 = new Apps(); $this->setEntityId($app3, 30);
|
|
|
|
$allApps = [$app1, $app2, $app3];
|
|
$orgApps = [$app2]; // Org only has access to App 2
|
|
|
|
$result = $this->service->appsAccess($allApps, $orgApps);
|
|
|
|
$this->assertCount(3, $result);
|
|
|
|
// App 1 -> False
|
|
$this->assertSame($app1, $result[0]['entity']);
|
|
$this->assertFalse($result[0]['hasAccess']);
|
|
|
|
// App 2 -> True
|
|
$this->assertSame($app2, $result[1]['entity']);
|
|
$this->assertTrue($result[1]['hasAccess']);
|
|
|
|
// App 3 -> False
|
|
$this->assertSame($app3, $result[2]['entity']);
|
|
$this->assertFalse($result[2]['hasAccess']);
|
|
}
|
|
|
|
// ==========================================
|
|
// TEST: notifyOrganizationAdmins
|
|
// ==========================================
|
|
|
|
public function testNotifyOrganizationAdminsUserAccepted(): void
|
|
{
|
|
// 1. Setup Data
|
|
$targetUser = new User(); $this->setEntityId($targetUser, 100);
|
|
|
|
$adminUser = new User(); $this->setEntityId($adminUser, 999);
|
|
|
|
$org = new Organizations(); $this->setEntityId($org, 50);
|
|
|
|
$data = ['user' => $targetUser, 'organization' => $org];
|
|
|
|
// 2. Setup Admin Link (The user who IS admin)
|
|
$adminUO = new UsersOrganizations();
|
|
$this->setEntityId($adminUO, 555);
|
|
$adminUO->setUsers($adminUser);
|
|
$adminUO->setOrganization($org);
|
|
|
|
// 3. Setup Role Logic
|
|
$adminRole = new Roles(); $this->setEntityId($adminRole, 1);
|
|
$adminRole->setName('ADMIN');
|
|
|
|
// 4. Setup UOA Logic (Proof that user is Admin of an App)
|
|
$uoa = new UserOrganizatonApp();
|
|
$this->setEntityId($uoa, 777);
|
|
$uoa->setUserOrganization($adminUO);
|
|
$uoa->setRole($adminRole);
|
|
$uoa->setIsActive(true);
|
|
|
|
// 5. Mocks
|
|
// Mock Roles Repo
|
|
$rolesRepo = $this->createMock(EntityRepository::class);
|
|
$rolesRepo->method('findOneBy')->with(['name' => 'ADMIN'])->willReturn($adminRole);
|
|
|
|
// Mock UO Repo (Find potential admins in org)
|
|
$this->uoRepository->expects($this->once())
|
|
->method('findBy')
|
|
->with(['organization' => $org, 'isActive' => true])
|
|
->willReturn([$adminUO]);
|
|
|
|
// Mock UOA Repo (Check if they have ADMIN role)
|
|
$uoaRepo = $this->createMock(EntityRepository::class);
|
|
$uoaRepo->method('findOneBy')->willReturn($uoa);
|
|
|
|
$this->entityManager->method('getRepository')->willReturnMap([
|
|
[Roles::class, $rolesRepo],
|
|
[UserOrganizatonApp::class, $uoaRepo],
|
|
]);
|
|
|
|
// 6. Expectations
|
|
$this->notificationService->expects($this->once())
|
|
->method('notifyUserAcceptedInvite')
|
|
->with($adminUser, $targetUser, $org);
|
|
|
|
$this->loggerService->expects($this->once())
|
|
->method('logAdminNotified')
|
|
->with([
|
|
'admin_user_id' => 999,
|
|
'target_user_id' => 100,
|
|
'organization_id' => 50,
|
|
'case' => 'USER_ACCEPTED'
|
|
]);
|
|
|
|
// 7. Run
|
|
$result = $this->service->notifyOrganizationAdmins($data, 'USER_ACCEPTED');
|
|
|
|
}
|
|
|
|
/**
|
|
* This test ensures that if the admin is the SAME person as the target user,
|
|
* they do not get notified (Skip Self Check).
|
|
*/
|
|
public function testNotifyOrganizationAdminsSkipsSelf(): void
|
|
{
|
|
$user = new User(); $this->setEntityId($user, 100);
|
|
$org = new Organizations(); $this->setEntityId($org, 50);
|
|
|
|
// Admin IS the user
|
|
$adminUO = new UsersOrganizations();
|
|
$adminUO->setUsers($user);
|
|
|
|
$roleAdmin = new Roles();
|
|
|
|
$uoa = new UserOrganizatonApp(); // active admin link
|
|
|
|
// Mocks setup
|
|
$rolesRepo = $this->createMock(EntityRepository::class);
|
|
$rolesRepo->method('findOneBy')->willReturn($roleAdmin);
|
|
|
|
$this->uoRepository->method('findBy')->willReturn([$adminUO]);
|
|
|
|
$uoaRepo = $this->createMock(EntityRepository::class);
|
|
$uoaRepo->method('findOneBy')->willReturn($uoa);
|
|
|
|
$this->entityManager->method('getRepository')->willReturnMap([
|
|
[Roles::class, $rolesRepo],
|
|
[UserOrganizatonApp::class, $uoaRepo],
|
|
]);
|
|
|
|
// Expectations: Notification service should NEVER be called
|
|
$this->notificationService->expects($this->never())->method('notifyUserAcceptedInvite');
|
|
$this->loggerService->expects($this->never())->method('logAdminNotified');
|
|
|
|
$this->service->notifyOrganizationAdmins(['user' => $user, 'organization' => $org], 'USER_ACCEPTED');
|
|
}
|
|
|
|
public function testNotifyOrganizationAdminsSkipsNonAdmins(): void
|
|
{
|
|
// 1. Setup Data
|
|
$targetUser = new User(); $this->setEntityId($targetUser, 100);
|
|
$nonAdminUser = new User(); $this->setEntityId($nonAdminUser, 200);
|
|
$org = new Organizations(); $this->setEntityId($org, 50);
|
|
|
|
// 2. Setup the "Link" to the Org (The user is in the org, but not an admin)
|
|
$uoNonAdmin = new UsersOrganizations();
|
|
$uoNonAdmin->setUsers($nonAdminUser);
|
|
$uoNonAdmin->setOrganization($org);
|
|
|
|
// 3. Mock Repos
|
|
$rolesRepo = $this->createMock(EntityRepository::class);
|
|
// It doesn't matter what roles repo returns, the check fails later at UOA
|
|
|
|
// The UO Repo finds the user as a member of the org
|
|
$this->uoRepository->method('findBy')->willReturn([$uoNonAdmin]);
|
|
|
|
// CRITICAL: The UOA Repo returns NULL (No Admin record found)
|
|
$uoaRepo = $this->createMock(EntityRepository::class);
|
|
$uoaRepo->method('findOneBy')->willReturn(null);
|
|
|
|
$this->entityManager->method('getRepository')->willReturnMap([
|
|
[Roles::class, $rolesRepo],
|
|
[UserOrganizatonApp::class, $uoaRepo],
|
|
]);
|
|
|
|
// 4. Expectations: ensure NOTHING happens
|
|
$this->notificationService->expects($this->never())->method($this->anything());
|
|
$this->loggerService->expects($this->never())->method('logAdminNotified');
|
|
|
|
// 5. Run
|
|
$this->service->notifyOrganizationAdmins(
|
|
['user' => $targetUser, 'organization' => $org],
|
|
'USER_ACCEPTED'
|
|
);
|
|
}
|
|
|
|
#[DataProvider('notificationCasesProvider')]
|
|
public function testNotifyOrganizationAdminsHandlesAllCases(string $caseType, string $expectedMethod): void
|
|
{
|
|
// 1. Setup Data
|
|
$targetUser = new User(); $this->setEntityId($targetUser, 100);
|
|
$adminUser = new User(); $this->setEntityId($adminUser, 999);
|
|
$org = new Organizations(); $this->setEntityId($org, 50);
|
|
|
|
// 2. Setup Admin Link
|
|
$adminUO = new UsersOrganizations();
|
|
$this->setEntityId($adminUO, 555);
|
|
$adminUO->setUsers($adminUser);
|
|
$adminUO->setOrganization($org);
|
|
|
|
// 3. Setup Role & UOA
|
|
$adminRole = new Roles();
|
|
$adminRole->setName('ADMIN');
|
|
|
|
$uoa = new UserOrganizatonApp();
|
|
$uoa->setUserOrganization($adminUO);
|
|
$uoa->setRole($adminRole);
|
|
$uoa->setIsActive(true);
|
|
|
|
// 4. Mocks
|
|
$rolesRepo = $this->createMock(EntityRepository::class);
|
|
$rolesRepo->method('findOneBy')->willReturn($adminRole);
|
|
|
|
$this->uoRepository->method('findBy')->willReturn([$adminUO]);
|
|
|
|
$uoaRepo = $this->createMock(EntityRepository::class);
|
|
$uoaRepo->method('findOneBy')->willReturn($uoa);
|
|
|
|
$this->entityManager->method('getRepository')->willReturnMap([
|
|
[Roles::class, $rolesRepo],
|
|
[UserOrganizatonApp::class, $uoaRepo],
|
|
]);
|
|
|
|
// 5. Dynamic Expectations
|
|
// We expect the *variable* method name passed by the provider
|
|
$this->notificationService->expects($this->once())
|
|
->method($expectedMethod)
|
|
->with($adminUser, $targetUser, $org);
|
|
|
|
// We expect the logger to receive the specific $caseType
|
|
$this->loggerService->expects($this->once())
|
|
->method('logAdminNotified')
|
|
->with([
|
|
'admin_user_id' => 999,
|
|
'target_user_id' => 100,
|
|
'organization_id' => 50,
|
|
'case' => $caseType // <--- Verified here
|
|
]);
|
|
|
|
// 6. Run
|
|
$this->service->notifyOrganizationAdmins(
|
|
['user' => $targetUser, 'organization' => $org],
|
|
$caseType
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Provides the data for the test above.
|
|
* Format: [ 'Case String', 'Expected Service Method Name' ]
|
|
*/
|
|
public static function notificationCasesProvider(): array
|
|
{
|
|
return [
|
|
'Invited Case' => ['USER_INVITED', 'notifyUserInvited'],
|
|
'Deactivated Case' => ['USER_DEACTIVATED', 'notifyUserDeactivated'],
|
|
'Deleted Case' => ['USER_DELETED', 'notifyUserDeleted'],
|
|
'Activated Case' => ['USER_ACTIVATED', 'notifyUserActivated'],
|
|
|
|
];
|
|
}
|
|
} |