linkchecker-8.x-1.x-dev/tests/src/Kernel/LinkCheckerLinkAccessTest.php
tests/src/Kernel/LinkCheckerLinkAccessTest.php
<?php
namespace Drupal\Tests\linkchecker\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\linkchecker\Entity\LinkCheckerLink;
use Drupal\linkchecker\LinkCheckerLinkInterface;
use Drupal\user\RoleInterface;
/**
* Tests basic linkchecker link access functionality.
*
* @group linkchecker
*/
class LinkCheckerLinkAccessTest extends KernelTestBase {
use UserCreationTrait {
createUser as drupalCreateUser;
createRole as drupalCreateRole;
createAdminRole as drupalCreateAdminRole;
}
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'datetime',
'user',
'system',
'filter',
'field',
'text',
'path_alias',
'linkchecker',
];
/**
* Access handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $accessHandler;
/**
* List of fieldable entity types.
*
* @var \Drupal\Core\Entity\EntityType[]
*/
protected $entityTypeDefinitions;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Installing sequences table is deprecated since 10.2 release so call it
// conditionally.
// @see https://www.drupal.org/node/3349345
if (version_compare(\Drupal::VERSION, '10.2', '<')) {
$this->installSchema('system', 'sequences');
}
$this->installSchema('linkchecker', 'linkchecker_index');
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('path_alias');
$this->installEntitySchema('linkcheckerlink');
$this->installConfig('node');
$this->installConfig('linkchecker');
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
$entityTypeManager = $this->container->get('entity_type.manager');
$this->accessHandler = $entityTypeManager
->getAccessControlHandler('linkcheckerlink');
// Find all fieldable entities except LinkCheckerLink.
foreach ($entityTypeManager->getDefinitions() as $definition) {
if ($definition->entityClassImplements(FieldableEntityInterface::class)
&& $definition->id() != 'linkcheckerlink') {
$this->entityTypeDefinitions[] = $definition;
}
}
$this->entityTypeManager = $entityTypeManager;
// Clear permissions for authenticated users.
$this->config('user.role.' . RoleInterface::AUTHENTICATED_ID)
->set('permissions', [])
->save();
// Create user 1 who has special permissions.
$this->drupalCreateUser();
}
/**
* Runs basic tests for link access.
*/
public function testLinkAccess() {
$webUsers = [];
// Ensures user with 'access content' permission can view links.
$webUsers[] = $this->drupalCreateUser([
'administer linkchecker',
'access content',
]);
// Ensures user with 'access content' permission can view links.
$webUsers[] = $this->drupalCreateUser([
'edit linkchecker link settings',
'access content',
]);
// Ensures user without 'access content' permission can do nothing.
$webUsers[] = $this->drupalCreateUser([
'administer linkchecker',
]);
// Ensures user with 'access content' permission can do nothing.
$webUsers[] = $this->drupalCreateUser([
'edit linkchecker link settings',
]);
// Create each fieldable entity and test link access against it.
foreach ($this->entityTypeDefinitions as $entityTypeDefinition) {
$bundleId = $this->createBundle($entityTypeDefinition);
// Create dummy field to which link will be assigned.
$field_storage = [
'field_name' => 'test_text_field',
'entity_type' => $entityTypeDefinition->id(),
'type' => 'text_long',
];
FieldStorageConfig::create($field_storage)->save();
$field = [
'field_name' => $field_storage['field_name'],
'entity_type' => $entityTypeDefinition->id(),
'bundle' => $bundleId,
];
FieldConfig::create($field)->save();
$entity = $this->createEntity($entityTypeDefinition, $bundleId);
$link = LinkCheckerLink::create([
'url' => 'http://example.com/',
'parent_entity_type_id' => $entity->getEntityTypeId(),
'parent_entity_id' => $entity->id(),
'entity_field' => $field_storage['field_name'],
'entity_langcode' => $entity->language()->getId(),
]);
$link->save();
foreach ($webUsers as $user) {
$access = $entity->get($field_storage['field_name'])
->access('view', $user, FALSE);
$access = $access && $entity->access('view', $user, FALSE);
$this->assertLinkAccess([
'view' => $access,
], $link, $user);
}
}
// Check access if parent entity is not exists.
// By default such links will be cleaned up, but in a case if somebody will
// remove parent entity directly from database we should handle it.
$link = LinkCheckerLink::create([
'url' => 'http://example.com/',
'parent_entity_type_id' => 'node',
'parent_entity_id' => 9999,
'entity_field' => 'test_text_field',
'entity_langcode' => 'en',
]);
$link->save();
foreach ($webUsers as $user) {
$this->assertLinkAccess([
'view' => FALSE,
], $link, $user);
}
}
/**
* Asserts that link access correctly grants or denies access.
*
* @param array $ops
* An associative array of the expected link access grants for the link
* and account, with each key as the name of an operation (e.g. 'view',
* 'delete') and each value a Boolean indicating whether access to that
* operation should be granted.
* @param \Drupal\linkchecker\LinkCheckerLinkInterface $link
* The link object to check.
* @param \Drupal\Core\Session\AccountInterface $account
* The user account for which to check access.
*/
public function assertLinkAccess(array $ops, LinkCheckerLinkInterface $link, AccountInterface $account) {
foreach ($ops as $op => $result) {
$this->assertEquals($result, $this->accessHandler->access($link, $op, $account), $this->linkAccessAssertMessage($op, $result));
}
}
/**
* Constructs an assert message to display which link access was tested.
*
* @param string $operation
* The operation to check access for.
* @param bool $result
* Whether access should be granted or not.
*
* @return string
* An assert message string which contains information in plain English
* about the link access permission test that was performed.
*/
public function linkAccessAssertMessage($operation, $result) {
return new FormattableMarkup(
'LinkCheckerLink access returns @result with operation %op.',
[
'@result' => $result ? 'true' : 'false',
'%op' => $operation,
]
);
}
/**
* Helper function for bundle creation.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entityTypeDefinition
* The entity type.
*
* @return string
* The bundle ID.
*/
protected function createBundle(EntityTypeInterface $entityTypeDefinition) {
if ($bundleEntityType = $entityTypeDefinition->getBundleEntityType()) {
$bundleStorage = $this->entityTypeManager->getStorage($bundleEntityType);
// To be sure that we will create non-existing bundle.
do {
$bundleId = strtolower($this->randomMachineName(8));
} while ($bundleStorage->load($bundleId));
$bundleTypeDefinition = $this->entityTypeManager->getDefinition($bundleEntityType);
$bundle = $bundleStorage->create([
$bundleTypeDefinition->getKey('id') => $bundleId,
$bundleTypeDefinition->getKey('label') => $bundleId,
]);
$bundle->save();
}
// Some entities does not have bundles.
else {
$bundleId = $entityTypeDefinition->id();
}
return $bundleId;
}
/**
* Helper function for entity creation.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entityTypeDefinition
* The entity type.
* @param string $bundleId
* Bundle ID.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity.
*/
protected function createEntity(EntityTypeInterface $entityTypeDefinition, $bundleId) {
$entityData = [];
$entityData[$entityTypeDefinition->getKey('bundle')] = $bundleId;
if ($entityTypeDefinition->hasKey('published')) {
$entityData[$entityTypeDefinition->getKey('published')] = 1;
}
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$entity = $this->entityTypeManager
->getStorage($entityTypeDefinition->id())
->create($entityData);
foreach ($entity->getFieldDefinitions() as $fieldDefinition) {
// Skip read-only fields.
if ($fieldDefinition->isReadOnly()) {
continue;
}
// Skip non-required fields.
if (!$fieldDefinition->isRequired()) {
continue;
}
// Skip non-empty fields.
if (!$entity->get($fieldDefinition->getName())->isEmpty()) {
continue;
}
$field = $entity->get($fieldDefinition->getName());
switch ($fieldDefinition->getType()) {
case 'integer':
$field->setValue(['value' => 1]);
break;
default:
$field->setValue(['value' => $this->randomString()]);
break;
}
}
$entity->save();
return $entity;
}
}
