diff --git a/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php b/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php index 5f8744c14..2757444b1 100644 --- a/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php +++ b/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php @@ -86,8 +86,8 @@ protected function setUp() { 'first_name' => $this->getRandomGenerator()->word(16), 'last_name' => $this->getRandomGenerator()->word(16), ]); - $this->account->save(); $this->queueDeveloperResponse($this->account, Response::HTTP_CREATED); + $this->account->save(); } /** diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php index d61f1ff1a..1e4e04555 100644 --- a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php @@ -82,11 +82,7 @@ public function testEvent() { $team = $this->createTeam(); // Add team member. - $this->queueCompanyResponse($team->decorated()); - $this->queueDeveloperResponse($this->account); - $this->container->get('apigee_edge_teams.team_membership_manager')->addMembers($team->id(), [ - $this->account->getEmail(), - ]); + $this->addUserToTeam($team, $this->account); $this->assertLogsContains("Event apigee_edge_actions_entity_add_member:team was dispatched."); $this->assertLogsContains("Member {$this->account->first_name->value} was added to team {$team->getDisplayName()}."); diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php index 62387ec70..4ccbcedcc 100644 --- a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php @@ -80,15 +80,13 @@ public function testEvent() { // Create a new team. $team = $this->createTeam(); - // Add team member. - $this->queueCompanyResponse($team->decorated()); - $this->queueDeveloperResponse($this->account); $team_membership_manager = $this->container->get('apigee_edge_teams.team_membership_manager'); - $team_membership_manager->addMembers($team->id(), [ - $this->account->getEmail(), - ]); + + // Add team member. + $this->addUserToTeam($team, $this->account); // Remove team member. + $this->stack->queueMockResponse('no_content'); $team_membership_manager->removeMembers($team->id(), [ $this->account->getEmail(), ]); diff --git a/modules/apigee_edge_teams/src/Entity/ListBuilder/TeamListBuilder.php b/modules/apigee_edge_teams/src/Entity/ListBuilder/TeamListBuilder.php index 432cf1f84..a1a04e3ae 100644 --- a/modules/apigee_edge_teams/src/Entity/ListBuilder/TeamListBuilder.php +++ b/modules/apigee_edge_teams/src/Entity/ListBuilder/TeamListBuilder.php @@ -23,6 +23,7 @@ use Drupal\apigee_edge\Element\StatusPropertyElement; use Drupal\apigee_edge\Entity\ListBuilder\EdgeEntityListBuilder; use Drupal\apigee_edge_teams\Entity\TeamInterface; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Url; @@ -110,4 +111,35 @@ public function buildRow(EntityInterface $entity) { return $row + parent::buildRow($entity); } + /** + * {@inheritdoc} + */ + public function render() { + $build = parent::render(); + $account = $this->entityTypeManager->getStorage('user')->load(\Drupal::currentUser()->id()); + + $build = empty($build['table']) ? $build : $build['table']; + + $build['#cache']['keys'][] = 'team_list_per_user'; + + // Team lists vary for each user and their permissions. + // Note: Even though cache contexts will be optimized to only include the + // 'user' cache context, the element should be invalidated correctly when + // permissions change because the 'user.permissions' cache context defined + // cache tags for permission changes, which should have bubbled up for the + // element when it was optimized away. + // @see \Drupal\KernelTests\Core\Cache\CacheContextOptimizationTest + $build['#cache']['contexts'][] = 'user'; + $build['#cache']['contexts'][] = 'user.permissions'; + + $build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], $account->getCacheTags()); + + // Use cache expiration defined in configuration. + $build['#cache']['max-age'] = $this->configFactory + ->get('apigee_edge_teams.team_settings') + ->get('cache_expiration'); + + return $build; + } + } diff --git a/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php b/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php new file mode 100644 index 000000000..e357bfbcb --- /dev/null +++ b/modules/apigee_edge_teams/tests/src/Functional/TeamListBuilderTest.php @@ -0,0 +1,238 @@ +addOrganizationMatchedResponse(); + + $this->teamStorage = $this->entityTypeManager->getStorage('team'); + + $config_factory = \Drupal::configFactory(); + $config = $config_factory->getEditable('apigee_edge_teams.team_settings'); + $config->set('cache_expiration', 300); + $config->save(TRUE); + + // Create accounts: user 1, for members of two teams, and an extra one. + $this->account = $this->rootUser; + $this->aMemberAccount = $this->createNewAccount(); + $this->bMemberAccount = $this->createNewAccount(); + $this->cMemberAccount = $this->createNewAccount(); + + $this->customRole = $this->drupalCreateRole(['view any team']); + + // Create teams. + $this->teamA = $this->createTeam(); + $this->teamB = $this->createTeam(); + + // Add accounts to teams. + $this->addUserToTeam($this->teamA, $this->aMemberAccount); + $this->addUserToTeam($this->teamB, $this->bMemberAccount); + } + + /** + * {@inheritdoc} + */ + protected function tearDown() { + try { + $this->teamStorage->delete([$this->teamA, $this->teamB]); + $this->account->delete(); + $this->aMemberAccount->delete(); + $this->bMemberAccount->delete(); + $this->cMemberAccount->delete(); + } + catch (\Error $error) { + // Do nothing. + } + catch (\Exception $exception) { + // Do nothing. + } + } + + /** + * Tests team list cache. + */ + public function testTeamListCache() { + $companies = [ + $this->teamA->decorated(), + $this->teamB->decorated(), + ]; + + // aMemberAccount should only see teamA. + $this->drupalLogin($this->aMemberAccount); + $this->queueCompaniesResponse($companies); + $this->queueDeveloperResponse($this->aMemberAccount, 200, ['companies' => [$this->teamA->id()]]); + $this->drupalGet(Url::fromRoute('entity.team.collection')); + $assert = $this->assertSession(); + $assert->pageTextContains($this->teamA->label()); + $assert->pageTextNotContains($this->teamB->label()); + $this->drupalLogout(); + + // bMemberAccount should only see teamB. + $this->drupalLogin($this->bMemberAccount); + $this->queueCompaniesResponse($companies); + $this->queueDeveloperResponse($this->bMemberAccount, 200, ['companies' => [$this->teamB->id()]]); + $this->drupalGet(Url::fromUserInput('/teams')); + $assert = $this->assertSession(); + $assert->pageTextNotContains($this->teamA->label()); + $assert->pageTextContains($this->teamB->label()); + $this->drupalLogout(); + + // cMemberAccount should not see any teams. + $this->drupalLogin($this->cMemberAccount); + $this->queueCompaniesResponse($companies); + $this->queueDeveloperResponse($this->cMemberAccount); + $this->queueDeveloperResponse($this->cMemberAccount); + $this->drupalGet(Url::fromUserInput('/teams')); + $assert = $this->assertSession(); + $assert->pageTextNotContains($this->teamA->label()); + $assert->pageTextNotContains($this->teamB->label()); + + // Give cMemberAccount permission to view all teams. + $this->cMemberAccount->addRole($this->customRole); + $this->cMemberAccount->save(); + + // cMemberAccount should see both teams now. + $this->queueCompaniesResponse($companies); + $this->drupalGet(Url::fromUserInput('/teams')); + $assert = $this->assertSession(); + $assert->pageTextContains($this->teamA->label()); + $assert->pageTextContains($this->teamB->label()); + } + + /** + * Helper function to create a random user account. + * + * @return \Drupal\Core\Entity\EntityInterface + * The user account. + */ + protected function createNewAccount() { + $this->disableUserPresave(); + $account = $this->createAccount(); + + $fields = [ + 'email' => $account->getEmail(), + 'userName' => $account->getAccountName(), + 'firstName' => $this->getRandomGenerator()->word(8), + 'lastName' => $this->getRandomGenerator()->word(8), + ]; + + // Stack developer responses for "created" and "set active". + $this->queueDeveloperResponse($account, Response::HTTP_CREATED); + $this->stack->queueMockResponse('no_content'); + $developer = Developer::create($fields); + $developer->save(); + + return $account; + } + +} diff --git a/tests/modules/apigee_mock_api_client/tests/response-templates/companies.json.twig b/tests/modules/apigee_mock_api_client/tests/response-templates/companies.json.twig new file mode 100644 index 000000000..bee0379a3 --- /dev/null +++ b/tests/modules/apigee_mock_api_client/tests/response-templates/companies.json.twig @@ -0,0 +1,19 @@ +{# +/** + * @file + * Companies + * + * Usage: + * @code {% include 'companies.json.twig' %} @endcode + * + * Variables: + * - companies: an array of company objects. + */ +#} +{ + "company" : [ + {% for company in companies %} + {% include 'company.json.twig' with {'company': company} %}{{ loop.last ? '' : ',' }} + {% endfor %} + ] +} diff --git a/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php b/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php index d11097242..74640ac56 100644 --- a/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php +++ b/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php @@ -210,6 +210,21 @@ protected function queueCompanyResponse(Company $company, $response_code = NULL) $this->stack->queueMockResponse(['company' => $context]); } + /** + * Queues up a mock companies response. + * + * @param array $companies + * An array of company objects. + * @param string|null $response_code + * Add a response code to override the default. + */ + protected function queueCompaniesResponse(array $companies, $response_code = NULL) { + $context = empty($response_code) ? [] : ['status_code' => $response_code]; + $context['companies'] = $companies; + + $this->stack->queueMockResponse(['companies' => $context]); + } + /** * Queues up a mock developers in a company response. * @@ -238,6 +253,7 @@ protected function createDeveloperApp(): DeveloperAppInterface { static $appId; $appId = $appId ? $appId++ : 1; + $this->queueDeveloperResponse($this->account); /** @var \Drupal\apigee_edge\Entity\DeveloperAppInterface $entity */ $entity = DeveloperApp::create([ 'appId' => $this->integration_enabled ? NULL : $appId, @@ -267,12 +283,42 @@ protected function createTeam(): TeamInterface { 'displayName' => $this->randomGenerator->name(), ]); $this->queueCompanyResponse($team->decorated()); - $this->queueDeveloperResponse($this->account); + $this->stack->queueMockResponse('no_content'); $team->save(); return $team; } + /** + * Adds a user to a team. + * + * Adding a team to a user will add the team as long as the developer entity + * is loaded from cache. + * + * @param \Drupal\apigee_edge_teams\Entity\TeamInterface $team + * The team. + * @param \Drupal\user\UserInterface $user + * A drupal user. + * + * @return \Drupal\apigee_edge\Entity\DeveloperInterface + * The developer entity. + */ + public function addUserToTeam(TeamInterface $team, UserInterface $user) { + $this->queueDevsInCompanyResponse([ + ['email' => $user->getEmail()], + ]); + $this->queueCompanyResponse($team->decorated()); + + $teamMembershipManager = \Drupal::service('apigee_edge_teams.team_membership_manager'); + $teamMembershipManager->addMembers($team->id(), [$user->getEmail()]); + + $this->queueDeveloperResponse($user, 200, [ + 'companies' => [$team->id()], + ]); + + return $this->entityTypeManager->getStorage('developer')->load($user->getEmail()); + } + /** * Helper to add Edge entity response to stack. * diff --git a/tests/src/Kernel/Entity/ListBuilder/EntityListBuilderTest.php b/tests/src/Kernel/Entity/ListBuilder/EntityListBuilderTest.php index 050261b2c..af705e929 100644 --- a/tests/src/Kernel/Entity/ListBuilder/EntityListBuilderTest.php +++ b/tests/src/Kernel/Entity/ListBuilder/EntityListBuilderTest.php @@ -89,6 +89,8 @@ protected function setUp() { $this->apigeeTestHelperSetup(); + $this->addOrganizationMatchedResponse(); + $this->account = User::create([ 'mail' => $this->randomMachineName() . '@example.com', 'name' => $this->randomMachineName(), @@ -96,7 +98,6 @@ protected function setUp() { 'last_name' => $this->getRandomGenerator()->word(16), ]); $this->account->save(); - $this->queueDeveloperResponse($this->account, Response::HTTP_CREATED); } /** @@ -134,10 +135,7 @@ protected function tearDown() { public function testDisplaySettings() { /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ $entity_type_manager = $this->container->get('entity_type.manager'); - $this->queueDeveloperResponse($this->account); $this->app = $this->createDeveloperApp(); - - $this->queueDeveloperResponse($this->account); $this->stack->queueMockResponse([ 'get_developer_apps' => [ 'apps' => [$this->app]