Newer
Older
catch
committed
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Access\AccessResultTest.
*/
namespace Drupal\Tests\Core\Access;
use Drupal\Core\Access\AccessResult;
Alex Pott
committed
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Access\AccessResultReasonInterface;
catch
committed
use Drupal\Core\Cache\Cache;
Alex Pott
committed
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
catch
committed
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\Core\Access\AccessResult
* @group Access
*/
class AccessResultTest extends UnitTestCase {
/**
* The cache contexts manager.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheContextsManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $this->cacheContextsManager);
\Drupal::setContainer($container);
}
catch
committed
protected function assertDefaultCacheability(AccessResult $access) {
$this->assertSame([], $access->getCacheContexts());
catch
committed
$this->assertSame([], $access->getCacheTags());
$this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
}
/**
* Tests the construction of an AccessResult object.
*
* @covers ::neutral
catch
committed
*/
public function testConstruction() {
$verify = function (AccessResult $access) {
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertTrue($access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
};
// Verify the object when using the constructor.
Alex Pott
committed
$a = new AccessResultNeutral();
catch
committed
$verify($a);
// Verify the object when using the ::create() convenience method.
Alex Pott
committed
$b = AccessResult::neutral();
catch
committed
$verify($b);
$this->assertEquals($a, $b);
}
/**
* @covers ::allowed
* @covers ::isAllowed
* @covers ::isForbidden
Alex Pott
committed
* @covers ::isNeutral
catch
committed
*/
public function testAccessAllowed() {
$verify = function (AccessResult $access) {
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
};
// Verify the object when using the ::allowed() convenience static method.
$b = AccessResult::allowed();
$verify($b);
}
/**
* @covers ::forbidden
* @covers ::isAllowed
* @covers ::isForbidden
Alex Pott
committed
* @covers ::isNeutral
catch
committed
*/
public function testAccessForbidden() {
$verify = function (AccessResult $access) {
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
};
// Verify the object when using the ::forbidden() convenience static method.
$b = AccessResult::forbidden();
$verify($b);
}
/**
* @covers ::forbidden
*/
public function testAccessForbiddenReason() {
$verify = function (AccessResult $access, $reason) {
$this->assertInstanceOf(AccessResultReasonInterface::class, $access);
$this->assertSame($reason, $access->getReason());
};
$b = AccessResult::forbidden();
$verify($b, NULL);
$reason = $this->getRandomGenerator()->string();
$b = AccessResult::forbidden($reason);
$verify($b, $reason);
$b = AccessResult::forbiddenIf(TRUE, $reason);
$verify($b, $reason);
}
catch
committed
/**
* @covers ::allowedIf
* @covers ::isAllowed
* @covers ::isForbidden
Alex Pott
committed
* @covers ::isNeutral
catch
committed
*/
public function testAccessConditionallyAllowed() {
Alex Pott
committed
$verify = function (AccessResult $access, $allowed) {
catch
committed
$this->assertSame($allowed, $access->isAllowed());
Alex Pott
committed
$this->assertFalse($access->isForbidden());
$this->assertSame(!$allowed, $access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
};
$b1 = AccessResult::allowedIf(TRUE);
$verify($b1, TRUE);
$b2 = AccessResult::allowedIf(FALSE);
$verify($b2, FALSE);
}
/**
* @covers ::forbiddenIf
* @covers ::isAllowed
* @covers ::isForbidden
Alex Pott
committed
* @covers ::isNeutral
catch
committed
*/
public function testAccessConditionallyForbidden() {
Alex Pott
committed
$verify = function (AccessResult $access, $forbidden) {
$this->assertFalse($access->isAllowed());
catch
committed
$this->assertSame($forbidden, $access->isForbidden());
Alex Pott
committed
$this->assertSame(!$forbidden, $access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
};
$b1 = AccessResult::forbiddenIf(TRUE);
$verify($b1, TRUE);
$b2 = AccessResult::forbiddenIf(FALSE);
$verify($b2, FALSE);
}
/**
* @covers ::andIf
*/
public function testAndIf() {
Alex Pott
committed
$neutral = AccessResult::neutral('neutral message');
catch
committed
$allowed = AccessResult::allowed();
Alex Pott
committed
$forbidden = AccessResult::forbidden('forbidden message');
catch
committed
$unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
$unused_access_result_due_to_lazy_evaluation->expects($this->never())
->method($this->anything());
Alex Pott
committed
// ALLOWED && ALLOWED === ALLOWED.
$access = $allowed->andIf($allowed);
catch
committed
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// ALLOWED && NEUTRAL === NEUTRAL.
$access = $allowed->andIf($neutral);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertTrue($access->isNeutral());
Alex Pott
committed
$this->assertEquals('neutral message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// ALLOWED && FORBIDDEN === FORBIDDEN.
$access = $allowed->andIf($forbidden);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// NEUTRAL && ALLOW == NEUTRAL
$access = $neutral->andIf($allowed);
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
Alex Pott
committed
$this->assertEquals('neutral message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// NEUTRAL && NEUTRAL === NEUTRAL.
$access = $neutral->andIf($neutral);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertTrue($access->isNeutral());
Alex Pott
committed
$this->assertEquals('neutral message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// NEUTRAL && FORBIDDEN === FORBIDDEN.
$access = $neutral->andIf($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// FORBIDDEN && ALLOWED = FORBIDDEN
$access = $forbidden->andif($allowed);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// FORBIDDEN && NEUTRAL = FORBIDDEN
$access = $forbidden->andif($neutral);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// FORBIDDEN && FORBIDDEN = FORBIDDEN
$access = $forbidden->andif($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// FORBIDDEN && * === FORBIDDEN: lazy evaluation verification.
$access = $forbidden->andIf($unused_access_result_due_to_lazy_evaluation);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
}
/**
* @covers ::orIf
*/
public function testOrIf() {
Alex Pott
committed
$neutral = AccessResult::neutral('neutral message');
$neutral_other = AccessResult::neutral('other neutral message');
$neutral_reasonless = AccessResult::neutral();
catch
committed
$allowed = AccessResult::allowed();
Alex Pott
committed
$forbidden = AccessResult::forbidden('forbidden message');
$forbidden_other = AccessResult::forbidden('other forbidden message');
$forbidden_reasonless = AccessResult::forbidden();
catch
committed
$unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
$unused_access_result_due_to_lazy_evaluation->expects($this->never())
->method($this->anything());
Alex Pott
committed
// ALLOWED || ALLOWED === ALLOWED.
$access = $allowed->orIf($allowed);
catch
committed
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// ALLOWED || NEUTRAL === ALLOWED.
$access = $allowed->orIf($neutral);
catch
committed
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// ALLOWED || FORBIDDEN === FORBIDDEN.
$access = $allowed->orIf($forbidden);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// NEUTRAL || NEUTRAL === NEUTRAL.
$access = $neutral->orIf($neutral);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertTrue($access->isNeutral());
Alex Pott
committed
$this->assertEquals('neutral message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
// Reason inheritance edge case: first reason is kept.
$access = $neutral->orIf($neutral_other);
$this->assertEquals('neutral message', $access->getReason());
$access = $neutral_other->orIf($neutral);
$this->assertEquals('other neutral message', $access->getReason());
// Reason inheritance edge case: one of the operands is reasonless.
$access = $neutral->orIf($neutral_reasonless);
$this->assertEquals('neutral message', $access->getReason());
$access = $neutral_reasonless->orIf($neutral);
$this->assertEquals('neutral message', $access->getReason());
$access = $neutral_reasonless->orIf($neutral_reasonless);
$this->assertNull($access->getReason());
catch
committed
Alex Pott
committed
// NEUTRAL || ALLOWED === ALLOWED.
$access = $neutral->orIf($allowed);
catch
committed
$this->assertTrue($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
$this->assertDefaultCacheability($access);
// NEUTRAL || FORBIDDEN === FORBIDDEN.
$access = $neutral->orIf($forbidden);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// FORBIDDEN || ALLOWED === FORBIDDEN.
$access = $forbidden->orIf($allowed);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// FORBIDDEN || NEUTRAL === FORBIDDEN.
$access = $forbidden->orIf($neutral);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
Alex Pott
committed
// FORBIDDEN || FORBIDDEN === FORBIDDEN.
$access = $forbidden->orIf($forbidden);
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
Alex Pott
committed
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
Alex Pott
committed
$this->assertDefaultCacheability($access);
// Reason inheritance edge case: first reason is kept.
$access = $forbidden->orIf($forbidden_other);
$this->assertEquals('forbidden message', $access->getReason());
$access = $forbidden_other->orIf($forbidden);
$this->assertEquals('other forbidden message', $access->getReason());
// Reason inheritance edge case: one of the operands is reasonless.
$access = $forbidden->orIf($forbidden_reasonless);
$this->assertEquals('forbidden message', $access->getReason());
$access = $forbidden_reasonless->orIf($forbidden);
$this->assertEquals('forbidden message', $access->getReason());
$access = $forbidden_reasonless->orIf($forbidden_reasonless);
$this->assertNull($access->getReason());
Alex Pott
committed
// FORBIDDEN || * === FORBIDDEN.
$access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
$this->assertFalse($access->isAllowed());
$this->assertTrue($access->isForbidden());
$this->assertFalse($access->isNeutral());
Alex Pott
committed
$this->assertEquals('forbidden message', $access->getReason());
catch
committed
$this->assertDefaultCacheability($access);
}
/**
* @covers ::setCacheMaxAge
* @covers ::getCacheMaxAge
*/
public function testCacheMaxAge() {
Alex Pott
committed
$this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
$this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
catch
committed
}
/**
* @covers ::addCacheContexts
* @covers ::resetCacheContexts
* @covers ::getCacheContexts
* @covers ::cachePerPermissions
catch
committed
* @covers ::cachePerUser
* @covers ::allowedIfHasPermission
*/
public function testCacheContexts() {
$verify = function (AccessResult $access, array $contexts) {
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertTrue($access->isNeutral());
catch
committed
$this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
$this->assertSame($contexts, $access->getCacheContexts());
catch
committed
$this->assertSame([], $access->getCacheTags());
};
$access = AccessResult::neutral()->addCacheContexts(['foo']);
$verify($access, ['foo']);
catch
committed
// Verify resetting works.
$access->resetCacheContexts();
$verify($access, []);
// Verify idempotency.
$access->addCacheContexts(['foo'])
->addCacheContexts(['foo']);
$verify($access, ['foo']);
catch
committed
// Verify same values in different call order yields the same result.
$access->resetCacheContexts()
->addCacheContexts(['foo'])
->addCacheContexts(['bar']);
$verify($access, ['bar', 'foo']);
catch
committed
$access->resetCacheContexts()
->addCacheContexts(['bar'])
->addCacheContexts(['foo']);
$verify($access, ['bar', 'foo']);
catch
committed
// ::cachePerPermissions() convenience method.
$contexts = ['user.permissions'];
Alex Pott
committed
$a = AccessResult::neutral()->addCacheContexts($contexts);
catch
committed
$verify($a, $contexts);
$b = AccessResult::neutral()->cachePerPermissions();
catch
committed
$verify($b, $contexts);
$this->assertEquals($a, $b);
// ::cachePerUser() convenience method.
$contexts = ['user'];
Alex Pott
committed
$a = AccessResult::neutral()->addCacheContexts($contexts);
catch
committed
$verify($a, $contexts);
Alex Pott
committed
$b = AccessResult::neutral()->cachePerUser();
catch
committed
$verify($b, $contexts);
$this->assertEquals($a, $b);
// Both.
$contexts = ['user', 'user.permissions'];
Alex Pott
committed
$a = AccessResult::neutral()->addCacheContexts($contexts);
catch
committed
$verify($a, $contexts);
$b = AccessResult::neutral()->cachePerPermissions()->cachePerUser();
catch
committed
$verify($b, $contexts);
$c = AccessResult::neutral()->cachePerUser()->cachePerPermissions();
catch
committed
$verify($c, $contexts);
$this->assertEquals($a, $b);
$this->assertEquals($a, $c);
// ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
$account = $this->getMock('\Drupal\Core\Session\AccountInterface');
$account->expects($this->any())
->method('hasPermission')
->with('may herd llamas')
->will($this->returnValue(FALSE));
$contexts = ['user.permissions'];
catch
committed
// Verify the object when using the ::allowedIfHasPermission() convenience
// static method.
$b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
$verify($b, $contexts);
}
/**
* @covers ::addCacheTags
* @covers ::addCacheableDependency
catch
committed
* @covers ::getCacheTags
* @covers ::resetCacheTags
catch
committed
*/
public function testCacheTags() {
$verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) {
catch
committed
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
Alex Pott
committed
$this->assertTrue($access->isNeutral());
$this->assertSame($max_age, $access->getCacheMaxAge());
$this->assertSame($contexts, $access->getCacheContexts());
catch
committed
$this->assertSame($tags, $access->getCacheTags());
};
$access = AccessResult::neutral()->addCacheTags(['foo:bar']);
$verify($access, ['foo:bar']);
catch
committed
// Verify resetting works.
$access->resetCacheTags();
$verify($access, []);
// Verify idempotency.
$access->addCacheTags(['foo:bar'])
->addCacheTags(['foo:bar']);
$verify($access, ['foo:bar']);
catch
committed
// Verify same values in different call order yields the same result.
$access->resetCacheTags()
->addCacheTags(['bar:baz'])
->addCacheTags(['bar:qux'])
->addCacheTags(['foo:bar'])
->addCacheTags(['foo:baz']);
$verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
catch
committed
$access->resetCacheTags()
->addCacheTags(['foo:bar'])
->addCacheTags(['bar:qux'])
->addCacheTags(['foo:baz'])
->addCacheTags(['bar:baz']);
$verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
catch
committed
// ::addCacheableDependency() convenience method.
catch
committed
$node = $this->getMock('\Drupal\node\NodeInterface');
$node->expects($this->any())
->method('getCacheTags')
->will($this->returnValue(['node:20011988']));
$node->expects($this->any())
->method('getCacheMaxAge')
->willReturn(600);
$node->expects($this->any())
->method('getCacheContexts')
->willReturn(['user']);
$tags = ['node:20011988'];
Alex Pott
committed
$a = AccessResult::neutral()->addCacheTags($tags);
catch
committed
$verify($a, $tags);
$b = AccessResult::neutral()->addCacheableDependency($node);
$verify($b, $tags, ['user'], 600);
$non_cacheable_dependency = new \stdClass();
$non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
$verify($non_cacheable, [], [], 0);
catch
committed
}
/**
Alex Pott
committed
* @covers ::inheritCacheability
catch
committed
*/
Alex Pott
committed
public function testInheritCacheability() {
catch
committed
// andIf(); 1st has defaults, 2nd has custom tags, contexts and max-age.
Alex Pott
committed
$access = AccessResult::allowed();
$other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']);
Alex Pott
committed
$this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
$this->assertSame(['user.permissions'], $access->getCacheContexts());
$this->assertSame(['node:20011988'], $access->getCacheTags());
catch
committed
$this->assertSame(1500, $access->getCacheMaxAge());
// andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age.
Alex Pott
committed
$access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200);
$other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400);
Alex Pott
committed
$this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
$this->assertSame(['user'], $access->getCacheContexts());
$this->assertSame(['node:14031991'], $access->getCacheTags());
catch
committed
$this->assertSame(43200, $access->getCacheMaxAge());
Alex Pott
committed
}
catch
committed
Alex Pott
committed
/**
* Provides a list of access result pairs and operations to test.
*
* This tests the propagation of cacheability metadata. Rather than testing
* every single bit of cacheability metadata, which would lead to a mind-
* boggling number of permutations, in this test, we only consider the
* permutations of all pairs of the following set:
Alex Pott
committed
* - Allowed, implements CDI and is cacheable.
* - Allowed, implements CDI and is not cacheable.
* - Allowed, does not implement CDI (hence not cacheable).
* - Forbidden, implements CDI and is cacheable.
* - Forbidden, implements CDI and is not cacheable.
* - Forbidden, does not implement CDI (hence not cacheable).
* - Neutral, implements CDI and is cacheable.
* - Neutral, implements CDI and is not cacheable.
* - Neutral, does not implement CDI (hence not cacheable).
Alex Pott
committed
*
* (Where "CDI" is CacheableDependencyInterface.)
Alex Pott
committed
*
* This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There
* are two operations to test (AND and OR), so that leads to a grand total of
* 144 permutations, all of which are tested.
*
* There are two "contagious" patterns:
Alex Pott
committed
* - Any operation with a forbidden access result yields a forbidden result.
* This therefore also applies to the cacheability metadata associated with
* a forbidden result. This is the case for bullets 4, 5 and 6 in the set
* above.
* - Any operation yields an access result object that is of the same class
* (implementation) as the first operand. This is because operations are
* invoked on the first operand. Therefore, if the first implementation
* does not implement CacheableDependencyInterface, then the result won't
* either. This is the case for bullets 3, 6 and 9 in the set above.
Alex Pott
committed
*/
public function andOrCacheabilityPropagationProvider() {
// ct: cacheable=true, cf: cacheable=false, un: uncacheable.
// Note: the test cases that have a "un" access result as the first operand
// test UncacheableTestAccessResult, not AccessResult. However, we
// definitely want to verify that AccessResult's orIf() and andIf() methods
// work correctly when given an AccessResultInterface implementation that
Alex Pott
committed
// does not implement CacheableDependencyInterface, and we want to test the
// full gamut of permutations, so that's not a problem.
Alex Pott
committed
$allowed_ct = AccessResult::allowed();
Alex Pott
committed
$allowed_cf = AccessResult::allowed()->setCacheMaxAge(0);
Alex Pott
committed
$allowed_un = new UncacheableTestAccessResult('ALLOWED');
$forbidden_ct = AccessResult::forbidden();
Alex Pott
committed
$forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0);
Alex Pott
committed
$forbidden_un = new UncacheableTestAccessResult('FORBIDDEN');
$neutral_ct = AccessResult::neutral();
Alex Pott
committed
$neutral_cf = AccessResult::neutral()->setCacheMaxAge(0);
Alex Pott
committed
$neutral_un = new UncacheableTestAccessResult('NEUTRAL');
// Structure:
// - First column: first access result.
// - Second column: operator ('OR' or 'AND').
// - Third column: second access result.
Alex Pott
committed
// - Fourth column: whether result implements CacheableDependencyInterface
Alex Pott
committed
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
// - Fifth column: whether the result is cacheable (if column 4 is TRUE)
return [
// Allowed (ct) OR allowed (ct,cf,un).
[$allowed_ct, 'OR', $allowed_ct, TRUE, TRUE],
[$allowed_ct, 'OR', $allowed_cf, TRUE, TRUE],
[$allowed_ct, 'OR', $allowed_un, TRUE, TRUE],
// Allowed (cf) OR allowed (ct,cf,un).
[$allowed_cf, 'OR', $allowed_ct, TRUE, TRUE],
[$allowed_cf, 'OR', $allowed_cf, TRUE, FALSE],
[$allowed_cf, 'OR', $allowed_un, TRUE, FALSE],
// Allowed (un) OR allowed (ct,cf,un).
[$allowed_un, 'OR', $allowed_ct, FALSE, NULL],
[$allowed_un, 'OR', $allowed_cf, FALSE, NULL],
[$allowed_un, 'OR', $allowed_un, FALSE, NULL],
// Allowed (ct) OR forbidden (ct,cf,un).
[$allowed_ct, 'OR', $forbidden_ct, TRUE, TRUE],
[$allowed_ct, 'OR', $forbidden_cf, TRUE, FALSE],
[$allowed_ct, 'OR', $forbidden_un, TRUE, FALSE],
// Allowed (cf) OR forbidden (ct,cf,un).
[$allowed_cf, 'OR', $forbidden_ct, TRUE, TRUE],
[$allowed_cf, 'OR', $forbidden_cf, TRUE, FALSE],
[$allowed_cf, 'OR', $forbidden_un, TRUE, FALSE],
// Allowed (un) OR forbidden (ct,cf,un).
[$allowed_un, 'OR', $forbidden_ct, FALSE, NULL],
[$allowed_un, 'OR', $forbidden_cf, FALSE, NULL],
[$allowed_un, 'OR', $forbidden_un, FALSE, NULL],
// Allowed (ct) OR neutral (ct,cf,un).
[$allowed_ct, 'OR', $neutral_ct, TRUE, TRUE],
[$allowed_ct, 'OR', $neutral_cf, TRUE, TRUE],
[$allowed_ct, 'OR', $neutral_un, TRUE, TRUE],
// Allowed (cf) OR neutral (ct,cf,un).
[$allowed_cf, 'OR', $neutral_ct, TRUE, FALSE],
[$allowed_cf, 'OR', $neutral_cf, TRUE, FALSE],
[$allowed_cf, 'OR', $neutral_un, TRUE, FALSE],
// Allowed (un) OR neutral (ct,cf,un).
[$allowed_un, 'OR', $neutral_ct, FALSE, NULL],
[$allowed_un, 'OR', $neutral_cf, FALSE, NULL],
[$allowed_un, 'OR', $neutral_un, FALSE, NULL],
// Forbidden (ct) OR allowed (ct,cf,un).
[$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
[$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
[$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
// Forbidden (cf) OR allowed (ct,cf,un).
[$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
[$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
[$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
// Forbidden (un) OR allowed (ct,cf,un).
[$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
[$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
[$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
// Forbidden (ct) OR neutral (ct,cf,un).
[$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
[$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
[$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
// Forbidden (cf) OR neutral (ct,cf,un).
[$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
[$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
[$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
// Forbidden (un) OR neutral (ct,cf,un).
[$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
[$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
[$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
// Forbidden (ct) OR forbidden (ct,cf,un).
[$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
[$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
[$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
// Forbidden (cf) OR forbidden (ct,cf,un).
[$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
[$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
[$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
// Forbidden (un) OR forbidden (ct,cf,un).
[$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
[$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
[$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
// Neutral (ct) OR allowed (ct,cf,un).
[$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
[$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
[$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
// Neutral (cf) OR allowed (ct,cf,un).
[$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
[$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
[$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
// Neutral (un) OR allowed (ct,cf,un).
[$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
[$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
[$neutral_un, 'OR', $allowed_un, FALSE, NULL],
// Neutral (ct) OR neutral (ct,cf,un).
[$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
[$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
[$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
// Neutral (cf) OR neutral (ct,cf,un).
[$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
[$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
[$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
// Neutral (un) OR neutral (ct,cf,un).
[$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
[$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
[$neutral_un, 'OR', $neutral_un, FALSE, NULL],
// Neutral (ct) OR forbidden (ct,cf,un).
[$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
[$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
[$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
// Neutral (cf) OR forbidden (ct,cf,un).
[$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
[$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
[$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
// Neutral (un) OR forbidden (ct,cf,un).
[$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
[$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
[$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
// Allowed (ct) AND allowed (ct,cf,un).
[$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
[$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
[$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
// Allowed (cf) AND allowed (ct,cf,un).
[$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
[$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
[$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
// Allowed (un) AND allowed (ct,cf,un).
[$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
[$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
[$allowed_un, 'AND', $allowed_un, FALSE, NULL],
// Allowed (ct) AND forbidden (ct,cf,un).
[$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
[$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
[$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
// Allowed (cf) AND forbidden (ct,cf,un).
[$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
[$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
[$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
// Allowed (un) AND forbidden (ct,cf,un).
[$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
[$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
[$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
// Allowed (ct) AND neutral (ct,cf,un).
[$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
[$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
[$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
// Allowed (cf) AND neutral (ct,cf,un).
[$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
[$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
[$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
// Allowed (un) AND neutral (ct,cf,un).
[$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
[$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
[$allowed_un, 'AND', $neutral_un, FALSE, NULL],
// Forbidden (ct) AND allowed (ct,cf,un).
[$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
[$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
[$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
// Forbidden (cf) AND allowed (ct,cf,un).
[$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
[$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
[$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
// Forbidden (un) AND allowed (ct,cf,un).
[$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
[$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
[$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
// Forbidden (ct) AND neutral (ct,cf,un).
[$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
[$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
[$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
// Forbidden (cf) AND neutral (ct,cf,un).
[$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
[$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
[$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
// Forbidden (un) AND neutral (ct,cf,un).
[$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
[$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
[$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
// Forbidden (ct) AND forbidden (ct,cf,un).
[$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
[$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
[$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
// Forbidden (cf) AND forbidden (ct,cf,un).
[$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
[$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
[$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
// Forbidden (un) AND forbidden (ct,cf,un).
[$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
[$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
[$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
// Neutral (ct) AND allowed (ct,cf,un).
[$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
[$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
[$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
// Neutral (cf) AND allowed (ct,cf,un).
[$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
[$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
[$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
// Neutral (un) AND allowed (ct,cf,un).
[$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
[$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
[$neutral_un, 'AND', $allowed_un, FALSE, NULL],
// Neutral (ct) AND neutral (ct,cf,un).
[$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
[$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
[$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
// Neutral (cf) AND neutral (ct,cf,un).
[$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
[$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
[$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
// Neutral (un) AND neutral (ct,cf,un).
[$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
[$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
[$neutral_un, 'AND', $neutral_un, FALSE, NULL],
// Neutral (ct) AND forbidden (ct,cf,un).
[$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
[$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
[$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
// Neutral (cf) AND forbidden (ct,cf,un).
[$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
[$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
[$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
// Neutral (un) AND forbidden (ct,cf,un).
[$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
[$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
[$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
];
}
catch
committed
Alex Pott
committed
/**
* @covers ::andIf
* @covers ::orIf
* @covers ::inheritCacheability
*
* @dataProvider andOrCacheabilityPropagationProvider
*/
Alex Pott
committed
public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
Alex Pott
committed
if ($op === 'OR') {
$result = $first->orIf($second);
}
elseif ($op === 'AND') {
Alex Pott
committed
$result = $first->andIf($second);
}
else {
throw new \LogicException('Invalid operator specified');
}
Alex Pott
committed
if ($implements_cacheable_dependency_interface) {
$this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
if ($result instanceof CacheableDependencyInterface) {
$this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
Alex Pott
committed
}
}
else {
Alex Pott
committed
$this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
Alex Pott
committed
}
}
catch
committed
/**
* @covers ::orIf
*
* Tests the special case of ORing non-forbidden access results that are both
* cacheable but have different cacheability metadata.
* This is only the case for non-forbidden access results; we still abort the
* ORing process as soon as a forbidden access result is encountered. This is
* tested in ::testOrIf().
*/
public function testOrIfCacheabilityMerging() {
$merge_both_directions = function (AccessResult $a, AccessResult $b) {
// A globally cacheable access result.
$a->setCacheMaxAge(3600);
// Another access result that is cacheable per permissions.
$b->setCacheMaxAge(86400)->cachePerPermissions();
$r1 = $a->orIf($b);
$this->assertTrue($r1->getCacheMaxAge() === 3600);
$this->assertSame(['user.permissions'], $r1->getCacheContexts());
$r2 = $b->orIf($a);
$this->assertTrue($r2->getCacheMaxAge() === 3600);
$this->assertSame(['user.permissions'], $r2->getCacheContexts());
};
// Merge either direction, get the same result.
$merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
$merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
$merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
$merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
}
/**
* Tests allowedIfHasPermissions().
*
* @covers ::allowedIfHasPermissions
*
* @dataProvider providerTestAllowedIfHasPermissions
Alex Pott
committed
*
* @param string[] $permissions
* The permissions to check for.
* @param string $conjunction
* The conjunction to use when checking for permission. 'AND' or 'OR'.
* @param \Drupal\Core\Access\AccessResult $expected_access
* The expected access check result.
*/
Alex Pott
committed
public function testAllowedIfHasPermissions($permissions, $conjunction, AccessResult $expected_access) {
$account = $this->getMock('\Drupal\Core\Session\AccountInterface');
$account->expects($this->any())
->method('hasPermission')
->willReturnMap([
['allowed', TRUE],
['denied', FALSE],
]);
Alex Pott
committed
if ($permissions) {
$expected_access->cachePerPermissions();
}
$access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
$this->assertEquals($expected_access, $access_result);
}
/**
* Provides data for the testAllowedIfHasPermissions() method.
*
* @return array
*/
public function providerTestAllowedIfHasPermissions() {
Alex Pott
committed
$access_result = AccessResult::allowedIf(FALSE);
$data[] = [[], 'AND', $access_result];
$data[] = [[], 'OR', $access_result];
$access_result = AccessResult::allowedIf(TRUE);
$data[] = [['allowed'], 'OR', $access_result];
$data[] = [['allowed'], 'AND', $access_result];
$access_result = AccessResult::allowedIf(FALSE);
$access_result->setReason("The 'denied' permission is required.");
$data[] = [['denied'], 'OR', $access_result];
$data[] = [['denied'], 'AND', $access_result];
$access_result = AccessResult::allowedIf(TRUE);
$data[] = [['allowed', 'denied'], 'OR', $access_result];
$data[] = [['denied', 'allowed'], 'OR', $access_result];
$access_result = AccessResult::allowedIf(TRUE);
$data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
$access_result = AccessResult::allowedIf(FALSE);
$access_result->setReason("The following permissions are required: 'allowed' AND 'denied'.");
$data[] = [['allowed', 'denied'], 'AND', $access_result];
return $data;
}
Alex Pott
committed
}
catch
committed
Alex Pott
committed
class UncacheableTestAccessResult implements AccessResultInterface {
/**
* The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
*
* @var string
*/
protected $value;
/**
* Constructs a new UncacheableTestAccessResult object.
*/
public function __construct($value) {
$this->value = $value;
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function isAllowed() {
return $this->value === 'ALLOWED';
}
/**
* {@inheritdoc}
*/
public function isForbidden() {
return $this->value === 'FORBIDDEN';