diff --git a/modules/apigee_edge_teams/src/Entity/Storage/TeamStorage.php b/modules/apigee_edge_teams/src/Entity/Storage/TeamStorage.php index 8f6144a32..dafee3e8f 100644 --- a/modules/apigee_edge_teams/src/Entity/Storage/TeamStorage.php +++ b/modules/apigee_edge_teams/src/Entity/Storage/TeamStorage.php @@ -38,6 +38,7 @@ use Drupal\apigee_edge_teams\Entity\Controller\TeamControllerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\State\StateInterface; /** * Entity storage implementation for teams. @@ -65,6 +66,13 @@ class TeamStorage extends AttributesAwareFieldableEdgeEntityStorageBase implemen */ private $logger; + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + /** * Constructs an TeamStorage instance. * @@ -84,8 +92,10 @@ class TeamStorage extends AttributesAwareFieldableEdgeEntityStorageBase implemen * Configuration factory. * @param \Psr\Log\LoggerInterface $logger * The logger. + * @param \Drupal\Core\State\StateInterface $state + * The state service. */ - public function __construct(EntityTypeInterface $entity_type, CacheBackendInterface $cache_backend, MemoryCacheInterface $memory_cache, TimeInterface $system_time, TeamControllerInterface $team_controller, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config, LoggerInterface $logger) { + public function __construct(EntityTypeInterface $entity_type, CacheBackendInterface $cache_backend, MemoryCacheInterface $memory_cache, TimeInterface $system_time, TeamControllerInterface $team_controller, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config, LoggerInterface $logger, StateInterface $state) { parent::__construct($entity_type, $cache_backend, $memory_cache, $system_time); $this->teamController = $team_controller; $config = $config->get('apigee_edge_teams.team_settings'); @@ -93,6 +103,7 @@ public function __construct(EntityTypeInterface $entity_type, CacheBackendInterf $this->cacheInsertChunkSize = $config->get('cache_insert_chunk_size') ?? static::DEFAULT_PERSISTENT_CACHE_INSERT_CHUNK_SIZE; $this->entityTypeManager = $entity_type_manager; $this->logger = $logger; + $this->state = $state; } /** @@ -107,7 +118,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('apigee_edge_teams.controller.team'), $container->get('entity_type.manager'), $container->get('config.factory'), - $container->get('logger.channel.apigee_edge_teams') + $container->get('logger.channel.apigee_edge_teams'), + $container->get('state') ); } @@ -211,4 +223,79 @@ protected function doDelete($entities) { } } + /** + * {@inheritdoc} + */ + protected function getFromPersistentCache(?array &$ids = NULL) { + + if ($this->cacheExpiration === 0 || !$this->entityType->isPersistentlyCacheable()) { + return []; + } + + if ($ids === NULL) { + // During tests, this state is set to TRUE (in parent::setUp()) to + // force a cache miss and take data from the Mock API. + // This prevents test isolation failures where + // stale data from a previous test could cause the current test to fail. + if ($this->state->get('apigee_teams_test_skip_cache', FALSE)) { + return []; + } + $all_ids_cid = 'all_ids:' . $this->entityTypeId; + // Try to load our "master ID list" from the cache. + if ($cache = $this->cacheBackend->get($all_ids_cid)) { + // We found the list! Set $ids to this list. + $ids = $cache->data; + } + // If we did NOT find the list, $ids remains NULL. The code + // will proceed as normal, hit the API, and our modified + // setPersistentCache() will create the list for next time. + } + + if (empty($ids)) { + return []; + } + + return parent::getFromPersistentCache($ids); + } + + /** + * {@inheritdoc} + */ + protected function setPersistentCache(array $entities) { + parent::setPersistentCache($entities); + + $entity_count = 0; + if (!empty($entities)) { + // Get all entity IDs. + $all_entity_ids = array_keys($entities); + $entity_count = count($all_entity_ids); + } + + // After all chunks are saved, save the master ID list. + // We only do this if we actually processed entities from the API. + if ($entity_count > 0) { + $all_ids_cid = 'all_ids:' . $this->entityTypeId; + // Use the main entity type tag so this item is cleared when + // the rest of the entity cache is cleared. + $all_ids_tags = [$this->entityTypeId . ':values']; + + $this->cacheBackend->set( + $all_ids_cid, + $all_entity_ids, + $this->getPersistentCacheExpiration(), + $all_ids_tags + ); + } + } + + /** + * {@inheritdoc} + */ + public function resetCache(?array $ids = NULL) { + + $this->cacheBackend->delete('all_ids:' . $this->entityTypeId); + + parent::resetCache($ids); + } + } diff --git a/modules/apigee_edge_teams/tests/src/Functional/AccessTest.php b/modules/apigee_edge_teams/tests/src/Functional/AccessTest.php index d4d7f3e19..50cc7ed5e 100644 --- a/modules/apigee_edge_teams/tests/src/Functional/AccessTest.php +++ b/modules/apigee_edge_teams/tests/src/Functional/AccessTest.php @@ -209,6 +209,12 @@ protected function setUp(): void { $this->teamMembershipManager = $this->container->get('apigee_edge_teams.team_membership_manager'); $this->teamPermissionHandler = $this->container->get('apigee_edge_teams.team_permissions'); $this->state = $this->container->get('state'); + // This state acts as a "kill switch." Our overridden + // `getFromPersistentCache()` method checks for this state. + // When it's TRUE, that method will *always* return [], forcing + // a cache miss and guaranteeing that every test gets fresh data + // from the source (the API) instead of the cache. + $this->state->set('apigee_teams_test_skip_cache', TRUE); $team_entity_type = $this->container->get('entity_type.manager')->getDefinition('team'); $team_app_entity_type = $this->container->get('entity_type.manager')->getDefinition('team_app'); diff --git a/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/AccessTest.php b/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/AccessTest.php index 79b3a070a..41c153a49 100644 --- a/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/AccessTest.php +++ b/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/AccessTest.php @@ -205,13 +205,20 @@ protected function setUp(): void { // Setting isApigeeX() to true for Apigee X org. TeamApp::$apigeex = TRUE; parent::setUp(); + $this->teamStorage = $this->container->get('entity_type.manager')->getStorage('team'); $this->teamAppStorage = $this->container->get('entity_type.manager')->getStorage('team_app'); $this->teamRoleStorage = $this->container->get('entity_type.manager')->getStorage('team_role'); $this->teamMemberRoleStorage = $this->container->get('entity_type.manager')->getStorage('team_member_role'); $this->teamMembershipManager = $this->container->get('apigee_edge_teams.team_membership_manager'); $this->teamPermissionHandler = $this->container->get('apigee_edge_teams.team_permissions'); + // This state acts as a "kill switch." Our overridden + // `getFromPersistentCache()` method checks for this state. + // When it's TRUE, that method will *always* return [], forcing + // a cache miss and guaranteeing that every test gets fresh data + // from the source (the API) instead of the cache. $this->state = $this->container->get('state'); + $this->state->set('apigee_teams_test_skip_cache', TRUE); $team_entity_type = $this->container->get('entity_type.manager')->getDefinition('team'); $team_app_entity_type = $this->container->get('entity_type.manager')->getDefinition('team_app'); diff --git a/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/TeamListBuilderTest.php b/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/TeamListBuilderTest.php index 76bd5c89f..098274959 100644 --- a/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/TeamListBuilderTest.php +++ b/modules/apigee_edge_teams/tests/src/Functional/ApigeeX/TeamListBuilderTest.php @@ -58,6 +58,13 @@ class TeamListBuilderTest extends ApigeeEdgeTeamsFunctionalTestBase { */ protected $teamStorage; + /** + * The state service. + * + * @var \Drupal\Core\State\State + */ + protected $state; + /** * The user 1 account. * @@ -113,6 +120,14 @@ class TeamListBuilderTest extends ApigeeEdgeTeamsFunctionalTestBase { protected function setUp(): void { parent::setUp(); + // This state acts as a "kill switch." Our overridden + // `getFromPersistentCache()` method checks for this state. + // When it's TRUE, that method will *always* return [], forcing + // a cache miss and guaranteeing that every test gets fresh data + // from the source (the API) instead of the cache. + $this->state = $this->container->get('state'); + $this->state->set('apigee_teams_test_skip_cache', TRUE); + $this->storeToken(); $this->addApigeexOrganizationMatchedResponse(); diff --git a/modules/apigee_edge_teams/tests/src/Functional/TeamInvitationsTest.php b/modules/apigee_edge_teams/tests/src/Functional/TeamInvitationsTest.php index 1a2996cb1..a40b32532 100644 --- a/modules/apigee_edge_teams/tests/src/Functional/TeamInvitationsTest.php +++ b/modules/apigee_edge_teams/tests/src/Functional/TeamInvitationsTest.php @@ -77,12 +77,27 @@ class TeamInvitationsTest extends ApigeeEdgeTeamsFunctionalTestBase { */ protected $teamB; + /** + * The state service. + * + * @var \Drupal\Core\State\State + */ + protected $state; + /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); + // This state acts as a "kill switch." Our overridden + // `getFromPersistentCache()` method checks for this state. + // When it's TRUE, that method will *always* return [], forcing + // a cache miss and guaranteeing that every test gets fresh data + // from the source (the API) instead of the cache. + $this->state = $this->container->get('state'); + $this->state->set('apigee_teams_test_skip_cache', TRUE); + $this->addOrganizationMatchedResponse(); $this->teamA = $this->createTeam(); diff --git a/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php b/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php index cf02b7530..c90fa3297 100644 --- a/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php +++ b/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php @@ -107,12 +107,27 @@ class TeamListBuilderTest extends ApigeeEdgeTeamsFunctionalTestBase { */ protected $customRole; + /** + * The state service. + * + * @var \Drupal\Core\State\State + */ + protected $state; + /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); + // This state acts as a "kill switch." Our overridden + // `getFromPersistentCache()` method checks for this state. + // When it's TRUE, that method will *always* return [], forcing + // a cache miss and guaranteeing that every test gets fresh data + // from the source (the API) instead of the cache. + $this->state = $this->container->get('state'); + $this->state->set('apigee_teams_test_skip_cache', TRUE); + $this->addOrganizationMatchedResponse(); $this->teamStorage = $this->entityTypeManager->getStorage('team');