View source
<?php
namespace Drupal\Tests\jsonapi\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Url;
use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\jsonapi\Traits\CommonCollectionFilterAccessTestPatternsTrait;
use Drupal\user\Entity\User;
use GuzzleHttp\RequestOptions;
class NodeTest extends ResourceTestBase {
use CommonCollectionFilterAccessTestPatternsTrait;
public static $modules = [
'node',
'path',
];
protected static $entityTypeId = 'node';
protected static $resourceTypeName = 'node--camelids';
protected static $resourceTypeIsVersionable = TRUE;
protected static $newRevisionsShouldBeAutomatic = TRUE;
protected $entity;
protected static $patchProtectedFieldNames = [
'revision_timestamp' => NULL,
'created' => "The 'administer nodes' permission is required.",
'changed' => NULL,
'promote' => "The 'administer nodes' permission is required.",
'sticky' => "The 'administer nodes' permission is required.",
'path' => "The following permissions are required: 'create url aliases' OR 'administer url aliases'.",
'revision_uid' => NULL,
];
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this
->grantPermissionsToTestedRole([
'access content',
]);
break;
case 'POST':
$this
->grantPermissionsToTestedRole([
'access content',
'create camelids content',
]);
break;
case 'PATCH':
$this
->grantPermissionsToTestedRole([
'access content',
'edit any camelids content',
]);
break;
case 'DELETE':
$this
->grantPermissionsToTestedRole([
'access content',
'delete any camelids content',
]);
break;
}
}
protected function setUpRevisionAuthorization($method) {
parent::setUpRevisionAuthorization($method);
$this
->grantPermissionsToTestedRole([
'view all revisions',
]);
}
protected function createEntity() {
if (!NodeType::load('camelids')) {
NodeType::create([
'name' => 'Camelids',
'type' => 'camelids',
])
->save();
}
$node = Node::create([
'type' => 'camelids',
]);
$node
->setTitle('Llama')
->setOwnerId($this->account
->id())
->setPublished()
->setCreatedTime(123456789)
->setChangedTime(123456789)
->setRevisionCreationTime(123456789)
->set('path', '/llama')
->save();
return $node;
}
protected function getExpectedDocument() {
$author = User::load($this->entity
->getOwnerId());
$base_url = Url::fromUri('base:/jsonapi/node/camelids/' . $this->entity
->uuid())
->setAbsolute();
$self_url = clone $base_url;
$version_identifier = 'id:' . $this->entity
->getRevisionId();
$self_url = $self_url
->setOption('query', [
'resourceVersion' => $version_identifier,
]);
$version_query_string = '?resourceVersion=' . urlencode($version_identifier);
return [
'jsonapi' => [
'meta' => [
'links' => [
'self' => [
'href' => 'http://jsonapi.org/format/1.0/',
],
],
],
'version' => '1.0',
],
'links' => [
'self' => [
'href' => $base_url
->toString(),
],
],
'data' => [
'id' => $this->entity
->uuid(),
'type' => 'node--camelids',
'links' => [
'self' => [
'href' => $self_url
->toString(),
],
],
'attributes' => [
'created' => '1973-11-29T21:33:09+00:00',
'changed' => (new \DateTime())
->setTimestamp($this->entity
->getChangedTime())
->setTimezone(new \DateTimeZone('UTC'))
->format(\DateTime::RFC3339),
'default_langcode' => TRUE,
'langcode' => 'en',
'path' => [
'alias' => '/llama',
'pid' => 1,
'langcode' => 'en',
],
'promote' => TRUE,
'revision_log' => NULL,
'revision_timestamp' => '1973-11-29T21:33:09+00:00',
'revision_translation_affected' => TRUE,
'status' => TRUE,
'sticky' => FALSE,
'title' => 'Llama',
'drupal_internal__nid' => 1,
'drupal_internal__vid' => 1,
],
'relationships' => [
'node_type' => [
'data' => [
'id' => NodeType::load('camelids')
->uuid(),
'type' => 'node_type--node_type',
],
'links' => [
'related' => [
'href' => $base_url
->toString() . '/node_type' . $version_query_string,
],
'self' => [
'href' => $base_url
->toString() . '/relationships/node_type' . $version_query_string,
],
],
],
'uid' => [
'data' => [
'id' => $author
->uuid(),
'type' => 'user--user',
],
'links' => [
'related' => [
'href' => $base_url
->toString() . '/uid' . $version_query_string,
],
'self' => [
'href' => $base_url
->toString() . '/relationships/uid' . $version_query_string,
],
],
],
'revision_uid' => [
'data' => [
'id' => $author
->uuid(),
'type' => 'user--user',
],
'links' => [
'related' => [
'href' => $base_url
->toString() . '/revision_uid' . $version_query_string,
],
'self' => [
'href' => $base_url
->toString() . '/relationships/revision_uid' . $version_query_string,
],
],
],
],
],
];
}
protected function getPostDocument() {
return [
'data' => [
'type' => 'node--camelids',
'attributes' => [
'title' => 'Dramallama',
],
],
];
}
protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET':
case 'POST':
case 'PATCH':
case 'DELETE':
return "The 'access content' permission is required.";
}
}
public function testPatchPath() {
$this
->setUpAuthorization('GET');
$this
->setUpAuthorization('PATCH');
$this
->config('jsonapi.settings')
->set('read_only', FALSE)
->save(TRUE);
$url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
'entity' => $this->entity
->uuid(),
]);
$response = $this
->request('GET', $url, $this
->getAuthenticationRequestOptions());
$normalization = Json::decode((string) $response
->getBody());
$normalization['data']['attributes']['path']['alias'] .= 's-rule-the-world';
$request_options = $this
->getAuthenticationRequestOptions();
$request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
$request_options[RequestOptions::BODY] = Json::encode($normalization);
$response = $this
->request('PATCH', $url, $request_options);
$this
->assertResourceErrorResponse(403, "The current user is not allowed to PATCH the selected field (path). The following permissions are required: 'create url aliases' OR 'administer url aliases'.", $url, $response, '/data/attributes/path');
$this
->grantPermissionsToTestedRole([
'create url aliases',
]);
$response = $this
->request('PATCH', $url, $request_options);
$this
->assertResourceResponse(200, FALSE, $response);
$updated_normalization = Json::decode((string) $response
->getBody());
$this
->assertSame($normalization['data']['attributes']['path']['alias'], $updated_normalization['data']['attributes']['path']['alias']);
}
public function testGetIndividual() {
parent::testGetIndividual();
$this->entity
->setUnpublished()
->save();
$url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
'entity' => $this->entity
->uuid(),
]);
$request_options = $this
->getAuthenticationRequestOptions();
$response = $this
->request('GET', $url, $request_options);
$expected_document = [
'jsonapi' => static::$jsonApiMember,
'errors' => [
[
'title' => 'Forbidden',
'status' => '403',
'detail' => 'The current user is not allowed to GET the selected resource.',
'links' => [
'info' => [
'href' => HttpExceptionNormalizer::getInfoUrl(403),
],
'via' => [
'href' => $url
->setAbsolute()
->toString(),
],
],
'source' => [
'pointer' => '/data',
],
],
],
];
$this
->assertResourceResponse(403, $expected_document, $response, [
'4xx-response',
'http_response',
'node:1',
], [
'url.query_args:resourceVersion',
'url.site',
'user.permissions',
], FALSE, 'MISS');
$this
->grantPermissionsToTestedRole([
'view own unpublished content',
]);
$response = $this
->request('GET', $url, $request_options);
$expected_cache_contexts = Cache::mergeContexts($this
->getExpectedCacheContexts(), [
'user',
]);
$expected_cache_contexts = array_diff($expected_cache_contexts, [
'user.permissions',
]);
$this
->assertResourceResponse(200, FALSE, $response, $this
->getExpectedCacheTags(), $expected_cache_contexts, FALSE, 'UNCACHEABLE');
}
protected static function getIncludePermissions() {
return [
'uid.node_type' => [
'administer users',
],
'uid.roles' => [
'administer permissions',
],
];
}
public function testPostNonExistingAuthor() {
$this
->setUpAuthorization('POST');
$this
->config('jsonapi.settings')
->set('read_only', FALSE)
->save(TRUE);
$this
->grantPermissionsToTestedRole([
'administer nodes',
]);
$random_uuid = \Drupal::service('uuid')
->generate();
$doc = $this
->getPostDocument();
$doc['data']['relationships']['uid']['data'] = [
'type' => 'user--user',
'id' => $random_uuid,
];
$url = Url::fromRoute(sprintf('jsonapi.%s.collection.post', static::$resourceTypeName));
$request_options = $this
->getAuthenticationRequestOptions();
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
$request_options[RequestOptions::BODY] = Json::encode($doc);
$response = $this
->request('POST', $url, $request_options);
$expected_document = [
'errors' => [
0 => [
'status' => '404',
'title' => 'Not Found',
'detail' => "The resource identified by `user--user:{$random_uuid}` (given as a relationship item) could not be found.",
'links' => [
'info' => [
'href' => HttpExceptionNormalizer::getInfoUrl(404),
],
'via' => [
'href' => $url
->setAbsolute()
->toString(),
],
],
],
],
'jsonapi' => static::$jsonApiMember,
];
$this
->assertResourceResponse(404, $expected_document, $response);
}
public function testCollectionFilterAccess() {
$label_field_name = 'title';
$this
->doTestCollectionFilterAccessForPublishableEntities($label_field_name, 'access content', 'bypass node access');
$collection_url = Url::fromRoute('jsonapi.entity_test--bar.collection');
$collection_filter_url = $collection_url
->setOption('query', [
"filter[spotlight.{$label_field_name}]" => $this->entity
->label(),
]);
$request_options = [];
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$request_options = NestedArray::mergeDeep($request_options, $this
->getAuthenticationRequestOptions());
$this
->revokePermissionsFromTestedRole([
'bypass node access',
]);
$response = $this
->request('GET', $collection_filter_url, $request_options);
$doc = Json::decode((string) $response
->getBody());
$this
->assertCount(0, $doc['data']);
$this
->grantPermissionsToTestedRole([
'view own unpublished content',
]);
$response = $this
->request('GET', $collection_filter_url, $request_options);
$doc = Json::decode((string) $response
->getBody());
$this
->assertCount(1, $doc['data']);
$this->entity
->setOwnerId(0)
->save();
$response = $this
->request('GET', $collection_filter_url, $request_options);
$doc = Json::decode((string) $response
->getBody());
$this
->assertCount(0, $doc['data']);
$this
->assertTrue($this->container
->get('module_installer')
->install([
'node_access_test',
], TRUE), 'Installed modules.');
node_access_rebuild();
$this
->rebuildAll();
$response = $this
->request('GET', $collection_filter_url, $request_options);
$this
->assertTrue(in_array('user.node_grants:view', explode(' ', $response
->getHeader('X-Drupal-Cache-Contexts')[0]), TRUE));
}
}