og-8.x-1.x-dev/tests/src/Functional/GroupTabTest.php
tests/src/Functional/GroupTabTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\og\Functional;
use Drupal\user\UserInterface;
use Drupal\node\NodeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\og\Traits\OgMembershipCreationTrait;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\og\Entity\OgRole;
use Drupal\og\Og;
use Drupal\og\OgMembershipInterface;
use Drupal\og\OgRoleInterface;
use Drupal\user\Entity\User;
/**
* Tests the "Group" tab.
*
* @group og
*/
class GroupTabTest extends BrowserTestBase {
use OgMembershipCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'og', 'views', 'entity_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Test entity group.
*/
protected NodeInterface $groupNode;
/**
* Test non-group entity.
*/
protected NodeInterface $nonGroup;
/**
* A group bundle name.
*/
protected string $bundle1;
/**
* A group bundle name.
*/
protected string $bundle2;
/**
* The group author user.
*/
protected UserInterface $authorUser;
/**
* The group administrator user.
*/
protected UserInterface $groupAdminUser;
/**
* A group user with 'manage members' permission.
*/
protected UserInterface $groupManagerUser;
/**
* The node group membership for another user.
*/
protected OgMembershipInterface $anotherNodeMembership;
/**
* A group that is of type entity_test.
*/
protected EntityTest $groupTestEntity;
/**
* The entity_test group membership for another user.
*/
protected OgMembershipInterface $anotherTestEntityMembership;
/**
* A administrative user.
*/
protected UserInterface $user1;
/**
* A non-author user.
*/
protected UserInterface $user2;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create bundles.
$this->bundle1 = mb_strtolower($this->randomMachineName());
$this->bundle2 = mb_strtolower($this->randomMachineName());
// Create node types.
$node_type1 = NodeType::create([
'type' => $this->bundle1,
'name' => $this->bundle1,
]);
$node_type1->save();
$node_type2 = NodeType::create([
'type' => $this->bundle2,
'name' => $this->bundle2,
]);
$node_type2->save();
// Define the first bundle as group.
Og::groupTypeManager()->addGroup('node', $this->bundle1);
// Define the entity_test entity as a group.
Og::groupTypeManager()->addGroup('entity_test', 'entity_test');
// Add a role with manager members permission for each group type.
$values = [
'name' => 'manager',
'label' => 'Member manager',
'permissions' => ['manage members'],
];
$node_role = OgRole::create($values);
$node_role->setGroupType('node');
$node_role->setGroupBundle($this->bundle1);
$node_role->save();
$this->assertSame(['manage members'], $node_role->getPermissions());
$entity_test_role = OgRole::create($values);
$entity_test_role->setGroupType('entity_test');
$entity_test_role->setGroupBundle('entity_test');
$entity_test_role->save();
// Create node author user. The "z" prevents matching user 1 who gets
// the name "admin" in the test.
$this->authorUser = $this->createUser([], 'author-adminz');
$this->groupManagerUser = $this->createUser([], 'group-members-manager');
// Saving the group node creates a membership for the author.
$this->groupNode = Node::create([
'type' => $this->bundle1,
'title' => $this->randomString(),
'uid' => $this->authorUser->id(),
]);
$this->groupNode->save();
$this->groupAdminUser = $this->createUser([], 'adminz-group');
$this->createOgMembership($this->groupNode, $this->groupAdminUser, [OgRoleInterface::ADMINISTRATOR]);
$this->createOgMembership($this->groupNode, $this->groupManagerUser, [$node_role->getName()]);
$another_user = $this->createUser([], 'another');
$this->anotherNodeMembership = $this->createOgMembership($this->groupNode, $another_user);
$this->groupTestEntity = EntityTest::create([
'name' => $this->randomString(),
'user_id' => $this->authorUser->id(),
]);
$this->groupTestEntity->save();
$this->createOgMembership($this->groupTestEntity, $this->groupAdminUser, [OgRoleInterface::ADMINISTRATOR]);
$this->createOgMembership($this->groupTestEntity, $this->groupManagerUser, [$entity_test_role->getName()]);
$this->anotherTestEntityMembership = $this->createOgMembership($this->groupTestEntity, $another_user);
$this->nonGroup = Node::create([
'type' => $this->bundle2,
'title' => $this->randomString(),
'uid' => $this->authorUser->id(),
]);
$this->nonGroup->save();
$this->user1 = $this->drupalCreateUser(['administer organic groups'], 'adminz-all-groups');
$this->user2 = $this->drupalCreateUser([], 'somebody');
}
/**
* Tests access to the group tab and pages.
*/
public function testMembershipAdd(): void {
$loop = 0;
$random_name = $this->randomMachineName();
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manger */
$entity_type_manger = $this->container->get('entity_type.manager');
/** @var \Drupal\og\MembershipManager $membership_manager */
$membership_manager = $this->container->get('og.membership_manager');
foreach ($this->membershipAddScenarios() as $scenario) {
[$account] = $scenario;
$this->drupalLogin($account);
$group_data = [
[$this->groupNode, $this->anotherNodeMembership],
[$this->groupTestEntity, $this->anotherTestEntityMembership],
];
foreach ($group_data as $data) {
[$group, $membership] = $data;
/** @var \Drupal\og\OgMembershipInterface $membership */
$exiting_member = $membership->getOwner();
$this->drupalGet($this->groupMemberAddFormUrl($group));
$value = $exiting_member->getDisplayName() . ' (' . $exiting_member->id() . ')';
$this->submitForm(['Username' => $value], 'Save');
$this->assertSession()->pageTextMatches('/The user .+ is already a member in this group/');
$match = 'adminz';
// Test entity query match.
$found = $entity_type_manger->getStorage('user')
->getQuery()
->accessCheck()
->condition('uid', 0, '<>')
->condition('name', $match, 'CONTAINS')
->execute();
$this->assertCount(3, $found, print_r($found, TRUE));
// Two of the three possible matches are already members.
$this->assertAutoCompleteMatches($group, $match, 1);
// Verify that we can add a new user after matching.
$new_user = $this->createUser([], $random_name . $loop++);
$json_data = $this->assertAutoCompleteMatches($group, $new_user->getDisplayName(), 1);
$this->drupalGet($this->groupMemberAddFormUrl($group));
$this->submitForm(['Username' => $json_data[0]['value']], 'Save');
$this->assertSession()->pageTextMatches('/Added .+ to .+/');
$this->assertTrue($membership_manager->isMember($group, $new_user->id()));
$new_membership = $membership_manager->getMembership($group, $new_user->id());
$this->drupalGet($new_membership->toUrl('edit-form'));
$this->submitForm(['state' => OgMembershipInterface::STATE_BLOCKED], 'Save');
$new_membership = $entity_type_manger
->getStorage('og_membership')
->loadUnchanged($new_membership->id());
$this->assertSame(OgMembershipInterface::STATE_BLOCKED, $new_membership->getState());
$this->drupalGet($new_membership->toUrl('delete-form'));
$this->submitForm([], 'Delete');
$new_membership = $entity_type_manger
->getStorage('og_membership')
->loadUnchanged($new_membership->id());
$this->assertNull($new_membership);
}
}
}
/**
* Provide data for testMembershipAdd.
*
* @return array[]
* Array of test scenarios.
*/
protected function membershipAddScenarios(): array {
return [
[$this->authorUser],
[$this->groupAdminUser],
[$this->user1],
];
}
/**
* Test adding a group-blocked user and a site-wide blocked user.
*/
public function testBlockedUserAdd(): void {
$this->drupalLogin($this->groupAdminUser);
$blocked_user = $this->drupalCreateUser([], 'bbblocked', FALSE, ['status' => 0]);
/** @var \Drupal\og\MembershipManager $membership_manager */
$membership_manager = $this->container->get('og.membership_manager');
$group_data = [
[$this->groupNode, $this->anotherNodeMembership],
[$this->groupTestEntity, $this->anotherTestEntityMembership],
];
foreach ($group_data as $data) {
[$group, $membership] = $data;
/** @var \Drupal\og\OgMembershipInterface $membership */
$exiting_member = $membership->getOwner();
$membership->setState(OgMembershipInterface::STATE_BLOCKED);
$membership->save();
// Directly test autocomplete endpoint.
$this->assertAutoCompleteMatches($group, $exiting_member->getDisplayName(), 0);
$this->drupalGet($this->groupMemberAddFormUrl($group));
$value = $exiting_member->getDisplayName() . ' (' . $exiting_member->id() . ')';
$this->submitForm(['Username' => $value], 'Save');
$this->assertSession()->pageTextMatches('/The user .+ is already a member in this group/');
$this->drupalGet($this->groupMemberAddFormUrl($group));
// API validate too.
$new_membership = $membership_manager->createMembership($group, $exiting_member);
$errors = $new_membership->validate();
$this->assertTrue(count($errors) > 0);
// Directly test autocomplete endpoint.
$this->assertAutoCompleteMatches($group, 'bbbl', 0);
$this->drupalGet($this->groupMemberAddFormUrl($group));
$value = $blocked_user->getDisplayName() . ' (' . $blocked_user->id() . ')';
$this->submitForm(['Username' => $value], 'Save');
$this->assertSession()->pageTextMatches('/This entity .+ cannot be referenced/');
$this->assertFalse($membership_manager->isMember($group, $blocked_user->id(), []));
// API validate too.
$new_membership = $membership_manager->createMembership($group, $blocked_user);
$errors = $new_membership->validate();
$this->assertTrue(count($errors) > 0);
}
// A user with 'administer users' permission can add blocked users as group
// members.
$perms = ['administer users', 'administer organic groups'];
$admin_user = $this->drupalCreateUser($perms, 'super');
$this->drupalLogin($admin_user);
foreach ($group_data as $data) {
$group = $data[0];
// Blocked user is now found in autocomplete.
$json_data = $this->assertAutoCompleteMatches($group, 'bbbl', 1);
$this->drupalGet($this->groupMemberAddFormUrl($group));
$this->submitForm(['Username' => $json_data[0]['value']], 'Save');
$this->assertTrue($membership_manager->isMember($group, $blocked_user->id()));
}
}
/**
* Get the Url for the member add for for a group.
*
* @param \Drupal\Core\Entity\EntityInterface $group
* The group.
*
* @return \Drupal\Core\Url
* The Url object.
*/
protected function groupMemberAddFormUrl(EntityInterface $group): Url {
$entity_type_id = $group->getEntityTypeId();
$add_form_parameters = [
'group' => $group->id(),
'entity_type_id' => $entity_type_id,
'og_membership_type' => OgMembershipInterface::TYPE_DEFAULT,
];
return Url::fromRoute('entity.og_membership.add_form', $add_form_parameters);
}
/**
* Assert an expected number of matches looking to add a user to a group.
*
* @param \Drupal\Core\Entity\EntityInterface $group
* The group.
* @param string $match
* The search string.
* @param int $expected_count
* The expected count of matches.
*
* @return array
* The data decoded from JSON.
*/
protected function assertAutoCompleteMatches(EntityInterface $group, string $match, int $expected_count): array {
$this->drupalGet($this->groupMemberAddFormUrl($group));
$page = $this->getSession()->getPage();
$input = $page->findField('edit-uid-0-target-id');
$path = $input->getAttribute('data-autocomplete-path');
// Remove potential base path when the site is under a subdirectory.
$base_path = rtrim(base_path(), '/');
if ($base_path && strpos($path, $base_path) === 0) {
$path = substr($path, strlen($base_path));
}
$this->drupalGet($path, ['query' => ['q' => $match]]);
$header = $this->getSession()->getResponseHeader('content-type');
$this->assertSame('application/json', $header);
$out = $this->getSession()->getPage()->getContent();
$data = json_decode($out, TRUE);
$this->assertCount($expected_count, $data, $out);
return $data;
}
/**
* Tests access to the group tab and pages.
*/
public function testGroupTabAccess(): void {
foreach ($this->groupTabScenarios() as $scenario) {
[$account, $code] = $scenario;
if (!$account->isAnonymous()) {
$this->drupalLogin($account);
}
$group_data = [
[$this->groupNode, $this->anotherNodeMembership],
[$this->groupTestEntity, $this->anotherTestEntityMembership],
];
foreach ($group_data as $data) {
[$group, $membership] = $data;
$entity_type_id = $group->getEntityTypeId();
// @see \Drupal\og\Routing\RouteSubscriber::alterRoutes() for the
// routes.
$route_name = "entity.$entity_type_id.og_admin_routes";
$route_parameters = [$entity_type_id => $group->id()];
// For nodes the base admin path is
// 'group/node/' . $this->group->id() . '/admin'.
$this->drupalGet(Url::fromRoute($route_name, $route_parameters));
$this->assertSession()->statusCodeEquals($code);
// This page is rendered by a view. For nodes the path is
// 'group/node/' . $this->group->id() . '/admin/members'.
$members_list_route_name = $route_name . '.members';
$this->drupalGet(Url::fromRoute($members_list_route_name, $route_parameters));
$this->assertSession()->statusCodeEquals($code);
$add_member_route_name = $route_name . '.add_membership_page';
$this->drupalGet(Url::fromRoute($add_member_route_name, $route_parameters));
$this->assertSession()->statusCodeEquals($code);
$add_form_parameters = [
'group' => $group->id(),
'entity_type_id' => $entity_type_id,
'og_membership_type' => OgMembershipInterface::TYPE_DEFAULT,
];
$this->drupalGet(Url::fromRoute('entity.og_membership.add_form', $add_form_parameters));
$this->assertSession()->statusCodeEquals($code);
$this->drupalGet($membership->toUrl());
$this->assertSession()->statusCodeEquals($code);
}
$entity_type_id = $this->nonGroup->getEntityTypeId();
$route_name = "entity.$entity_type_id.og_admin_routes";
$route_parameters = [$entity_type_id => $this->nonGroup->id()];
$this->drupalGet(Url::fromRoute($route_name, $route_parameters));
$this->assertSession()->statusCodeEquals(403);
}
}
/**
* Provide data for testGroupTab().
*
* @return array[]
* Array of test scenarios.
*/
protected function groupTabScenarios(): array {
return [
[$this->authorUser, 200],
[$this->groupAdminUser, 200],
[$this->groupManagerUser, 200],
[$this->user1, 200],
[$this->user2, 403],
[User::load(0), 403],
];
}
}
