class AccessPolicyProcessorTest

Same name in other branches
  1. 10 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
     *   The access policy processor.
     */
    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 Modifiers Object type Summary Overriden Title
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.
ExpectDeprecationTrait::expectDeprecation public function Adds an expected deprecation.
ExpectDeprecationTrait::setUpErrorHandler public function Sets up the test error handler.
ExpectDeprecationTrait::tearDownErrorHandler public function Tears down the test error handler.
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.
UnitTestCase::$root protected property The app root.
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::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::setDebugDumpHandler public static function Registers the dumper CLI handler when the DebugDump extension is enabled.

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