og-8.x-1.x-dev/tests/src/Unit/GroupTypeManagerTest.php
tests/src/Unit/GroupTypeManagerTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\og\Unit;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\og\Entity\OgRole;
use Drupal\og\Event\GroupCreationEvent;
use Drupal\og\Event\GroupCreationEventInterface;
use Drupal\og\Event\PermissionEventInterface;
use Drupal\og\GroupTypeManager;
use Drupal\og\GroupTypeManagerInterface;
use Drupal\og\OgGroupAudienceHelperInterface;
use Drupal\og\OgMembershipInterface;
use Drupal\og\OgMembershipTypeInterface;
use Drupal\og\OgRoleInterface;
use Drupal\og\OgRoleManagerInterface;
use Drupal\og\PermissionManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Tests the group manager.
*
* @group og
* @coversDefaultClass \Drupal\og\GroupTypeManager
*/
class GroupTypeManagerTest extends UnitTestCase {
use ProphecyTrait;
/**
* The config prophecy used in the test.
*/
protected Config|ObjectProphecy $config;
/**
* The config factory prophecy used in the test.
*/
protected ConfigFactoryInterface|ObjectProphecy $configFactory;
/**
* The entity type manager prophecy used in the test.
*/
protected EntityTypeManagerInterface|ObjectProphecy $entityTypeManager;
/**
* The entity storage prophecy used in the test.
*/
protected EntityStorageInterface|ObjectProphecy $entityStorage;
/**
* The OG role prophecy used in the test.
*/
protected OgRoleInterface|ObjectProphecy $ogRole;
/**
* The entity type bundle info prophecy used in the test.
*/
protected EntityTypeBundleInfoInterface|ObjectProphecy $entityTypeBundleInfo;
/**
* The event dispatcher prophecy used in the test.
*/
protected EventDispatcherInterface|ObjectProphecy $eventDispatcher;
/**
* The permission event prophecy used in the test.
*/
protected PermissionEventInterface|ObjectProphecy $permissionEvent;
/**
* The cache prophecy used in the test.
*/
protected CacheBackendInterface|ObjectProphecy $cache;
/**
* The OG permission manager prophecy used in the test.
*/
protected PermissionManagerInterface|ObjectProphecy $permissionManager;
/**
* The OG role manager prophecy used in the test.
*/
protected OgRoleManagerInterface|ObjectProphecy $ogRoleManager;
/**
* The route builder service used in the test.
*/
protected RouteBuilderInterface|ObjectProphecy $routeBuilder;
/**
* The OG group audience helper.
*/
protected OgGroupAudienceHelperInterface|ObjectProphecy $groupAudienceHelper;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->config = $this->prophesize(Config::class);
$this->configFactory = $this->prophesize(ConfigFactoryInterface::class);
$this->entityTypeBundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class);
$this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
$this->entityStorage = $this->prophesize(EntityStorageInterface::class);
$this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$this->ogRole = $this->prophesize(OgRole::class);
$this->ogRoleManager = $this->prophesize(OgRoleManagerInterface::class);
$this->permissionEvent = $this->prophesize(PermissionEventInterface::class);
$this->permissionManager = $this->prophesize(PermissionManagerInterface::class);
$this->cache = $this->prophesize(CacheBackendInterface::class);
$this->routeBuilder = $this->prophesize(RouteBuilderInterface::class);
$this->groupAudienceHelper = $this->prophesize(OgGroupAudienceHelperInterface::class);
}
/**
* Tests getting an instance of the group manager.
*
* @covers ::__construct
*/
public function testInstance(): void {
// Just creating an instance should be lightweight, no methods should be
// called.
$group_manager = $this->createGroupManager();
$this->assertInstanceOf(GroupTypeManagerInterface::class, $group_manager);
}
/**
* Tests checking if an entity is a group.
*
* @covers ::isGroup
*
* @dataProvider providerTestIsGroup
*/
public function testIsGroup(string $entity_type_id, string $bundle_id, bool $expected_result): void {
// It is expected that the group map will be retrieved from config.
$groups = ['test_entity' => ['a', 'b']];
$this->expectGroupMapRetrieval($groups);
$manager = $this->createGroupManager();
$this->assertSame($expected_result, $manager->isGroup($entity_type_id, $bundle_id));
}
/**
* Data provider for testIsGroup.
*
* @return array
* array with the entity type ID, bundle ID and boolean indicating the
* expected result.
*/
public static function providerTestIsGroup(): array {
return [
['test_entity', 'a', TRUE],
['test_entity', 'b', TRUE],
['test_entity', 'c', FALSE],
['test_entity_non_existent', 'a', FALSE],
['test_entity_non_existent', 'c', FALSE],
];
}
/**
* Tests getting all the groups IDs of an entity type.
*
* @covers ::getGroupBundleIdsByEntityType
*/
public function testGetGroupBundleIdsByEntityType(): void {
// It is expected that the group map will be retrieved from config.
$groups = ['test_entity' => ['a', 'b']];
$this->expectGroupMapRetrieval($groups);
$manager = $this->createGroupManager();
$this->assertSame($groups['test_entity'], $manager->getGroupBundleIdsByEntityType('test_entity'));
$this->assertSame([], $manager->getGroupBundleIdsByEntityType('test_entity_non_existent'));
}
/**
* Tests adding an existing group.
*
* @covers ::addGroup
*/
public function testAddGroupExisting(): void {
// It is expected that the group map will be retrieved from config.
$groups_before = ['test_entity' => ['a', 'b']];
$this->expectGroupMapRetrieval($groups_before);
$groups_after = ['test_entity' => ['a', 'b', 'c']];
$this->config->get('groups')
->willReturn($groups_after)
->shouldBeCalled();
$manager = $this->createGroupManager();
// Add to existing.
$this->expectException(\InvalidArgumentException::class);
$manager->addGroup('test_entity', 'c');
$this->assertSame(
['a', 'b', 'c'],
$manager->getGroupBundleIdsByEntityType('test_entity')
);
$this->assertTrue($manager->isGroup('test_entity', 'c'));
}
/**
* Tests adding a new group.
*
* @covers ::addGroup
*/
public function testAddGroupNew(): void {
$this->configFactory->getEditable('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
// It is expected that the group map will be retrieved from config.
$groups_before = [];
$this->expectGroupMapRetrieval($groups_before);
$groups_after = ['test_entity_new' => ['a']];
$config_prophecy = $this->config;
$this->config->set('groups', $groups_after)
->will(function () use ($groups_after, $config_prophecy): void {
$config_prophecy->get('groups')
->willReturn($groups_after)
->shouldBeCalled();
})
->shouldBeCalled();
$this->config->save()
->shouldBeCalled();
$manager = $this->createGroupManager();
$this->ogRoleManager->createPerBundleRoles('test_entity_new', 'a');
$this->eventDispatcher->dispatch(Argument::type(GroupCreationEvent::class), GroupCreationEventInterface::EVENT_NAME)
->shouldBeCalled();
// Add a new entity type.
$manager->addGroup('test_entity_new', 'a');
$this->assertSame(['a'], $manager->getGroupBundleIdsByEntityType('test_entity_new'));
$this->assertTrue($manager->isGroup('test_entity_new', 'a'));
}
/**
* Tests removing a group.
*
* @covers ::addGroup
*/
public function testRemoveGroup(): void {
$this->configFactory->getEditable('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
// It is expected that the group map will be retrieved from config.
$groups_before = ['test_entity' => ['a', 'b']];
$this->expectGroupMapRetrieval($groups_before);
$groups_after = ['test_entity' => ['a']];
$membership_types_before = [
'test_entity:a' => 'default',
'test_entity:b' => 'default',
];
$membership_types_after = [
'test_entity:a' => 'default',
];
$this->config->get('group_membership_types')
->willReturn($membership_types_before)
->shouldBeCalled();
$this->config->set('groups', $groups_after)
->shouldBeCalled();
$this->config->set('group_membership_types', $membership_types_after)
->shouldBeCalled();
$this->config->save()
->shouldBeCalled();
$this->config->get('groups')
->willReturn($groups_after)
->shouldBeCalled();
$manager = $this->createGroupManager();
// Add to existing.
$manager->removeGroup('test_entity', 'b');
$this->assertSame(['a'], $manager->getGroupBundleIdsByEntityType('test_entity'));
$this->assertFalse($manager->isGroup('test_entity', 'b'));
$this->assertTrue($manager->isGroup('test_entity', 'a'));
}
/**
* Creates a group manager instance with a mock config factory.
*
* @return \Drupal\og\GroupTypeManagerInterface
* Returns the group manager.
*/
protected function createGroupManager(): GroupTypeManagerInterface {
return new GroupTypeManager(
$this->configFactory->reveal(),
$this->entityTypeManager->reveal(),
$this->entityTypeBundleInfo->reveal(),
$this->eventDispatcher->reveal(),
$this->cache->reveal(),
$this->permissionManager->reveal(),
$this->ogRoleManager->reveal(),
$this->routeBuilder->reveal(),
$this->groupAudienceHelper->reveal()
);
}
/**
* Sets up an expectation that the group map will be retrieved from config.
*
* @param array $groups
* The expected group map that will be returned by the mocked config.
*/
protected function expectGroupMapRetrieval(array $groups = []): void {
$this->configFactory->get('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
$this->config->get('groups')
->willReturn($groups)
->shouldBeCalled();
}
/**
* Tests getting the membership type for a group bundle.
*
* @param array $membership_types
* The configured membership types.
* @param string $entity_type_id
* The entity type ID to test.
* @param string $bundle_id
* The bundle ID to test.
* @param string $expected_result
* The expected membership type.
*
* @covers ::getGroupDefaultMembershipType
* @dataProvider providerTestGetGroupDefaultMembershipType
*/
public function testGetGroupDefaultMembershipType(array $membership_types, string $entity_type_id, string $bundle_id, string $expected_result): void {
$this->configFactory->get('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
$this->config->get('group_membership_types')
->willReturn($membership_types)
->shouldBeCalled();
// Get the configured membership type for this bundle.
$configured_type = $membership_types["$entity_type_id:$bundle_id"] ?? NULL;
// If a custom type is configured, mock loading it.
if ($configured_type && $configured_type !== OgMembershipInterface::TYPE_DEFAULT) {
$storage = $this->prophesize(EntityStorageInterface::class);
$mock_entity = $this->prophesize(OgMembershipTypeInterface::class);
$storage->load($configured_type)
->willReturn($mock_entity->reveal())
->shouldBeCalled();
$this->entityTypeManager->getStorage('og_membership_type')
->willReturn($storage->reveal())
->shouldBeCalled();
}
$manager = $this->createGroupManager();
$this->assertSame($expected_result, $manager->getGroupDefaultMembershipType($entity_type_id, $bundle_id));
}
/**
* Data provider for testGetGroupDefaultMembershipType.
*
* @return array
* Test cases with membership types config, entity type, bundle, and
* expected result.
*/
public static function providerTestGetGroupDefaultMembershipType(): array {
return [
// No membership types configured - returns default.
[[], 'node', 'group_type_a', OgMembershipInterface::TYPE_DEFAULT],
// Custom membership type configured for this bundle.
[
['node:group_type_a' => 'premium_membership'],
'node',
'group_type_a',
'premium_membership',
],
// Different bundle has different type.
[
[
'node:group_type_a' => 'premium_membership',
'node:group_type_b' => 'basic_membership',
],
'node',
'group_type_b',
'basic_membership',
],
// Non-configured bundle returns default.
[
['node:group_type_a' => 'premium_membership'],
'node',
'group_type_b',
OgMembershipInterface::TYPE_DEFAULT,
],
];
}
/**
* Tests setting a custom membership type for a group bundle.
*
* @covers ::setGroupDefaultMembershipType
* @covers ::getGroupDefaultMembershipType
*/
public function testSetGroupDefaultMembershipType(): void {
$this->configFactory->getEditable('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
$this->configFactory->get('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
// After setting a custom type.
$membership_types_after = ['node:group_type_a' => 'premium_membership'];
// Return empty array on first two calls, then the updated array.
// Call 1: First getGroupDefaultMembershipType() - returns []
// Call 2: setGroupDefaultMembershipType() reads current value - returns []
// Call 3: Second getGroupDefaultMembershipType() - returns updated array.
$this->config->get('group_membership_types')
->willReturn([], [], $membership_types_after);
$this->config->set('group_membership_types', $membership_types_after)
->shouldBeCalled();
$this->config->save()
->shouldBeCalled();
// Mock entity storage for the second getGroupDefaultMembershipType call
// which will validate the 'premium_membership' entity exists.
$storage = $this->prophesize(EntityStorageInterface::class);
$mock_entity = $this->prophesize(OgMembershipTypeInterface::class);
$storage->load('premium_membership')
->willReturn($mock_entity->reveal())
->shouldBeCalled();
$this->entityTypeManager->getStorage('og_membership_type')
->willReturn($storage->reveal())
->shouldBeCalled();
$manager = $this->createGroupManager();
// Verify default is returned initially.
$this->assertSame(
OgMembershipInterface::TYPE_DEFAULT,
$manager->getGroupDefaultMembershipType('node', 'group_type_a')
);
// Set a custom membership type.
$manager->setGroupDefaultMembershipType('node', 'group_type_a', 'premium_membership');
// Verify the custom type is now returned.
$this->assertSame(
'premium_membership',
$manager->getGroupDefaultMembershipType('node', 'group_type_a')
);
}
/**
* Tests that a deleted membership type falls back to default.
*
* This tests the scenario where a membership type is configured for a group
* but the membership type entity has been deleted. In this case, the system
* should gracefully fall back to the default membership type.
*
* @covers ::getGroupDefaultMembershipType
*/
public function testGetGroupDefaultMembershipTypeDeletedType(): void {
$this->configFactory->get('og.settings')
->willReturn($this->config->reveal())
->shouldBeCalled();
// Configuration references a deleted membership type.
$this->config->get('group_membership_types')
->willReturn(['node:group_type_a' => 'deleted_membership'])
->shouldBeCalled();
// Mock the entity type manager and storage.
$storage = $this->prophesize(EntityStorageInterface::class);
// The membership type entity doesn't exist (returns NULL).
$storage->load('deleted_membership')
->willReturn(NULL)
->shouldBeCalled();
$this->entityTypeManager->getStorage('og_membership_type')
->willReturn($storage->reveal())
->shouldBeCalled();
$manager = $this->createGroupManager();
// Should fall back to default when the configured type doesn't exist.
$this->assertSame(
OgMembershipInterface::TYPE_DEFAULT,
$manager->getGroupDefaultMembershipType('node', 'group_type_a')
);
}
}
