Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/jsonapi/tests/src/Functional/NodeTest.php @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Tests\jsonapi\Functional; | |
4 | |
5 use Drupal\Component\Serialization\Json; | |
6 use Drupal\Component\Utility\NestedArray; | |
7 use Drupal\Core\Cache\Cache; | |
8 use Drupal\Core\Url; | |
9 use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer; | |
10 use Drupal\node\Entity\Node; | |
11 use Drupal\node\Entity\NodeType; | |
12 use Drupal\Tests\jsonapi\Traits\CommonCollectionFilterAccessTestPatternsTrait; | |
13 use Drupal\user\Entity\User; | |
14 use GuzzleHttp\RequestOptions; | |
15 | |
16 /** | |
17 * JSON:API integration test for the "Node" content entity type. | |
18 * | |
19 * @group jsonapi | |
20 */ | |
21 class NodeTest extends ResourceTestBase { | |
22 | |
23 use CommonCollectionFilterAccessTestPatternsTrait; | |
24 | |
25 /** | |
26 * {@inheritdoc} | |
27 */ | |
28 public static $modules = ['node', 'path']; | |
29 | |
30 /** | |
31 * {@inheritdoc} | |
32 */ | |
33 protected static $entityTypeId = 'node'; | |
34 | |
35 /** | |
36 * {@inheritdoc} | |
37 */ | |
38 protected static $resourceTypeName = 'node--camelids'; | |
39 | |
40 /** | |
41 * {@inheritdoc} | |
42 */ | |
43 protected static $resourceTypeIsVersionable = TRUE; | |
44 | |
45 /** | |
46 * {@inheritdoc} | |
47 */ | |
48 protected static $newRevisionsShouldBeAutomatic = TRUE; | |
49 | |
50 /** | |
51 * {@inheritdoc} | |
52 * | |
53 * @var \Drupal\node\NodeInterface | |
54 */ | |
55 protected $entity; | |
56 | |
57 /** | |
58 * {@inheritdoc} | |
59 */ | |
60 protected static $patchProtectedFieldNames = [ | |
61 'revision_timestamp' => NULL, | |
62 'created' => "The 'administer nodes' permission is required.", | |
63 'changed' => NULL, | |
64 'promote' => "The 'administer nodes' permission is required.", | |
65 'sticky' => "The 'administer nodes' permission is required.", | |
66 'path' => "The following permissions are required: 'create url aliases' OR 'administer url aliases'.", | |
67 'revision_uid' => NULL, | |
68 ]; | |
69 | |
70 /** | |
71 * {@inheritdoc} | |
72 */ | |
73 protected function setUpAuthorization($method) { | |
74 switch ($method) { | |
75 case 'GET': | |
76 $this->grantPermissionsToTestedRole(['access content']); | |
77 break; | |
78 | |
79 case 'POST': | |
80 $this->grantPermissionsToTestedRole(['access content', 'create camelids content']); | |
81 break; | |
82 | |
83 case 'PATCH': | |
84 // Do not grant the 'create url aliases' permission to test the case | |
85 // when the path field is protected/not accessible, see | |
86 // \Drupal\Tests\rest\Functional\EntityResource\Term\TermResourceTestBase | |
87 // for a positive test. | |
88 $this->grantPermissionsToTestedRole(['access content', 'edit any camelids content']); | |
89 break; | |
90 | |
91 case 'DELETE': | |
92 $this->grantPermissionsToTestedRole(['access content', 'delete any camelids content']); | |
93 break; | |
94 } | |
95 } | |
96 | |
97 /** | |
98 * {@inheritdoc} | |
99 */ | |
100 protected function setUpRevisionAuthorization($method) { | |
101 parent::setUpRevisionAuthorization($method); | |
102 $this->grantPermissionsToTestedRole(['view all revisions']); | |
103 } | |
104 | |
105 /** | |
106 * {@inheritdoc} | |
107 */ | |
108 protected function createEntity() { | |
109 if (!NodeType::load('camelids')) { | |
110 // Create a "Camelids" node type. | |
111 NodeType::create([ | |
112 'name' => 'Camelids', | |
113 'type' => 'camelids', | |
114 ])->save(); | |
115 } | |
116 | |
117 // Create a "Llama" node. | |
118 $node = Node::create(['type' => 'camelids']); | |
119 $node->setTitle('Llama') | |
120 ->setOwnerId($this->account->id()) | |
121 ->setPublished() | |
122 ->setCreatedTime(123456789) | |
123 ->setChangedTime(123456789) | |
124 ->setRevisionCreationTime(123456789) | |
125 ->set('path', '/llama') | |
126 ->save(); | |
127 | |
128 return $node; | |
129 } | |
130 | |
131 /** | |
132 * {@inheritdoc} | |
133 */ | |
134 protected function getExpectedDocument() { | |
135 $author = User::load($this->entity->getOwnerId()); | |
136 $base_url = Url::fromUri('base:/jsonapi/node/camelids/' . $this->entity->uuid())->setAbsolute(); | |
137 $self_url = clone $base_url; | |
138 $version_identifier = 'id:' . $this->entity->getRevisionId(); | |
139 $self_url = $self_url->setOption('query', ['resourceVersion' => $version_identifier]); | |
140 $version_query_string = '?resourceVersion=' . urlencode($version_identifier); | |
141 return [ | |
142 'jsonapi' => [ | |
143 'meta' => [ | |
144 'links' => [ | |
145 'self' => ['href' => 'http://jsonapi.org/format/1.0/'], | |
146 ], | |
147 ], | |
148 'version' => '1.0', | |
149 ], | |
150 'links' => [ | |
151 'self' => ['href' => $base_url->toString()], | |
152 ], | |
153 'data' => [ | |
154 'id' => $this->entity->uuid(), | |
155 'type' => 'node--camelids', | |
156 'links' => [ | |
157 'self' => ['href' => $self_url->toString()], | |
158 ], | |
159 'attributes' => [ | |
160 'created' => '1973-11-29T21:33:09+00:00', | |
161 'changed' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339), | |
162 'default_langcode' => TRUE, | |
163 'langcode' => 'en', | |
164 'path' => [ | |
165 'alias' => '/llama', | |
166 'pid' => 1, | |
167 'langcode' => 'en', | |
168 ], | |
169 'promote' => TRUE, | |
170 'revision_log' => NULL, | |
171 'revision_timestamp' => '1973-11-29T21:33:09+00:00', | |
172 // @todo Attempt to remove this in https://www.drupal.org/project/drupal/issues/2933518. | |
173 'revision_translation_affected' => TRUE, | |
174 'status' => TRUE, | |
175 'sticky' => FALSE, | |
176 'title' => 'Llama', | |
177 'drupal_internal__nid' => 1, | |
178 'drupal_internal__vid' => 1, | |
179 ], | |
180 'relationships' => [ | |
181 'node_type' => [ | |
182 'data' => [ | |
183 'id' => NodeType::load('camelids')->uuid(), | |
184 'type' => 'node_type--node_type', | |
185 ], | |
186 'links' => [ | |
187 'related' => [ | |
188 'href' => $base_url->toString() . '/node_type' . $version_query_string, | |
189 ], | |
190 'self' => [ | |
191 'href' => $base_url->toString() . '/relationships/node_type' . $version_query_string, | |
192 ], | |
193 ], | |
194 ], | |
195 'uid' => [ | |
196 'data' => [ | |
197 'id' => $author->uuid(), | |
198 'type' => 'user--user', | |
199 ], | |
200 'links' => [ | |
201 'related' => [ | |
202 'href' => $base_url->toString() . '/uid' . $version_query_string, | |
203 ], | |
204 'self' => [ | |
205 'href' => $base_url->toString() . '/relationships/uid' . $version_query_string, | |
206 ], | |
207 ], | |
208 ], | |
209 'revision_uid' => [ | |
210 'data' => [ | |
211 'id' => $author->uuid(), | |
212 'type' => 'user--user', | |
213 ], | |
214 'links' => [ | |
215 'related' => [ | |
216 'href' => $base_url->toString() . '/revision_uid' . $version_query_string, | |
217 ], | |
218 'self' => [ | |
219 'href' => $base_url->toString() . '/relationships/revision_uid' . $version_query_string, | |
220 ], | |
221 ], | |
222 ], | |
223 ], | |
224 ], | |
225 ]; | |
226 } | |
227 | |
228 /** | |
229 * {@inheritdoc} | |
230 */ | |
231 protected function getPostDocument() { | |
232 return [ | |
233 'data' => [ | |
234 'type' => 'node--camelids', | |
235 'attributes' => [ | |
236 'title' => 'Dramallama', | |
237 ], | |
238 ], | |
239 ]; | |
240 } | |
241 | |
242 /** | |
243 * {@inheritdoc} | |
244 */ | |
245 protected function getExpectedUnauthorizedAccessMessage($method) { | |
246 switch ($method) { | |
247 case 'GET': | |
248 case 'POST': | |
249 case 'PATCH': | |
250 case 'DELETE': | |
251 return "The 'access content' permission is required."; | |
252 } | |
253 } | |
254 | |
255 /** | |
256 * Tests PATCHing a node's path with and without 'create url aliases'. | |
257 * | |
258 * For a positive test, see the similar test coverage for Term. | |
259 * | |
260 * @see \Drupal\Tests\jsonapi\Functional\TermTest::testPatchPath() | |
261 * @see \Drupal\Tests\rest\Functional\EntityResource\Term\TermResourceTestBase::testPatchPath() | |
262 */ | |
263 public function testPatchPath() { | |
264 $this->setUpAuthorization('GET'); | |
265 $this->setUpAuthorization('PATCH'); | |
266 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); | |
267 | |
268 // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. | |
269 $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); | |
270 /* $url = $this->entity->toUrl('jsonapi'); */ | |
271 | |
272 // GET node's current normalization. | |
273 $response = $this->request('GET', $url, $this->getAuthenticationRequestOptions()); | |
274 $normalization = Json::decode((string) $response->getBody()); | |
275 | |
276 // Change node's path alias. | |
277 $normalization['data']['attributes']['path']['alias'] .= 's-rule-the-world'; | |
278 | |
279 // Create node PATCH request. | |
280 $request_options = $this->getAuthenticationRequestOptions(); | |
281 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; | |
282 $request_options[RequestOptions::BODY] = Json::encode($normalization); | |
283 | |
284 // PATCH request: 403 when creating URL aliases unauthorized. | |
285 $response = $this->request('PATCH', $url, $request_options); | |
286 $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'); | |
287 | |
288 // Grant permission to create URL aliases. | |
289 $this->grantPermissionsToTestedRole(['create url aliases']); | |
290 | |
291 // Repeat PATCH request: 200. | |
292 $response = $this->request('PATCH', $url, $request_options); | |
293 $this->assertResourceResponse(200, FALSE, $response); | |
294 $updated_normalization = Json::decode((string) $response->getBody()); | |
295 $this->assertSame($normalization['data']['attributes']['path']['alias'], $updated_normalization['data']['attributes']['path']['alias']); | |
296 } | |
297 | |
298 /** | |
299 * {@inheritdoc} | |
300 */ | |
301 public function testGetIndividual() { | |
302 parent::testGetIndividual(); | |
303 | |
304 // Unpublish node. | |
305 $this->entity->setUnpublished()->save(); | |
306 | |
307 // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. | |
308 $url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]); | |
309 /* $url = $this->entity->toUrl('jsonapi'); */ | |
310 $request_options = $this->getAuthenticationRequestOptions(); | |
311 | |
312 // 403 when accessing own unpublished node. | |
313 $response = $this->request('GET', $url, $request_options); | |
314 // @todo Remove $expected + assertResourceResponse() in favor of the commented line below once https://www.drupal.org/project/jsonapi/issues/2943176 lands. | |
315 $expected_document = [ | |
316 'jsonapi' => static::$jsonApiMember, | |
317 'errors' => [ | |
318 [ | |
319 'title' => 'Forbidden', | |
320 'status' => '403', | |
321 'detail' => 'The current user is not allowed to GET the selected resource.', | |
322 'links' => [ | |
323 'info' => ['href' => HttpExceptionNormalizer::getInfoUrl(403)], | |
324 'via' => ['href' => $url->setAbsolute()->toString()], | |
325 ], | |
326 'source' => [ | |
327 'pointer' => '/data', | |
328 ], | |
329 ], | |
330 ], | |
331 ]; | |
332 $this->assertResourceResponse( | |
333 403, | |
334 $expected_document, | |
335 $response, | |
336 ['4xx-response', 'http_response', 'node:1'], | |
337 ['url.query_args:resourceVersion', 'url.site', 'user.permissions'], | |
338 FALSE, | |
339 'MISS' | |
340 ); | |
341 /* $this->assertResourceErrorResponse(403, 'The current user is not allowed to GET the selected resource.', $response, '/data'); */ | |
342 | |
343 // 200 after granting permission. | |
344 $this->grantPermissionsToTestedRole(['view own unpublished content']); | |
345 $response = $this->request('GET', $url, $request_options); | |
346 // The response varies by 'user', causing the 'user.permissions' cache | |
347 // context to be optimized away. | |
348 $expected_cache_contexts = Cache::mergeContexts($this->getExpectedCacheContexts(), ['user']); | |
349 $expected_cache_contexts = array_diff($expected_cache_contexts, ['user.permissions']); | |
350 $this->assertResourceResponse(200, FALSE, $response, $this->getExpectedCacheTags(), $expected_cache_contexts, FALSE, 'UNCACHEABLE'); | |
351 } | |
352 | |
353 /** | |
354 * {@inheritdoc} | |
355 */ | |
356 protected static function getIncludePermissions() { | |
357 return [ | |
358 'uid.node_type' => ['administer users'], | |
359 'uid.roles' => ['administer permissions'], | |
360 ]; | |
361 } | |
362 | |
363 /** | |
364 * Creating relationships to missing resources should be 404 per JSON:API 1.1. | |
365 * | |
366 * @see https://github.com/json-api/json-api/issues/1033 | |
367 */ | |
368 public function testPostNonExistingAuthor() { | |
369 $this->setUpAuthorization('POST'); | |
370 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); | |
371 $this->grantPermissionsToTestedRole(['administer nodes']); | |
372 | |
373 $random_uuid = \Drupal::service('uuid')->generate(); | |
374 $doc = $this->getPostDocument(); | |
375 $doc['data']['relationships']['uid']['data'] = [ | |
376 'type' => 'user--user', | |
377 'id' => $random_uuid, | |
378 ]; | |
379 | |
380 // Create node POST request. | |
381 $url = Url::fromRoute(sprintf('jsonapi.%s.collection.post', static::$resourceTypeName)); | |
382 $request_options = $this->getAuthenticationRequestOptions(); | |
383 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; | |
384 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json'; | |
385 $request_options[RequestOptions::BODY] = Json::encode($doc); | |
386 | |
387 // POST request: 404 when adding relationships to non-existing resources. | |
388 $response = $this->request('POST', $url, $request_options); | |
389 $expected_document = [ | |
390 'errors' => [ | |
391 0 => [ | |
392 'status' => '404', | |
393 'title' => 'Not Found', | |
394 'detail' => "The resource identified by `user--user:$random_uuid` (given as a relationship item) could not be found.", | |
395 'links' => [ | |
396 'info' => ['href' => HttpExceptionNormalizer::getInfoUrl(404)], | |
397 'via' => ['href' => $url->setAbsolute()->toString()], | |
398 ], | |
399 ], | |
400 ], | |
401 'jsonapi' => static::$jsonApiMember, | |
402 ]; | |
403 $this->assertResourceResponse(404, $expected_document, $response); | |
404 } | |
405 | |
406 /** | |
407 * {@inheritdoc} | |
408 */ | |
409 public function testCollectionFilterAccess() { | |
410 $label_field_name = 'title'; | |
411 $this->doTestCollectionFilterAccessForPublishableEntities($label_field_name, 'access content', 'bypass node access'); | |
412 | |
413 $collection_url = Url::fromRoute('jsonapi.entity_test--bar.collection'); | |
414 $collection_filter_url = $collection_url->setOption('query', ["filter[spotlight.$label_field_name]" => $this->entity->label()]); | |
415 $request_options = []; | |
416 $request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json'; | |
417 $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions()); | |
418 | |
419 $this->revokePermissionsFromTestedRole(['bypass node access']); | |
420 | |
421 // 0 results because the node is unpublished. | |
422 $response = $this->request('GET', $collection_filter_url, $request_options); | |
423 $doc = Json::decode((string) $response->getBody()); | |
424 $this->assertCount(0, $doc['data']); | |
425 | |
426 $this->grantPermissionsToTestedRole(['view own unpublished content']); | |
427 | |
428 // 1 result because the current user is the owner of the unpublished node. | |
429 $response = $this->request('GET', $collection_filter_url, $request_options); | |
430 $doc = Json::decode((string) $response->getBody()); | |
431 $this->assertCount(1, $doc['data']); | |
432 | |
433 $this->entity->setOwnerId(0)->save(); | |
434 | |
435 // 0 results because the current user is no longer the owner. | |
436 $response = $this->request('GET', $collection_filter_url, $request_options); | |
437 $doc = Json::decode((string) $response->getBody()); | |
438 $this->assertCount(0, $doc['data']); | |
439 | |
440 // Assert bubbling of cacheability from query alter hook. | |
441 $this->assertTrue($this->container->get('module_installer')->install(['node_access_test'], TRUE), 'Installed modules.'); | |
442 node_access_rebuild(); | |
443 $this->rebuildAll(); | |
444 $response = $this->request('GET', $collection_filter_url, $request_options); | |
445 $this->assertTrue(in_array('user.node_grants:view', explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0]), TRUE)); | |
446 } | |
447 | |
448 } |