class AccessPolicyProcessorTest

Same name and namespace in other branches
  1. 11.x core/tests/Drupal/Tests/Core/Session/AccessPolicyProcessorTest.php \Drupal\Tests\Core\Session\AccessPolicyProcessorTest

Tests the AccessPolicyProcessor service.

@covers \Drupal\Core\Session\AccessPolicyBase
@covers \Drupal\Core\Session\AccessPolicyProcessor
@group Session

Hierarchy

Expanded class hierarchy of AccessPolicyProcessorTest

File

core/tests/Drupal/Tests/Core/Session/AccessPolicyProcessorTest.php, line 32

Namespace

Drupal\Tests\Core\Session
View source
class AccessPolicyProcessorTest extends UnitTestCase {
  
  /**
   * {@inheritdoc}
   */
  public function setUp() : void {
    parent::setUp();
    $cache_context_manager = $this->prophesize(CacheContextsManager::class);
    $cache_context_manager->assertValidTokens(Argument::any())
      ->willReturn(TRUE);
    $container = $this->prophesize(ContainerInterface::class);
    $container->get('cache_contexts_manager')
      ->willReturn($cache_context_manager->reveal());
    \Drupal::setContainer($container->reveal());
  }
  
  /**
   * Tests that access policies are properly processed.
   */
  public function testCalculatePermissions() : void {
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $access_policy = new BarAccessPolicy();
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy($access_policy);
    $access_policy_permissions = $access_policy->calculatePermissions($account, 'bar');
    $access_policy_permissions->addCacheTags([
      'access_policies',
    ]);
    $this->assertEquals(new CalculatedPermissions($access_policy_permissions), $processor->processAccessPolicies($account, 'bar'));
  }
  
  /**
   * Tests that access policies that do not apply are not processed.
   */
  public function testCalculatePermissionsNoApply() : void {
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $access_policy = new BarAccessPolicy();
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy($access_policy);
    $no_permissions = new RefinableCalculatedPermissions();
    $no_permissions->addCacheTags([
      'access_policies',
    ]);
    $calculated_permissions = $processor->processAccessPolicies($account, 'nothing');
    $this->assertEquals(new CalculatedPermissions($no_permissions), $calculated_permissions);
  }
  
  /**
   * Tests that access policies can alter the final result.
   */
  public function testAlterPermissions() : void {
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy(new BarAccessPolicy());
    $processor->addAccessPolicy(new BarAlterAccessPolicy());
    $actual_permissions = $processor->processAccessPolicies($account, 'bar')
      ->getItem('bar', 1)
      ->getPermissions();
    $this->assertEquals([
      'foo',
      'baz',
    ], $actual_permissions);
  }
  
  /**
   * Tests that alters that do not apply are not processed.
   */
  public function testAlterPermissionsNoApply() : void {
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy($access_policy = new FooAccessPolicy());
    $processor->addAccessPolicy(new BarAlterAccessPolicy());
    $access_policy_permissions = $access_policy->calculatePermissions($account, 'foo');
    $access_policy_permissions->addCacheTags([
      'access_policies',
    ]);
    $this->assertEquals(new CalculatedPermissions($access_policy_permissions), $processor->processAccessPolicies($account, 'foo'));
  }
  
  /**
   * Tests that access policies which do nothing are properly processed.
   */
  public function testEmptyCalculator() : void {
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $access_policy = new EmptyAccessPolicy();
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy($access_policy);
    $access_policy_permissions = $access_policy->calculatePermissions($account, 'anything');
    $access_policy_permissions->addCacheTags([
      'access_policies',
    ]);
    $calculated_permissions = $processor->processAccessPolicies($account, 'anything');
    $this->assertEquals(new CalculatedPermissions($access_policy_permissions), $calculated_permissions);
  }
  
  /**
   * Tests that everything works if no access policies are present.
   */
  public function testNoCalculators() : void {
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $processor = $this->setUpAccessPolicyProcessor();
    $no_permissions = new RefinableCalculatedPermissions();
    $no_permissions->addCacheTags([
      'access_policies',
    ]);
    $calculated_permissions = $processor->processAccessPolicies($account, 'anything');
    $this->assertEquals(new CalculatedPermissions($no_permissions), $calculated_permissions);
  }
  
  /**
   * Tests the wrong scope exception.
   */
  public function testWrongScopeException() : void {
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy(new AlwaysAddsAccessPolicy());
    $this->expectException(AccessPolicyScopeException::class);
    $this->expectExceptionMessage(sprintf('The access policy "%s" returned permissions for scopes other than "%s".', AlwaysAddsAccessPolicy::class, 'bar'));
    $processor->processAccessPolicies($this->prophesize(AccountInterface::class)
      ->reveal(), 'bar');
  }
  
  /**
   * Tests the multiple scopes exception.
   */
  public function testMultipleScopeException() : void {
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy(new FooAccessPolicy());
    $processor->addAccessPolicy(new AlwaysAddsAccessPolicy());
    $this->expectException(AccessPolicyScopeException::class);
    $this->expectExceptionMessage(sprintf('The access policy "%s" returned permissions for scopes other than "%s".', AlwaysAddsAccessPolicy::class, 'foo'));
    $processor->processAccessPolicies($this->prophesize(AccountInterface::class)
      ->reveal(), 'foo');
  }
  
  /**
   * Tests the multiple scopes exception.
   */
  public function testMultipleScopeAlterException() : void {
    $processor = $this->setUpAccessPolicyProcessor();
    $processor->addAccessPolicy(new FooAccessPolicy());
    $processor->addAccessPolicy(new AlwaysAltersAccessPolicy());
    $this->expectException(AccessPolicyScopeException::class);
    $this->expectExceptionMessage(sprintf('The access policy "%s" altered permissions in a scope other than "%s".', AlwaysAltersAccessPolicy::class, 'foo'));
    $processor->processAccessPolicies($this->prophesize(AccountInterface::class)
      ->reveal(), 'foo');
  }
  
  /**
   * Tests if the account switcher switches properly when user cache context is present.
   *
   * @param bool $has_user_context
   *   Whether a user based cache context is present.
   * @param bool $is_current_user
   *   Whether the passed in account is the current user.
   * @param bool $should_call_switcher
   *   Whether the account switcher should be called.
   *
   * @dataProvider accountSwitcherProvider
   */
  public function testAccountSwitcher(bool $has_user_context, bool $is_current_user, bool $should_call_switcher) : void {
    $account = $this->prophesize(AccountInterface::class);
    $account->id()
      ->willReturn(2);
    $account = $account->reveal();
    $current_user = $this->prophesize(AccountProxyInterface::class);
    $current_user->id()
      ->willReturn($is_current_user ? 2 : 13);
    $account_switcher = $this->prophesize(AccountSwitcherInterface::class);
    if ($should_call_switcher) {
      $account_switcher->switchTo($account)
        ->shouldBeCalledTimes(1);
      $account_switcher->switchBack()
        ->shouldBeCalledTimes(1);
    }
    else {
      $account_switcher->switchTo($account)
        ->shouldNotBeCalled();
      $account_switcher->switchBack()
        ->shouldNotBeCalled();
    }
    $processor = $this->setUpAccessPolicyProcessor(NULL, NULL, NULL, $current_user->reveal(), $account_switcher->reveal());
    $processor->addAccessPolicy(new BarAccessPolicy());
    if ($has_user_context) {
      $processor->addAccessPolicy(new UserContextAccessPolicy());
    }
    $processor->processAccessPolicies($account, 'bar');
  }
  
  /**
   * Data provider for testAccountSwitcher().
   *
   * @return array
   *   A list of testAccountSwitcher method arguments.
   */
  public static function accountSwitcherProvider() {
    $cases['no-user-context-no-current-user'] = [
      'has_user_context' => FALSE,
      'is_current_user' => FALSE,
      'should_call_switcher' => FALSE,
    ];
    $cases['no-user-context-current-user'] = [
      'has_user_context' => FALSE,
      'is_current_user' => TRUE,
      'should_call_switcher' => FALSE,
    ];
    $cases['user-context-no-current-user'] = [
      'has_user_context' => TRUE,
      'is_current_user' => FALSE,
      'should_call_switcher' => TRUE,
    ];
    $cases['user-context-current-user'] = [
      'has_user_context' => TRUE,
      'is_current_user' => TRUE,
      'should_call_switcher' => FALSE,
    ];
    return $cases;
  }
  
  /**
   * Tests if the caches are called correctly.
   *
   * @dataProvider cachingProvider
   */
  public function testCaching(bool $db_cache_hit, bool $static_cache_hit) : void {
    if ($static_cache_hit) {
      $this->assertFalse($db_cache_hit, 'DB cache should never be checked when there is a static hit.');
    }
    $account = $this->prophesize(AccountInterface::class)
      ->reveal();
    $scope = 'bar';
    $bar_access_policy = new BarAccessPolicy();
    $bar_permissions = $bar_access_policy->calculatePermissions($account, $scope);
    $bar_permissions->addCacheTags([
      'access_policies',
    ]);
    $none_refinable_bar_permissions = new CalculatedPermissions($bar_permissions);
    $cache_static = $this->prophesize(VariationCacheInterface::class);
    $cache_db = $this->prophesize(VariationCacheInterface::class);
    if (!$static_cache_hit) {
      if (!$db_cache_hit) {
        $cache_db->get(Argument::cetera())
          ->willReturn(FALSE);
        $cache_db->set(Argument::any(), $bar_permissions, Argument::cetera())
          ->shouldBeCalled();
      }
      else {
        $cache_item = new CacheItem($bar_permissions);
        $cache_db->get(Argument::cetera())
          ->willReturn($cache_item);
        $cache_db->set()
          ->shouldNotBeCalled();
      }
      $cache_static->get(Argument::cetera())
        ->willReturn(FALSE);
      $cache_static->set(Argument::any(), $none_refinable_bar_permissions, Argument::cetera())
        ->shouldBeCalled();
    }
    else {
      $cache_item = new CacheItem($none_refinable_bar_permissions);
      $cache_static->get(Argument::cetera())
        ->willReturn($cache_item);
      $cache_static->set()
        ->shouldNotBeCalled();
    }
    $cache_static = $cache_static->reveal();
    $cache_db = $cache_db->reveal();
    $processor = $this->setUpAccessPolicyProcessor($cache_db, $cache_static);
    $processor->addAccessPolicy($bar_access_policy);
    $permissions = $processor->processAccessPolicies($account, $scope);
    $this->assertEquals($none_refinable_bar_permissions, $permissions, 'Cached permission matches calculated.');
  }
  
  /**
   * Data provider for testCaching().
   *
   * @return array
   *   A list of testAccountSwitcher method arguments.
   */
  public static function cachingProvider() {
    $cases = [
      'no-cache' => [
        FALSE,
        FALSE,
      ],
      'static-cache-hit' => [
        FALSE,
        TRUE,
      ],
      'db-cache-hit' => [
        TRUE,
        FALSE,
      ],
    ];
    return $cases;
  }
  
  /**
   * Tests that only the cache contexts for policies that apply are added.
   */
  public function testCacheContexts() : void {
    // BazAccessPolicy and BarAlterAccessPolicy shouldn't add any contexts.
    $initial_cacheability = (new CacheableMetadata())->addCacheContexts([
      'foo',
      'bar',
    ]);
    $final_cacheability = (new CacheableMetadata())->addCacheContexts([
      'foo',
      'bar',
    ])
      ->addCacheTags([
      'access_policies',
    ]);
    $variation_cache = $this->prophesize(VariationCacheInterface::class);
    $variation_cache->get(Argument::cetera())
      ->willReturn(FALSE);
    $variation_cache->set([
      'access_policies',
      'anything',
    ], Argument::any(), $final_cacheability, $initial_cacheability)
      ->shouldBeCalled();
    $cache_static = $this->prophesize(CacheBackendInterface::class);
    $cache_static->get('access_policies:access_policy_processor:contexts:anything')
      ->willReturn(FALSE);
    $cache_static->set('access_policies:access_policy_processor:contexts:anything', [
      'foo',
      'bar',
    ])
      ->shouldBeCalled();
    $processor = $this->setUpAccessPolicyProcessor($variation_cache->reveal(), NULL, $cache_static->reveal());
    foreach ([
      new FooAccessPolicy(),
      new BarAccessPolicy(),
      new BazAccessPolicy(),
      new BarAlterAccessPolicy(),
    ] as $access_policy) {
      $processor->addAccessPolicy($access_policy);
    }
    $processor->processAccessPolicies($this->prophesize(AccountInterface::class)
      ->reveal(), 'anything');
  }
  
  /**
   * Tests that the persistent cache contexts are added properly.
   */
  public function testCacheContextCaching() : void {
    $cache_entry = new \stdClass();
    $cache_entry->data = [
      'baz',
    ];
    $cache_static = $this->prophesize(CacheBackendInterface::class);
    $cache_static->get('access_policies:access_policy_processor:contexts:anything')
      ->willReturn($cache_entry);
    $cache_static->set('access_policies:access_policy_processor:contexts:anything', Argument::any())
      ->shouldNotBeCalled();
    // Hard-coded to "baz" because of the above cache entry.
    $initial_cacheability = (new CacheableMetadata())->addCacheContexts([
      'baz',
    ]);
    // Still adds in "foo" and "bar" in calculatePermissions(). Under normal
    // circumstances this would trigger an exception in VariationCache, but we
    // deliberately poison the cache in this test to see if it's called.
    $final_cacheability = (new CacheableMetadata())->addCacheContexts([
      'foo',
      'bar',
    ])
      ->addCacheTags([
      'access_policies',
    ]);
    $variation_cache = $this->prophesize(VariationCacheInterface::class);
    $variation_cache->get([
      'access_policies',
      'anything',
    ], $initial_cacheability)
      ->shouldBeCalled()
      ->willReturn(FALSE);
    $variation_cache->set([
      'access_policies',
      'anything',
    ], Argument::any(), $final_cacheability, $initial_cacheability)
      ->shouldBeCalled();
    $processor = $this->setUpAccessPolicyProcessor($variation_cache->reveal(), NULL, $cache_static->reveal());
    foreach ([
      new FooAccessPolicy(),
      new BarAccessPolicy(),
      new BazAccessPolicy(),
      new BarAlterAccessPolicy(),
    ] as $access_policy) {
      $processor->addAccessPolicy($access_policy);
    }
    $processor->processAccessPolicies($this->prophesize(AccountInterface::class)
      ->reveal(), 'anything');
  }
  
  /**
   * Sets up the access policy processor.
   *
   * @return \Drupal\Core\Session\AccessPolicyProcessorInterface
   */
  protected function setUpAccessPolicyProcessor(?VariationCacheInterface $variation_cache = NULL, ?VariationCacheInterface $variation_cache_static = NULL, ?CacheBackendInterface $cache_static = NULL, ?AccountProxyInterface $current_user = NULL, ?AccountSwitcherInterface $account_switcher = NULL) {
    // Prophecy does not accept a willReturn call on a mocked method if said
    // method has a return type of void. However, without willReturn() or any
    // other will* call, the method mock will not be registered.
    $prophecy_workaround = function () {
    };
    if (!isset($variation_cache)) {
      $variation_cache = $this->prophesize(VariationCacheInterface::class);
      $variation_cache->get(Argument::cetera())
        ->willReturn(FALSE);
      $variation_cache->set(Argument::cetera())
        ->will($prophecy_workaround);
      $variation_cache = $variation_cache->reveal();
    }
    if (!isset($variation_cache_static)) {
      $variation_cache_static = $this->prophesize(VariationCacheInterface::class);
      $variation_cache_static->get(Argument::cetera())
        ->willReturn(FALSE);
      $variation_cache_static->set(Argument::cetera())
        ->will($prophecy_workaround);
      $variation_cache_static = $variation_cache_static->reveal();
    }
    if (!isset($cache_static)) {
      $cache_static = $this->prophesize(CacheBackendInterface::class);
      $cache_static->get(Argument::cetera())
        ->willReturn(FALSE);
      $cache_static->set(Argument::cetera())
        ->will($prophecy_workaround);
      $cache_static = $cache_static->reveal();
    }
    if (!isset($current_user)) {
      $current_user = $this->prophesize(AccountProxyInterface::class)
        ->reveal();
    }
    if (!isset($account_switcher)) {
      $account_switcher = $this->prophesize(AccountSwitcherInterface::class)
        ->reveal();
    }
    return new AccessPolicyProcessor($variation_cache, $variation_cache_static, $cache_static, $current_user, $account_switcher);
  }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overriden Title Overrides
AccessPolicyProcessorTest::accountSwitcherProvider public static function Data provider for testAccountSwitcher().
AccessPolicyProcessorTest::cachingProvider public static function Data provider for testCaching().
AccessPolicyProcessorTest::setUp public function Overrides UnitTestCase::setUp
AccessPolicyProcessorTest::setUpAccessPolicyProcessor protected function Sets up the access policy processor.
AccessPolicyProcessorTest::testAccountSwitcher public function Tests if the account switcher switches properly when user cache context is present.
AccessPolicyProcessorTest::testAlterPermissions public function Tests that access policies can alter the final result.
AccessPolicyProcessorTest::testAlterPermissionsNoApply public function Tests that alters that do not apply are not processed.
AccessPolicyProcessorTest::testCacheContextCaching public function Tests that the persistent cache contexts are added properly.
AccessPolicyProcessorTest::testCacheContexts public function Tests that only the cache contexts for policies that apply are added.
AccessPolicyProcessorTest::testCaching public function Tests if the caches are called correctly.
AccessPolicyProcessorTest::testCalculatePermissions public function Tests that access policies are properly processed.
AccessPolicyProcessorTest::testCalculatePermissionsNoApply public function Tests that access policies that do not apply are not processed.
AccessPolicyProcessorTest::testEmptyCalculator public function Tests that access policies which do nothing are properly processed.
AccessPolicyProcessorTest::testMultipleScopeAlterException public function Tests the multiple scopes exception.
AccessPolicyProcessorTest::testMultipleScopeException public function Tests the multiple scopes exception.
AccessPolicyProcessorTest::testNoCalculators public function Tests that everything works if no access policies are present.
AccessPolicyProcessorTest::testWrongScopeException public function Tests the wrong scope exception.
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
RandomGeneratorTrait::getRandomGenerator protected function Gets the random generator for the utility methods.
RandomGeneratorTrait::randomMachineName protected function Generates a unique random string containing letters and numbers.
RandomGeneratorTrait::randomObject public function Generates a random PHP object.
RandomGeneratorTrait::randomString public function Generates a pseudo-random string of ASCII characters of codes 32 to 126.
RandomGeneratorTrait::randomStringValidate Deprecated public function Callback for random string validation.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getConfigStorageStub public function Returns a stub config storage that returns the supplied configuration.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::setUpBeforeClass public static function
UnitTestCase::__get public function

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.