Chris@18: NULL, Chris@18: ]; Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: * Chris@18: * @var \Drupal\taxonomy\TermInterface Chris@18: */ Chris@18: protected $entity; Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function setUpAuthorization($method) { Chris@18: switch ($method) { Chris@18: case 'GET': Chris@18: $this->grantPermissionsToTestedRole(['access content']); Chris@18: break; Chris@18: Chris@18: case 'POST': Chris@18: $this->grantPermissionsToTestedRole(['create terms in camelids']); Chris@18: break; Chris@18: Chris@18: case 'PATCH': Chris@18: // Grant the 'create url aliases' permission to test the case when Chris@18: // the path field is accessible, see Chris@18: // \Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase Chris@18: // for a negative test. Chris@18: $this->grantPermissionsToTestedRole(['edit terms in camelids', 'create url aliases']); Chris@18: break; Chris@18: Chris@18: case 'DELETE': Chris@18: $this->grantPermissionsToTestedRole(['delete terms in camelids']); Chris@18: break; Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function createEntity() { Chris@18: $vocabulary = Vocabulary::load('camelids'); Chris@18: if (!$vocabulary) { Chris@18: // Create a "Camelids" vocabulary. Chris@18: $vocabulary = Vocabulary::create([ Chris@18: 'name' => 'Camelids', Chris@18: 'vid' => 'camelids', Chris@18: ]); Chris@18: $vocabulary->save(); Chris@18: } Chris@18: Chris@18: // Create a "Llama" taxonomy term. Chris@18: $term = Term::create(['vid' => $vocabulary->id()]) Chris@18: ->setName('Llama') Chris@18: ->setDescription("It is a little known fact that llamas cannot count higher than seven.") Chris@18: ->setChangedTime(123456789) Chris@18: ->set('path', '/llama'); Chris@18: $term->save(); Chris@18: Chris@18: return $term; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getExpectedDocument() { Chris@18: $self_url = Url::fromUri('base:/jsonapi/taxonomy_term/camelids/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl(); Chris@18: Chris@18: // We test with multiple parent terms, and combinations thereof. Chris@18: // @see ::createEntity() Chris@18: // @see ::testGetIndividual() Chris@18: // @see ::testGetIndividualTermWithParent() Chris@18: // @see ::providerTestGetIndividualTermWithParent() Chris@18: $parent_term_ids = []; Chris@18: for ($i = 0; $i < $this->entity->get('parent')->count(); $i++) { Chris@18: $parent_term_ids[$i] = (int) $this->entity->get('parent')[$i]->target_id; Chris@18: } Chris@18: Chris@18: $expected_parent_normalization = FALSE; Chris@18: switch ($parent_term_ids) { Chris@18: case [0]: Chris@18: $expected_parent_normalization = [ Chris@18: 'data' => [ Chris@18: [ Chris@18: 'id' => 'virtual', Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: 'meta' => [ Chris@18: 'links' => [ Chris@18: 'help' => [ Chris@18: 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#virtual', Chris@18: 'meta' => [ Chris@18: 'about' => "Usage and meaning of the 'virtual' resource identifier.", Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: 'links' => [ Chris@18: 'related' => ['href' => $self_url . '/parent'], Chris@18: 'self' => ['href' => $self_url . '/relationships/parent'], Chris@18: ], Chris@18: ]; Chris@18: break; Chris@18: Chris@18: case [2]: Chris@18: $expected_parent_normalization = [ Chris@18: 'data' => [ Chris@18: [ Chris@18: 'id' => Term::load(2)->uuid(), Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: ], Chris@18: ], Chris@18: 'links' => [ Chris@18: 'related' => ['href' => $self_url . '/parent'], Chris@18: 'self' => ['href' => $self_url . '/relationships/parent'], Chris@18: ], Chris@18: ]; Chris@18: break; Chris@18: Chris@18: case [0, 2]: Chris@18: $expected_parent_normalization = [ Chris@18: 'data' => [ Chris@18: [ Chris@18: 'id' => 'virtual', Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: 'meta' => [ Chris@18: 'links' => [ Chris@18: 'help' => [ Chris@18: 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#virtual', Chris@18: 'meta' => [ Chris@18: 'about' => "Usage and meaning of the 'virtual' resource identifier.", Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: [ Chris@18: 'id' => Term::load(2)->uuid(), Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: ], Chris@18: ], Chris@18: 'links' => [ Chris@18: 'related' => ['href' => $self_url . '/parent'], Chris@18: 'self' => ['href' => $self_url . '/relationships/parent'], Chris@18: ], Chris@18: ]; Chris@18: break; Chris@18: Chris@18: case [3, 2]: Chris@18: $expected_parent_normalization = [ Chris@18: 'data' => [ Chris@18: [ Chris@18: 'id' => Term::load(3)->uuid(), Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: ], Chris@18: [ Chris@18: 'id' => Term::load(2)->uuid(), Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: ], Chris@18: ], Chris@18: 'links' => [ Chris@18: 'related' => ['href' => $self_url . '/parent'], Chris@18: 'self' => ['href' => $self_url . '/relationships/parent'], Chris@18: ], Chris@18: ]; Chris@18: break; Chris@18: } Chris@18: Chris@18: return [ Chris@18: 'jsonapi' => [ Chris@18: 'meta' => [ Chris@18: 'links' => [ Chris@18: 'self' => ['href' => 'http://jsonapi.org/format/1.0/'], Chris@18: ], Chris@18: ], Chris@18: 'version' => '1.0', Chris@18: ], Chris@18: 'links' => [ Chris@18: 'self' => ['href' => $self_url], Chris@18: ], Chris@18: 'data' => [ Chris@18: 'id' => $this->entity->uuid(), Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: 'links' => [ Chris@18: 'self' => ['href' => $self_url], Chris@18: ], Chris@18: 'attributes' => [ Chris@18: 'changed' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339), Chris@18: 'default_langcode' => TRUE, Chris@18: 'description' => [ Chris@18: 'value' => 'It is a little known fact that llamas cannot count higher than seven.', Chris@18: 'format' => NULL, Chris@18: 'processed' => "

It is a little known fact that llamas cannot count higher than seven.

\n", Chris@18: ], Chris@18: 'langcode' => 'en', Chris@18: 'name' => 'Llama', Chris@18: 'path' => [ Chris@18: 'alias' => '/llama', Chris@18: 'pid' => 1, Chris@18: 'langcode' => 'en', Chris@18: ], Chris@18: 'weight' => 0, Chris@18: 'drupal_internal__tid' => 1, Chris@18: 'status' => TRUE, Chris@18: 'drupal_internal__revision_id' => 1, Chris@18: 'revision_created' => (new \DateTime())->setTimestamp($this->entity->getRevisionCreationTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339), Chris@18: 'revision_log_message' => NULL, Chris@18: // @todo Attempt to remove this in https://www.drupal.org/project/drupal/issues/2933518. Chris@18: 'revision_translation_affected' => TRUE, Chris@18: ], Chris@18: 'relationships' => [ Chris@18: 'parent' => $expected_parent_normalization, Chris@18: 'vid' => [ Chris@18: 'data' => [ Chris@18: 'id' => Vocabulary::load('camelids')->uuid(), Chris@18: 'type' => 'taxonomy_vocabulary--taxonomy_vocabulary', Chris@18: ], Chris@18: 'links' => [ Chris@18: 'related' => ['href' => $self_url . '/vid'], Chris@18: 'self' => ['href' => $self_url . '/relationships/vid'], Chris@18: ], Chris@18: ], Chris@18: 'revision_user' => [ Chris@18: 'data' => NULL, Chris@18: 'links' => [ Chris@18: 'related' => [ Chris@18: 'href' => $self_url . '/revision_user', Chris@18: ], Chris@18: 'self' => [ Chris@18: 'href' => $self_url . '/relationships/revision_user', Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getExpectedGetRelationshipDocumentData($relationship_field_name, EntityInterface $entity = NULL) { Chris@18: $data = parent::getExpectedGetRelationshipDocumentData($relationship_field_name, $entity); Chris@18: if ($relationship_field_name === 'parent') { Chris@18: $data = [ Chris@18: 0 => [ Chris@18: 'id' => 'virtual', Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: 'meta' => [ Chris@18: 'links' => [ Chris@18: 'help' => [ Chris@18: 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#virtual', Chris@18: 'meta' => [ Chris@18: 'about' => "Usage and meaning of the 'virtual' resource identifier.", Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: } Chris@18: return $data; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getPostDocument() { Chris@18: return [ Chris@18: 'data' => [ Chris@18: 'type' => 'taxonomy_term--camelids', Chris@18: 'attributes' => [ Chris@18: 'name' => 'Dramallama', Chris@18: 'description' => [ Chris@18: 'value' => 'Dramallamas are the coolest camelids.', Chris@18: 'format' => NULL, Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getExpectedUnauthorizedAccessMessage($method) { Chris@18: switch ($method) { Chris@18: case 'GET': Chris@18: return "The 'access content' permission is required and the taxonomy term must be published."; Chris@18: Chris@18: case 'POST': Chris@18: return "The following permissions are required: 'create terms in camelids' OR 'administer taxonomy'."; Chris@18: Chris@18: case 'PATCH': Chris@18: return "The following permissions are required: 'edit terms in camelids' OR 'administer taxonomy'."; Chris@18: Chris@18: case 'DELETE': Chris@18: return "The following permissions are required: 'delete terms in camelids' OR 'administer taxonomy'."; Chris@18: Chris@18: default: Chris@18: return parent::getExpectedUnauthorizedAccessMessage($method); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getExpectedUnauthorizedAccessCacheability() { Chris@18: $cacheability = parent::getExpectedUnauthorizedAccessCacheability(); Chris@18: $cacheability->addCacheableDependency($this->entity); Chris@18: return $cacheability; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests PATCHing a term's path. Chris@18: * Chris@18: * For a negative test, see the similar test coverage for Node. Chris@18: * Chris@18: * @see \Drupal\Tests\jsonapi\Functional\NodeTest::testPatchPath() Chris@18: * @see \Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase::testPatchPath() Chris@18: */ Chris@18: public function testPatchPath() { Chris@18: $this->setUpAuthorization('GET'); Chris@18: $this->setUpAuthorization('PATCH'); Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. Chris@18: $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); Chris@18: /* $url = $this->entity->toUrl('jsonapi'); */ Chris@18: $request_options = []; Chris@18: $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; Chris@18: $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; Chris@18: $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); Chris@18: Chris@18: // GET term's current normalization. Chris@18: $response = $this->request('GET', $url, $request_options); Chris@18: $normalization = Json::decode((string) $response->getBody()); Chris@18: Chris@18: // Change term's path alias. Chris@18: $normalization['data']['attributes']['path']['alias'] .= 's-rule-the-world'; Chris@18: Chris@18: // Create term PATCH request. Chris@18: $request_options[RequestOptions::BODY] = Json::encode($normalization); Chris@18: Chris@18: // PATCH request: 200. Chris@18: $response = $this->request('PATCH', $url, $request_options); Chris@18: $this->assertResourceResponse(200, FALSE, $response); Chris@18: $updated_normalization = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame($normalization['data']['attributes']['path']['alias'], $updated_normalization['data']['attributes']['path']['alias']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getExpectedCacheTags(array $sparse_fieldset = NULL) { Chris@18: $tags = parent::getExpectedCacheTags($sparse_fieldset); Chris@18: if ($sparse_fieldset === NULL || in_array('description', $sparse_fieldset)) { Chris@18: $tags = Cache::mergeTags($tags, ['config:filter.format.plain_text', 'config:filter.settings']); Chris@18: } Chris@18: return $tags; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: protected function getExpectedCacheContexts(array $sparse_fieldset = NULL) { Chris@18: $contexts = parent::getExpectedCacheContexts($sparse_fieldset); Chris@18: if ($sparse_fieldset === NULL || in_array('description', $sparse_fieldset)) { Chris@18: $contexts = Cache::mergeContexts($contexts, ['languages:language_interface', 'theme']); Chris@18: } Chris@18: return $contexts; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Tests GETting a term with a parent term other than the default (0). Chris@18: * Chris@18: * @see ::getExpectedNormalizedEntity() Chris@18: * Chris@18: * @dataProvider providerTestGetIndividualTermWithParent Chris@18: */ Chris@18: public function testGetIndividualTermWithParent(array $parent_term_ids) { Chris@18: // Create all possible parent terms. Chris@18: Term::create(['vid' => Vocabulary::load('camelids')->id()]) Chris@18: ->setName('Lamoids') Chris@18: ->save(); Chris@18: Term::create(['vid' => Vocabulary::load('camelids')->id()]) Chris@18: ->setName('Wimoids') Chris@18: ->save(); Chris@18: Chris@18: // Modify the entity under test to use the provided parent terms. Chris@18: $this->entity->set('parent', $parent_term_ids)->save(); Chris@18: Chris@18: // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. Chris@18: $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); Chris@18: /* $url = $this->entity->toUrl('jsonapi'); */ Chris@18: $request_options = []; Chris@18: $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; Chris@18: $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); Chris@18: $this->setUpAuthorization('GET'); Chris@18: $response = $this->request('GET', $url, $request_options); Chris@18: $this->assertSameDocument($this->getExpectedDocument(), Json::decode($response->getBody())); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Data provider for ::testGetIndividualTermWithParent(). Chris@18: */ Chris@18: public function providerTestGetIndividualTermWithParent() { Chris@18: return [ Chris@18: 'root parent: [0] (= no parent)' => [ Chris@18: [0], Chris@18: ], Chris@18: 'non-root parent: [2]' => [ Chris@18: [2], Chris@18: ], Chris@18: 'multiple parents: [0,2] (root + non-root parent)' => [ Chris@18: [0, 2], Chris@18: ], Chris@18: 'multiple parents: [3,2] (both non-root parents)' => [ Chris@18: [3, 2], Chris@18: ], Chris@18: ]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function testRelated() { Chris@18: $this->markTestSkipped('Remove this in https://www.drupal.org/project/jsonapi/issues/2940339'); Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function testCollectionFilterAccess() { Chris@18: $this->doTestCollectionFilterAccessBasedOnPermissions('name', 'access content'); Chris@18: } Chris@18: Chris@18: }