From a82f80f3ead45399722e329873eaa04e9ff9a81d Mon Sep 17 00:00:00 2001 From: Jared Whiklo Date: Tue, 30 Apr 2019 10:09:03 -0500 Subject: [PATCH] Display Fedora URI in Drupal (#124) * Make a pseudo field we can use * coder * Fix config and coder for root level * Default to empty array * Add tests and clean up some deprecations * Form validation of Gemini URI before bundle selection. * Coder * Add functional test for Islandora Settings --- composer.json | 2 +- config/schema/islandora.schema.yml | 12 ++ islandora.module | 56 ++++++++ islandora.services.yml | 7 + src/Form/IslandoraSettingsForm.php | 117 ++++++++++++++++- src/GeminiClientFactory.php | 47 +++++++ src/GeminiLookup.php | 101 +++++++++++++++ .../IslandoraFunctionalTestBase.php | 8 +- .../Functional/IslandoraSettingsFormTest.php | 61 +++++++++ tests/src/Kernel/EventGeneratorTest.php | 2 +- tests/src/Kernel/GeminiClientFactoryTest.php | 82 ++++++++++++ tests/src/Kernel/GeminiLookupTest.php | 121 ++++++++++++++++++ tests/src/Kernel/JwtEventSubscriberTest.php | 2 +- 13 files changed, 609 insertions(+), 9 deletions(-) create mode 100644 src/GeminiClientFactory.php create mode 100644 src/GeminiLookup.php create mode 100644 tests/src/Functional/IslandoraSettingsFormTest.php create mode 100644 tests/src/Kernel/GeminiClientFactoryTest.php create mode 100644 tests/src/Kernel/GeminiLookupTest.php diff --git a/composer.json b/composer.json index c4d6666f..0429cf69 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "drupal/migrate_source_csv" : "^2.1", "drupal/token" : "^1.3", "drupal/flysystem" : "^1.0", - "islandora/chullo" : "^0.2.0" + "islandora/crayfish-commons": "^0.0" }, "require-dev": { "phpunit/phpunit": "^6", diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index d786a9a5..a2ca48d3 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -11,6 +11,18 @@ islandora.settings: broadcast_queue: type: string label: 'Queue that handles distributing messages amongst multiple recipients' + jwt_expiry: + type: string + label: 'How long JWTs should last before expiring.' + gemini_url: + type: uri + label: 'Url to Gemini microservice' + gemini_pseudo_bundles: + type: sequence + label: 'List of node, media and taxonomy terms that should include the linked Fedora URI' + sequence: + type: string + action.configuration.emit_node_event: type: mapping diff --git a/islandora.module b/islandora.module index aacf9029..34bbc5fb 100644 --- a/islandora.module +++ b/islandora.module @@ -14,8 +14,12 @@ * @author Diego Pino Navarro https://github.com/diegopino */ +use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\islandora\Form\IslandoraSettingsForm; +use Drupal\islandora\GeminiLookup; use Drupal\node\NodeInterface; use Drupal\media\MediaInterface; use Drupal\file\FileInterface; @@ -344,3 +348,55 @@ function islandora_form_block_form_alter(&$form, FormStateInterface $form_state, unset($form['visibility']['node_has_term']); unset($form['visibility']['media_uses_filesystem']); } + +/** + * Implements hook_entity_extra_field_info(). + */ +function islandora_entity_extra_field_info() { + $config_factory = \Drupal::service('config.factory')->get(IslandoraSettingsForm::CONFIG_NAME); + $extra_field = []; + + $pseudo_bundles = $config_factory->get(IslandoraSettingsForm::GEMINI_PSEUDO) ? $config_factory->get(IslandoraSettingsForm::GEMINI_PSEUDO) : []; + + foreach ($pseudo_bundles as $key) { + list($bundle, $content_entity) = explode(":", $key); + $extra_field[$content_entity][$bundle]['display']['field_gemini_uri'] = [ + 'label' => t('Fedora URI'), + 'description' => t('The URI to the persistent'), + 'weight' => 100, + 'visible' => TRUE, + ]; + } + return $extra_field; +} + +/** + * Implements hook_entity_view(). + */ +function islandora_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { + if ($view_mode == 'full') { + if ($display->getComponent('field_gemini_uri')) { + $gemini = \Drupal::service('islandora.gemini.lookup'); + if ($gemini instanceof GeminiLookup) { + $fedora_uri = $gemini->lookup($entity); + if (!is_null($fedora_uri)) { + $build['field_gemini_uri'] = [ + '#type' => 'container', + '#attributes' => [ + 'id' => 'field-gemini-uri', + ], + 'internal_label' => [ + '#type' => 'item', + '#title' => t('Fedora URI'), + 'internal_uri' => [ + '#type' => 'link', + '#title' => t("@url", ['@url' => $fedora_uri]), + '#url' => Url::fromUri($fedora_uri), + ], + ], + ]; + } + } + } + } +} diff --git a/islandora.services.yml b/islandora.services.yml index 6f6b85c7..1a34ed37 100644 --- a/islandora.services.yml +++ b/islandora.services.yml @@ -51,3 +51,10 @@ services: islandora.utils: class: Drupal\islandora\IslandoraUtils arguments: ['@entity_type.manager', '@entity_field.manager', '@entity.query', '@context.manager', '@flysystem_factory'] + islandora.gemini.client: + class: Islandora\Crayfish\Commons\Client\GeminiClient + factory: ['Drupal\islandora\GeminiClientFactory', create] + arguments: ['@config.factory', '@logger.channel.islandora'] + islandora.gemini.lookup: + class: Drupal\islandora\GeminiLookup + arguments: ['@islandora.gemini.client', '@jwt.authentication.jwt', '@logger.channel.islandora'] diff --git a/src/Form/IslandoraSettingsForm.php b/src/Form/IslandoraSettingsForm.php index 730c1d25..6ec80165 100644 --- a/src/Form/IslandoraSettingsForm.php +++ b/src/Form/IslandoraSettingsForm.php @@ -2,11 +2,17 @@ namespace Drupal\islandora\Form; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityTypeBundleInfo; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use GuzzleHttp\Exception\ConnectException; +use Islandora\Crayfish\Commons\Client\GeminiClient; use Stomp\Client; use Stomp\Exception\StompException; use Stomp\StatefulStomp; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Config form for Islandora settings. @@ -16,6 +22,38 @@ class IslandoraSettingsForm extends ConfigFormBase { const CONFIG_NAME = 'islandora.settings'; const BROKER_URL = 'broker_url'; const JWT_EXPIRY = 'jwt_expiry'; + const GEMINI_URL = 'gemini_url'; + const GEMINI_PSEUDO = 'gemini_pseudo_bundles'; + + /** + * To list the available bundle types. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfo + */ + private $entityTypeBundleInfo; + + /** + * Constructs a \Drupal\system\ConfigFormBase object. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. + * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info + * The EntityTypeBundleInfo service. + */ + public function __construct(ConfigFactoryInterface $config_factory, EntityTypeBundleInfo $entity_type_bundle_info) { + $this->setConfigFactory($config_factory); + $this->entityTypeBundleInfo = $entity_type_bundle_info; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('entity_type.bundle.info') + ); + } /** * {@inheritdoc} @@ -51,6 +89,37 @@ class IslandoraSettingsForm extends ConfigFormBase { '#default_value' => $config->get(self::JWT_EXPIRY) ? $config->get(self::JWT_EXPIRY) : '+2 hour', ]; + $form[self::GEMINI_URL] = [ + '#type' => 'textfield', + '#title' => $this->t('Gemini URL'), + '#default_value' => $config->get(self::GEMINI_URL) ? $config->get(self::GEMINI_URL) : '', + ]; + + $selected_bundles = $config->get(self::GEMINI_PSEUDO) ? $config->get(self::GEMINI_PSEUDO) : []; + + $options = []; + foreach (['node', 'media', 'taxonomy_term'] as $content_entity) { + $bundles = $this->entityTypeBundleInfo->getBundleInfo($content_entity); + foreach ($bundles as $bundle => $bundle_properties) { + $options["{$bundle}:{$content_entity}"] = + $this->t('@label (@type)', [ + '@label' => $bundle_properties['label'], + '@type' => $content_entity, + ]); + } + } + + $form['bundle_container'] = [ + '#type' => 'details', + '#title' => $this->t('Bundles with Gemini URI Pseudo field'), + '#description' => $this->t('The selected bundles can display the pseudo-field showing the Gemini linked URI. Configured in the field display.'), + '#open' => TRUE, + self::GEMINI_PSEUDO => [ + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $selected_bundles, + ], + ]; return parent::buildForm($form, $form_state); } @@ -88,22 +157,66 @@ class IslandoraSettingsForm extends ConfigFormBase { $form_state->setErrorByName( self::JWT_EXPIRY, $this->t( - '"@exipry" is not a valid time or interval expression.', + '"@expiry" is not a valid time or interval expression.', ['@expiry' => $expiry] ) ); } + + // Needed for the elseif below. + $pseudo_types = array_filter($form_state->getValue(self::GEMINI_PSEUDO)); + + // Validate Gemini URL by validating the URL. + $geminiUrlValue = trim($form_state->getValue(self::GEMINI_URL)); + if (!empty($geminiUrlValue)) { + try { + $geminiUrl = Url::fromUri($geminiUrlValue); + $client = GeminiClient::create($geminiUrlValue, $this->logger('islandora')); + $client->findByUri('http://example.org'); + } + // Uri is invalid. + catch (\InvalidArgumentException $e) { + $form_state->setErrorByName( + self::GEMINI_URL, + $this->t( + 'Cannot parse URL @url', + ['@url' => $geminiUrlValue] + ) + ); + } + // Uri is not available. + catch (ConnectException $e) { + $form_state->setErrorByName( + self::GEMINI_URL, + $this->t( + 'Cannot connect to URL @url', + ['@url' => $geminiUrlValue] + ) + ); + } + } + elseif (count($pseudo_types) > 0) { + $form_state->setErrorByName( + self::GEMINI_URL, + $this->t('Must enter Gemini URL before selecting bundles to display a pseudo field on.') + ); + } + } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $config = \Drupal::service('config.factory')->getEditable(self::CONFIG_NAME); + $config = $this->configFactory->getEditable(self::CONFIG_NAME); + + $pseudo_types = array_filter($form_state->getValue(self::GEMINI_PSEUDO)); $config ->set(self::BROKER_URL, $form_state->getValue(self::BROKER_URL)) ->set(self::JWT_EXPIRY, $form_state->getValue(self::JWT_EXPIRY)) + ->set(self::GEMINI_URL, $form_state->getValue(self::GEMINI_URL)) + ->set(self::GEMINI_PSEUDO, $pseudo_types) ->save(); parent::submitForm($form, $form_state); diff --git a/src/GeminiClientFactory.php b/src/GeminiClientFactory.php new file mode 100644 index 00000000..5a8ccd5b --- /dev/null +++ b/src/GeminiClientFactory.php @@ -0,0 +1,47 @@ +get(IslandoraSettingsForm::CONFIG_NAME); + $geminiUrl = $settings->get(IslandoraSettingsForm::GEMINI_URL); + + // Only attempt if there is one. + if (!empty($geminiUrl)) { + return GeminiClient::create($geminiUrl, $logger); + } + else { + $logger->notice("Attempted to create Gemini client without a Gemini URL defined."); + throw new PreconditionFailedHttpException("Unable to instantiate GeminiClient, missing Gemini URI in Islandora setting."); + } + } + +} diff --git a/src/GeminiLookup.php b/src/GeminiLookup.php new file mode 100644 index 00000000..b10471b0 --- /dev/null +++ b/src/GeminiLookup.php @@ -0,0 +1,101 @@ +geminiClient = $client; + $this->jwtProvider = $jwt_auth; + $this->logger = $logger; + } + + /** + * Static creator. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. + * + * @return \Drupal\islandora\GeminiLookup + * A GeminiLookup service. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('islandora.gemini_client'), + $container->get('jwt.authentication.jwt'), + $container->get('logger.channel.islandora') + ); + } + + /** + * Lookup this entity's URI in the Gemini db and return the other URI. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to look for. + * + * @return string|null + * Return the URI or null + * + * @throws \Drupal\Core\Entity\EntityMalformedException + * If the entity cannot be converted to a URL. + */ + public function lookup(EntityInterface $entity) { + if ($entity->id() != NULL) { + $drupal_uri = $entity->toUrl()->setAbsolute()->toString(); + $drupal_uri .= '?_format=jsonld'; + $token = "Bearer " . $this->jwtProvider->generateToken(); + $linked_uri = $this->geminiClient->findByUri($drupal_uri, $token); + if (!is_null($linked_uri)) { + if (is_array($linked_uri)) { + $linked_uri = reset($linked_uri); + } + return $linked_uri; + } + } + // Return null if we weren't in a saved entity or we didn't find a uri. + return NULL; + } + +} diff --git a/tests/src/Functional/IslandoraFunctionalTestBase.php b/tests/src/Functional/IslandoraFunctionalTestBase.php index a689db78..e1d6e91f 100644 --- a/tests/src/Functional/IslandoraFunctionalTestBase.php +++ b/tests/src/Functional/IslandoraFunctionalTestBase.php @@ -5,11 +5,11 @@ namespace Drupal\Tests\islandora\Functional; use Drupal\Core\Config\FileStorage; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\link\LinkItemInterface; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; +use Drupal\Tests\media\Traits\MediaTypeCreationTrait; use Drupal\Tests\TestFileCreationTrait; -use Drupal\Tests\media\Functional\MediaFunctionalTestCreateMediaTypeTrait; /** * Base class for Functional tests. @@ -18,7 +18,7 @@ class IslandoraFunctionalTestBase extends BrowserTestBase { use EntityReferenceTestTrait; use TestFileCreationTrait; - use MediaFunctionalTestCreateMediaTypeTrait; + use MediaTypeCreationTrait; protected static $modules = ['context_ui', 'field_ui', 'islandora']; @@ -157,7 +157,7 @@ EOD; $this->createEntityReferenceField('node', 'test_type', 'field_tags', 'Tags', 'taxonomy_term', 'default', [], 2); // Create a media type. - $this->testMediaType = $this->createMediaType(['bundle' => 'test_media_type'], 'file'); + $this->testMediaType = $this->createMediaType('file', ['id' => 'test_media_type']); $this->testMediaType->save(); $this->createEntityReferenceField('media', $this->testMediaType->id(), 'field_media_of', 'Media Of', 'node', 'default', [], 2); $this->createEntityReferenceField('media', $this->testMediaType->id(), 'field_tags', 'Tags', 'taxonomy_term', 'default', [], 2); diff --git a/tests/src/Functional/IslandoraSettingsFormTest.php b/tests/src/Functional/IslandoraSettingsFormTest.php new file mode 100644 index 00000000..f33a10a9 --- /dev/null +++ b/tests/src/Functional/IslandoraSettingsFormTest.php @@ -0,0 +1,61 @@ +drupalCreateUser([ + 'bypass node access', + 'administer site configuration', + 'view media', + 'create media', + 'update media', + ]); + $this->drupalLogin($account); + } + + /** + * Test Gemini URL validation. + */ + public function testGeminiUri() { + $this->drupalGet('/admin/config/islandora/core'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains("Gemini URL"); + $this->assertSession()->fieldValueEquals('edit-gemini-url', ''); + + $this->drupalPostForm('admin/config/islandora/core', ['edit-gemini-url' => 'not_a_url'], t('Save configuration')); + $this->assertSession()->pageTextContainsOnce("Cannot parse URL not_a_url"); + + $this->drupalPostForm('admin/config/islandora/core', ['edit-gemini-url' => 'http://whaturl.bob'], t('Save configuration')); + $this->assertSession()->pageTextContainsOnce("Cannot connect to URL http://whaturl.bob"); + } + + /** + * Test block on choosing Pseudo field bundles without a Gemini URL. + */ + public function testPseudoFieldBundles() { + $this->drupalGet('/admin/config/islandora/core'); + $this->assertSession()->statusCodeEquals(200); + + $this->drupalPostForm('admin/config/islandora/core', [ + 'gemini_pseudo_bundles[test_type:node]' => TRUE, + ], t('Save configuration')); + $this->assertSession()->pageTextContainsOnce("Must enter Gemini URL before selecting bundles to display a pseudo field on."); + + } + +} diff --git a/tests/src/Kernel/EventGeneratorTest.php b/tests/src/Kernel/EventGeneratorTest.php index b33d4cd5..9f0bed06 100644 --- a/tests/src/Kernel/EventGeneratorTest.php +++ b/tests/src/Kernel/EventGeneratorTest.php @@ -5,7 +5,7 @@ namespace Drupal\Tests\islandora\Kernel; use Drupal\islandora\EventGenerator\EventGenerator; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; -use Drupal\simpletest\UserCreationTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; /** * Tests the EventGenerator default implementation. diff --git a/tests/src/Kernel/GeminiClientFactoryTest.php b/tests/src/Kernel/GeminiClientFactoryTest.php new file mode 100644 index 00000000..3259c221 --- /dev/null +++ b/tests/src/Kernel/GeminiClientFactoryTest.php @@ -0,0 +1,82 @@ +prophesize(LoggerInterface::class); + $prophecy->notice(Argument::any()); + $this->logger = $prophecy->reveal(); + } + + /** + * @covers ::create + * @expectedException \Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException + */ + public function testNoUrlBlank() { + $prophecy = $this->prophesize(ImmutableConfig::class); + $prophecy->get(Argument::any())->willReturn(''); + $immutConfig = $prophecy->reveal(); + + $prophecy = $this->prophesize(ConfigFactoryInterface::class); + $prophecy->get(Argument::any())->willReturn($immutConfig); + $configFactory = $prophecy->reveal(); + + GeminiClientFactory::create($configFactory, $this->logger); + } + + /** + * @covers ::create + * @expectedException \Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException + */ + public function testNoUrlNull() { + $prophecy = $this->prophesize(ImmutableConfig::class); + $prophecy->get(Argument::any())->willReturn(NULL); + $immutConfig = $prophecy->reveal(); + + $prophecy = $this->prophesize(ConfigFactoryInterface::class); + $prophecy->get(Argument::any())->willReturn($immutConfig); + $configFactory = $prophecy->reveal(); + + GeminiClientFactory::create($configFactory, $this->logger); + } + + /** + * @covers ::create + * @throws \Exception + */ + public function testUrl() { + $prophecy = $this->prophesize(ImmutableConfig::class); + $prophecy->get(Argument::any())->willReturn('http://localhost:8000/gemini'); + $immutConfig = $prophecy->reveal(); + + $prophecy = $this->prophesize(ConfigFactoryInterface::class); + $prophecy->get(Argument::any())->willReturn($immutConfig); + $configFactory = $prophecy->reveal(); + + $this->assertInstanceOf(GeminiClient::class, GeminiClientFactory::create($configFactory, $this->logger)); + } + +} diff --git a/tests/src/Kernel/GeminiLookupTest.php b/tests/src/Kernel/GeminiLookupTest.php new file mode 100644 index 00000000..9c96b6c2 --- /dev/null +++ b/tests/src/Kernel/GeminiLookupTest.php @@ -0,0 +1,121 @@ +prophesize(JwtAuth::class); + $prophecy->generateToken()->willReturn("islandora"); + $this->jwtAuth = $prophecy->reveal(); + + $prophecy = $this->prophesize(LoggerInterface::class); + $this->logger = $prophecy->reveal(); + + $prophecy = $this->prophesize(GeminiClient::class); + $prophecy->findByUri(Argument::any(), Argument::any())->willReturn(NULL); + $this->geminiClient = $prophecy->reveal(); + } + + /** + * @covers ::lookup + * @covers ::__construct + * @throws \Drupal\Core\Entity\EntityMalformedException + */ + public function testEntityNotSaved() { + $prophecy = $this->prophesize(EntityInterface::class); + $prophecy->id()->willReturn(NULL); + $entity = $prophecy->reveal(); + $this->geminiLookup = new GeminiLookup( + $this->geminiClient, + $this->jwtAuth, + $this->logger + ); + $this->assertEquals(NULL, $this->geminiLookup->lookup($entity)); + } + + /** + * @covers ::lookup + * @covers ::__construct + * @throws \Drupal\Core\Entity\EntityMalformedException + */ + public function testEntityNotFound() { + $prop1 = $this->prophesize(Url::class); + $prop1->toString()->willReturn("http://localhost:8000/node/456"); + + $prop2 = $this->prophesize(Url::class); + $prop2->setAbsolute()->willReturn($prop1->reveal()); + $url = $prop2->reveal(); + + $prophecy = $this->prophesize(EntityInterface::class); + $prophecy->id()->willReturn(456); + $prophecy->toUrl()->willReturn($url); + $entity = $prophecy->reveal(); + + $this->geminiLookup = new GeminiLookup( + $this->geminiClient, + $this->jwtAuth, + $this->logger + ); + + $this->assertEquals(NULL, $this->geminiLookup->lookup($entity)); + } + + /** + * @covers ::lookup + * @covers ::__construct + * @throws \Drupal\Core\Entity\EntityMalformedException + */ + public function testEntityFound() { + $prop1 = $this->prophesize(Url::class); + $prop1->toString()->willReturn("http://localhost:8000/node/456"); + + $prop2 = $this->prophesize(Url::class); + $prop2->setAbsolute()->willReturn($prop1->reveal()); + $url = $prop2->reveal(); + + $prophecy = $this->prophesize(EntityInterface::class); + $prophecy->id()->willReturn(456); + $prophecy->toUrl()->willReturn($url); + $entity = $prophecy->reveal(); + + $prophecy = $this->prophesize(GeminiClient::class); + $prophecy->findByUri(Argument::any(), Argument::any())->willReturn(["http://fedora:8080/some/uri"]); + $this->geminiClient = $prophecy->reveal(); + + $this->geminiLookup = new GeminiLookup( + $this->geminiClient, + $this->jwtAuth, + $this->logger + ); + + $this->assertEquals("http://fedora:8080/some/uri", $this->geminiLookup->lookup($entity)); + } + +} diff --git a/tests/src/Kernel/JwtEventSubscriberTest.php b/tests/src/Kernel/JwtEventSubscriberTest.php index 8fd9e2be..f97eab9f 100644 --- a/tests/src/Kernel/JwtEventSubscriberTest.php +++ b/tests/src/Kernel/JwtEventSubscriberTest.php @@ -7,7 +7,7 @@ use Drupal\jwt\Authentication\Event\JwtAuthValidEvent; use Drupal\jwt\Authentication\Event\JwtAuthValidateEvent; use Drupal\jwt\JsonWebToken\JsonWebToken; use Drupal\jwt\JsonWebToken\JsonWebTokenInterface; -use Drupal\simpletest\UserCreationTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\core\Entity\EntityStorageInterface; use Drupal\islandora\EventSubscriber\JwtEventSubscriber;