messageBus = $this->createMock(MessageBusInterface::class); $this->service = new NotificationService($this->messageBus); } /** * Helper to inject IDs into entities without setters. * Prevents "getId() on null" errors since we are not using a real DB. */ private function setEntityId(object $entity, int $id): void { $reflection = new \ReflectionClass($entity); if ($reflection->hasProperty('id')) { $property = $reflection->getProperty('id'); // $property->setAccessible(true); // Uncomment if using PHP < 8.1 $property->setValue($entity, $id); } } public function testNotifyUserInvited(): void { // 1. Setup Data $recipient = new User(); $this->setEntityId($recipient, 1); $invitedUser = new User(); $this->setEntityId($invitedUser, 2); $invitedUser->setName('John'); $invitedUser->setSurname('Doe'); $invitedUser->setEmail('john@doe.com'); $org = new Organizations(); $this->setEntityId($org, 100); $org->setName('Acme Corp'); // 2. Expect Dispatch $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { // Verify the content of the dispatched message object return $message->getType() === NotificationService::TYPE_USER_INVITED && $message->getTitle() === 'Invitation envoyée' && str_contains($message->getMessage(), 'John Doe a été invité à rejoindre Acme Corp') && $message->getData()['userEmail'] === 'john@doe.com' && $message->getOrganizationId() === 100; })) ->willReturn(new Envelope(new \stdClass())); // Dispatch returns an Envelope // 3. Run $this->service->notifyUserInvited($recipient, $invitedUser, $org); } public function testNotifyUserAcceptedInvite(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $acceptedUser = new User(); $this->setEntityId($acceptedUser, 2); $acceptedUser->setName('Jane'); $acceptedUser->setSurname('Smith'); $acceptedUser->setEmail('jane@smith.com'); $org = new Organizations(); $this->setEntityId($org, 200); $org->setName('TechGlobal'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === NotificationService::TYPE_USER_ACCEPTED && $message->getTitle() === 'Invitation acceptée' && str_contains($message->getMessage(), 'Jane Smith a accepté l\'invitation à TechGlobal') && $message->getData()['organizationName'] === 'TechGlobal'; })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyUserAcceptedInvite($recipient, $acceptedUser, $org); } public function testNotifyUserDeactivated(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $removedUser = new User(); $this->setEntityId($removedUser, 3); $removedUser->setName('Bob'); $removedUser->setSurname('Builder'); $org = new Organizations(); $this->setEntityId($org, 300); $org->setName('BuildIt'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === NotificationService::TYPE_USER_DEACTIVATED && $message->getTitle() === 'Membre retiré' && str_contains($message->getMessage(), 'Bob Builder a été désactivé de BuildIt') && $message->getData()['userId'] === 3; })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyUserDeactivated($recipient, $removedUser, $org); } public function testNotifyUserActivated(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $activatedUser = new User(); $this->setEntityId($activatedUser, 4); $activatedUser->setName('Alice'); $activatedUser->setSurname('Wonder'); $org = new Organizations(); $this->setEntityId($org, 400); $org->setName('Wonderland'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === 'user_activated' && $message->getTitle() === 'Membre réactivé' && str_contains($message->getMessage(), 'Alice Wonder a été réactivé dans Wonderland'); })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyUserActivated($recipient, $activatedUser, $org); } public function testNotifyOrganizationUpdate(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $org = new Organizations(); $this->setEntityId($org, 500); $org->setName('OrgUpdate'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === NotificationService::TYPE_ORG_UPDATE && $message->getTitle() === 'Organisation mise à jour' && str_contains($message->getMessage(), 'L\'organisation OrgUpdate a été Renamed') && $message->getData()['action'] === 'Renamed'; })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyOrganizationUpdate($recipient, $org, 'Renamed'); } public function testNotifyAppAccessChangedGranted(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $org = new Organizations(); $this->setEntityId($org, 600); $org->setName('AppCorp'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === NotificationService::TYPE_APP_ACCESS && str_contains($message->getMessage(), 'L\'accès à Portal a été autorisé pour AppCorp') && $message->getData()['granted'] === true; })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyAppAccessChanged($recipient, $org, 'Portal', true); } public function testNotifyAppAccessChangedRevoked(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $org = new Organizations(); $this->setEntityId($org, 600); $org->setName('AppCorp'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getData()['granted'] === false && str_contains($message->getMessage(), 'retiré'); })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyAppAccessChanged($recipient, $org, 'Portal', false); } public function testNotifyRoleChanged(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $targetUser = new User(); $this->setEntityId($targetUser, 5); $targetUser->setName('Tom'); $targetUser->setSurname('Role'); $org = new Organizations(); $this->setEntityId($org, 700); $org->setName('RoleOrg'); $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === NotificationService::TYPE_ROLE_CHANGED && $message->getTitle() === 'Rôle modifié' && str_contains($message->getMessage(), 'Tom Role a été changé en ADMIN') && $message->getData()['newRole'] === 'ADMIN'; })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyRoleChanged($recipient, $targetUser, $org, 'ADMIN'); } public function testNotifyUserDeleted(): void { $recipient = new User(); $this->setEntityId($recipient, 1); $deletedUser = new User(); $this->setEntityId($deletedUser, 99); $deletedUser->setName('Del'); $deletedUser->setSurname('User'); $deletedUser->setEmail('del@test.com'); // Test without organization (null) $this->messageBus->expects($this->once()) ->method('dispatch') ->with($this->callback(function (NotificationMessage $message) { return $message->getType() === NotificationService::TYPE_USER_REMOVED && $message->getTitle() === 'Utilisateur supprimé' && $message->getOrganizationId() === null && $message->getData()['userEmail'] === 'del@test.com'; })) ->willReturn(new Envelope(new \stdClass())); $this->service->notifyUserDeleted($recipient, $deletedUser, null); } }