comparison core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.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\comment\Entity\Comment;
6 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
7 use Drupal\comment\Tests\CommentTestTrait;
8 use Drupal\Component\Serialization\Json;
9 use Drupal\Core\Field\FieldStorageDefinitionInterface;
10 use Drupal\Core\Url;
11 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
12 use Drupal\entity_test\Entity\EntityTestMapField;
13 use Drupal\field\Entity\FieldConfig;
14 use Drupal\field\Entity\FieldStorageConfig;
15 use Drupal\node\Entity\Node;
16 use Drupal\node\Entity\NodeType;
17 use Drupal\shortcut\Entity\Shortcut;
18 use Drupal\taxonomy\Entity\Term;
19 use Drupal\taxonomy\Entity\Vocabulary;
20 use Drupal\user\Entity\Role;
21 use Drupal\user\Entity\User;
22 use Drupal\user\RoleInterface;
23 use GuzzleHttp\RequestOptions;
24
25 /**
26 * JSON:API regression tests.
27 *
28 * @group jsonapi
29 * @group legacy
30 *
31 * @internal
32 */
33 class JsonApiRegressionTest extends JsonApiFunctionalTestBase {
34
35 use CommentTestTrait;
36
37 /**
38 * {@inheritdoc}
39 */
40 public static $modules = [
41 'basic_auth',
42 ];
43
44 /**
45 * Ensure filtering on relationships works with bundle-specific target types.
46 *
47 * @see https://www.drupal.org/project/jsonapi/issues/2953207
48 */
49 public function testBundleSpecificTargetEntityTypeFromIssue2953207() {
50 // Set up data model.
51 $this->assertTrue($this->container->get('module_installer')->install(['comment'], TRUE), 'Installed modules.');
52 $this->addDefaultCommentField('taxonomy_term', 'tags', 'comment', CommentItemInterface::OPEN, 'tcomment');
53 $this->rebuildAll();
54
55 // Create data.
56 Term::create([
57 'name' => 'foobar',
58 'vid' => 'tags',
59 ])->save();
60 Comment::create([
61 'subject' => 'Llama',
62 'entity_id' => 1,
63 'entity_type' => 'taxonomy_term',
64 'field_name' => 'comment',
65 ])->save();
66
67 // Test.
68 $user = $this->drupalCreateUser([
69 'access comments',
70 ]);
71 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/comment/tcomment?include=entity_id&filter[entity_id.name]=foobar'), [
72 RequestOptions::AUTH => [
73 $user->getUsername(),
74 $user->pass_raw,
75 ],
76 ]);
77 $this->assertSame(200, $response->getStatusCode());
78 }
79
80 /**
81 * Ensure deep nested include works on multi target entity type field.
82 *
83 * @see https://www.drupal.org/project/jsonapi/issues/2973681
84 */
85 public function testDeepNestedIncludeMultiTargetEntityTypeFieldFromIssue2973681() {
86 // Set up data model.
87 $this->assertTrue($this->container->get('module_installer')->install(['comment'], TRUE), 'Installed modules.');
88 $this->addDefaultCommentField('node', 'article');
89 $this->addDefaultCommentField('taxonomy_term', 'tags', 'comment', CommentItemInterface::OPEN, 'tcomment');
90 $this->drupalCreateContentType(['type' => 'page']);
91 $this->createEntityReferenceField(
92 'node',
93 'page',
94 'field_comment',
95 NULL,
96 'comment',
97 'default',
98 [
99 'target_bundles' => [
100 'comment' => 'comment',
101 'tcomment' => 'tcomment',
102 ],
103 ],
104 FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
105 );
106 $this->rebuildAll();
107
108 // Create data.
109 $node = Node::create([
110 'title' => 'test article',
111 'type' => 'article',
112 ]);
113 $node->save();
114 $comment = Comment::create([
115 'subject' => 'Llama',
116 'entity_id' => 1,
117 'entity_type' => 'node',
118 'field_name' => 'comment',
119 ]);
120 $comment->save();
121 $page = Node::create([
122 'title' => 'test node',
123 'type' => 'page',
124 'field_comment' => [
125 'entity' => $comment,
126 ],
127 ]);
128 $page->save();
129
130 // Test.
131 $user = $this->drupalCreateUser([
132 'access content',
133 'access comments',
134 ]);
135 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/page?include=field_comment,field_comment.entity_id,field_comment.entity_id.uid'), [
136 RequestOptions::AUTH => [
137 $user->getUsername(),
138 $user->pass_raw,
139 ],
140 ]);
141 $this->assertSame(200, $response->getStatusCode());
142 }
143
144 /**
145 * Ensure POST and PATCH works for bundle-less relationship routes.
146 *
147 * @see https://www.drupal.org/project/jsonapi/issues/2976371
148 */
149 public function testBundlelessRelationshipMutationFromIssue2973681() {
150 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
151
152 // Set up data model.
153 $this->drupalCreateContentType(['type' => 'page']);
154 $this->createEntityReferenceField(
155 'node',
156 'page',
157 'field_test',
158 NULL,
159 'user',
160 'default',
161 [
162 'target_bundles' => NULL,
163 ],
164 FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
165 );
166 $this->rebuildAll();
167
168 // Create data.
169 $node = Node::create([
170 'title' => 'test article',
171 'type' => 'page',
172 ]);
173 $node->save();
174 $target = $this->createUser();
175
176 // Test.
177 $user = $this->drupalCreateUser(['bypass node access']);
178 $url = Url::fromRoute('jsonapi.node--page.field_test.relationship.post', ['entity' => $node->uuid()]);
179 $request_options = [
180 RequestOptions::HEADERS => [
181 'Content-Type' => 'application/vnd.api+json',
182 'Accept' => 'application/vnd.api+json',
183 ],
184 RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw],
185 RequestOptions::JSON => [
186 'data' => [
187 ['type' => 'user--user', 'id' => $target->uuid()],
188 ],
189 ],
190 ];
191 $response = $this->request('POST', $url, $request_options);
192 $this->assertSame(204, $response->getStatusCode(), (string) $response->getBody());
193 }
194
195 /**
196 * Ensures GETting terms works when multiple vocabularies exist.
197 *
198 * @see https://www.drupal.org/project/jsonapi/issues/2977879
199 */
200 public function testGetTermWhenMultipleVocabulariesExistFromIssue2977879() {
201 // Set up data model.
202 $this->assertTrue($this->container->get('module_installer')->install(['taxonomy'], TRUE), 'Installed modules.');
203 Vocabulary::create([
204 'name' => 'one',
205 'vid' => 'one',
206 ])->save();
207 Vocabulary::create([
208 'name' => 'two',
209 'vid' => 'two',
210 ])->save();
211 $this->rebuildAll();
212
213 // Create data.
214 Term::create(['vid' => 'one'])
215 ->setName('Test')
216 ->save();
217
218 // Test.
219 $user = $this->drupalCreateUser([
220 'access content',
221 ]);
222 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/taxonomy_term/one'), [
223 RequestOptions::AUTH => [
224 $user->getUsername(),
225 $user->pass_raw,
226 ],
227 ]);
228 $this->assertSame(200, $response->getStatusCode());
229 }
230
231 /**
232 * Cannot PATCH an entity with dangling references in an ER field.
233 *
234 * @see https://www.drupal.org/project/jsonapi/issues/2968972
235 */
236 public function testDanglingReferencesInAnEntityReferenceFieldFromIssue2968972() {
237 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
238
239 // Set up data model.
240 $this->drupalCreateContentType(['type' => 'journal_issue']);
241 $this->drupalCreateContentType(['type' => 'journal_article']);
242 $this->createEntityReferenceField(
243 'node',
244 'journal_article',
245 'field_issue',
246 NULL,
247 'node',
248 'default',
249 [
250 'target_bundles' => [
251 'journal_issue' => 'journal_issue',
252 ],
253 ],
254 FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
255 );
256 $this->rebuildAll();
257
258 // Create data.
259 $issue_node = Node::create([
260 'title' => 'Test Journal Issue',
261 'type' => 'journal_issue',
262 ]);
263 $issue_node->save();
264
265 $user = $this->drupalCreateUser([
266 'access content',
267 'edit own journal_article content',
268 ]);
269 $article_node = Node::create([
270 'title' => 'Test Journal Article',
271 'type' => 'journal_article',
272 'field_issue' => [
273 'target_id' => $issue_node->id(),
274 ],
275 ]);
276 $article_node->setOwner($user);
277 $article_node->save();
278
279 // Test.
280 $url = Url::fromUri(sprintf('internal:/jsonapi/node/journal_article/%s', $article_node->uuid()));
281 $request_options = [
282 RequestOptions::HEADERS => [
283 'Content-Type' => 'application/vnd.api+json',
284 'Accept' => 'application/vnd.api+json',
285 ],
286 RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw],
287 RequestOptions::JSON => [
288 'data' => [
289 'type' => 'node--journal_article',
290 'id' => $article_node->uuid(),
291 'attributes' => [
292 'title' => 'My New Article Title',
293 ],
294 ],
295 ],
296 ];
297 $issue_node->delete();
298 $response = $this->request('PATCH', $url, $request_options);
299 $this->assertSame(200, $response->getStatusCode(), (string) $response->getBody());
300 }
301
302 /**
303 * Ensures GETting node collection + hook_node_grants() implementations works.
304 *
305 * @see https://www.drupal.org/project/jsonapi/issues/2984964
306 */
307 public function testGetNodeCollectionWithHookNodeGrantsImplementationsFromIssue2984964() {
308 // Set up data model.
309 $this->assertTrue($this->container->get('module_installer')->install(['node_access_test'], TRUE), 'Installed modules.');
310 node_access_rebuild();
311 $this->rebuildAll();
312
313 // Create data.
314 Node::create([
315 'title' => 'test article',
316 'type' => 'article',
317 ])->save();
318
319 // Test.
320 $user = $this->drupalCreateUser([
321 'access content',
322 ]);
323 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/article'), [
324 RequestOptions::AUTH => [
325 $user->getUsername(),
326 $user->pass_raw,
327 ],
328 ]);
329 $this->assertSame(200, $response->getStatusCode());
330 $this->assertTrue(in_array('user.node_grants:view', explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0]), TRUE));
331 }
332
333 /**
334 * Cannot GET an entity with dangling references in an ER field.
335 *
336 * @see https://www.drupal.org/project/jsonapi/issues/2984647
337 */
338 public function testDanglingReferencesInAnEntityReferenceFieldFromIssue2984647() {
339 // Set up data model.
340 $this->drupalCreateContentType(['type' => 'journal_issue']);
341 $this->drupalCreateContentType(['type' => 'journal_conference']);
342 $this->drupalCreateContentType(['type' => 'journal_article']);
343 $this->createEntityReferenceField(
344 'node',
345 'journal_article',
346 'field_issue',
347 NULL,
348 'node',
349 'default',
350 [
351 'target_bundles' => [
352 'journal_issue' => 'journal_issue',
353 ],
354 ],
355 FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
356 );
357 $this->createEntityReferenceField(
358 'node',
359 'journal_article',
360 'field_mentioned_in',
361 NULL,
362 'node',
363 'default',
364 [
365 'target_bundles' => [
366 'journal_issue' => 'journal_issue',
367 'journal_conference' => 'journal_conference',
368 ],
369 ],
370 FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
371 );
372 $this->rebuildAll();
373
374 // Create data.
375 $issue_node = Node::create([
376 'title' => 'Test Journal Issue',
377 'type' => 'journal_issue',
378 ]);
379 $issue_node->save();
380 $conference_node = Node::create([
381 'title' => 'First Journal Conference!',
382 'type' => 'journal_conference',
383 ]);
384 $conference_node->save();
385
386 $user = $this->drupalCreateUser([
387 'access content',
388 'edit own journal_article content',
389 ]);
390 $article_node = Node::create([
391 'title' => 'Test Journal Article',
392 'type' => 'journal_article',
393 'field_issue' => [
394 ['target_id' => $issue_node->id()],
395 ],
396 'field_mentioned_in' => [
397 ['target_id' => $issue_node->id()],
398 ['target_id' => $conference_node->id()],
399 ],
400 ]);
401 $article_node->setOwner($user);
402 $article_node->save();
403
404 // Test.
405 $url = Url::fromUri(sprintf('internal:/jsonapi/node/journal_article/%s', $article_node->uuid()));
406 $request_options = [
407 RequestOptions::HEADERS => [
408 'Content-Type' => 'application/vnd.api+json',
409 'Accept' => 'application/vnd.api+json',
410 ],
411 RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw],
412 ];
413 $issue_node->delete();
414 $response = $this->request('GET', $url, $request_options);
415 $this->assertSame(200, $response->getStatusCode());
416
417 // Entity reference field allowing a single bundle: dangling reference's
418 // resource type is deduced.
419 $this->assertSame([
420 [
421 'type' => 'node--journal_issue',
422 'id' => 'missing',
423 'meta' => [
424 'links' => [
425 'help' => [
426 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#missing',
427 'meta' => [
428 'about' => "Usage and meaning of the 'missing' resource identifier.",
429 ],
430 ],
431 ],
432 ],
433 ],
434 ], Json::decode((string) $response->getBody())['data']['relationships']['field_issue']['data']);
435
436 // Entity reference field allowing multiple bundles: dangling reference's
437 // resource type is NOT deduced.
438 $this->assertSame([
439 [
440 'type' => 'unknown',
441 'id' => 'missing',
442 'meta' => [
443 'links' => [
444 'help' => [
445 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#missing',
446 'meta' => [
447 'about' => "Usage and meaning of the 'missing' resource identifier.",
448 ],
449 ],
450 ],
451 ],
452 ],
453 [
454 'type' => 'node--journal_conference',
455 'id' => $conference_node->uuid(),
456 ],
457 ], Json::decode((string) $response->getBody())['data']['relationships']['field_mentioned_in']['data']);
458 }
459
460 /**
461 * Ensures that JSON:API routes are caches are dynamically rebuilt.
462 *
463 * Adding a new relationship field should cause new routes to be immediately
464 * regenerated. The site builder should not need to manually rebuild caches.
465 *
466 * @see https://www.drupal.org/project/jsonapi/issues/2984886
467 */
468 public function testThatRoutesAreRebuiltAfterDataModelChangesFromIssue2984886() {
469 $user = $this->drupalCreateUser(['access content']);
470 $request_options = [
471 RequestOptions::AUTH => [
472 $user->getUsername(),
473 $user->pass_raw,
474 ],
475 ];
476
477 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog'), $request_options);
478 $this->assertSame(404, $response->getStatusCode());
479
480 $node_type_dog = NodeType::create(['type' => 'dog']);
481 $node_type_dog->save();
482 NodeType::create(['type' => 'cat'])->save();
483 \Drupal::service('router.builder')->rebuildIfNeeded();
484
485 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog'), $request_options);
486 $this->assertSame(200, $response->getStatusCode());
487
488 $this->createEntityReferenceField('node', 'dog', 'field_test', NULL, 'node');
489 \Drupal::service('router.builder')->rebuildIfNeeded();
490
491 $dog = Node::create(['type' => 'dog', 'title' => 'Rosie P. Mosie']);
492 $dog->save();
493
494 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog/' . $dog->uuid() . '/field_test'), $request_options);
495 $this->assertSame(200, $response->getStatusCode());
496
497 $this->createEntityReferenceField('node', 'cat', 'field_test', NULL, 'node');
498 \Drupal::service('router.builder')->rebuildIfNeeded();
499
500 $cat = Node::create(['type' => 'cat', 'title' => 'E. Napoleon']);
501 $cat->save();
502
503 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/cat/' . $cat->uuid() . '/field_test'), $request_options);
504 $this->assertSame(200, $response->getStatusCode());
505
506 FieldConfig::loadByName('node', 'cat', 'field_test')->delete();
507 \Drupal::service('router.builder')->rebuildIfNeeded();
508
509 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/cat/' . $cat->uuid() . '/field_test'), $request_options);
510 $this->assertSame(404, $response->getStatusCode());
511
512 $node_type_dog->delete();
513 \Drupal::service('router.builder')->rebuildIfNeeded();
514
515 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog'), $request_options);
516 $this->assertSame(404, $response->getStatusCode());
517 }
518
519 /**
520 * Ensures denormalizing relationships with aliased field names works.
521 *
522 * @see https://www.drupal.org/project/jsonapi/issues/3007113
523 * @see https://www.drupal.org/project/jsonapi_extras/issues/3004582#comment-12817261
524 */
525 public function testDenormalizeAliasedRelationshipFromIssue2953207() {
526 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
527
528 // Since the JSON:API module does not have an explicit mechanism to set up
529 // field aliases, create a strange data model so that automatic aliasing
530 // allows us to test aliased relationships.
531 // @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFieldMapping()
532 $internal_relationship_field_name = 'type';
533 $public_relationship_field_name = 'taxonomy_term_' . $internal_relationship_field_name;
534
535 // Set up data model.
536 $this->createEntityReferenceField(
537 'taxonomy_term',
538 'tags',
539 $internal_relationship_field_name,
540 NULL,
541 'user'
542 );
543 $this->rebuildAll();
544
545 // Create data.
546 Term::create([
547 'name' => 'foobar',
548 'vid' => 'tags',
549 'type' => ['target_id' => 1],
550 ])->save();
551
552 // Test.
553 $user = $this->drupalCreateUser([
554 'edit terms in tags',
555 ]);
556 $body = [
557 'data' => [
558 'type' => 'user--user',
559 'id' => User::load(0)->uuid(),
560 ],
561 ];
562
563 // Test.
564 $response = $this->request('PATCH', Url::fromUri(sprintf('internal:/jsonapi/taxonomy_term/tags/%s/relationships/%s', Term::load(1)->uuid(), $public_relationship_field_name)), [
565 RequestOptions::AUTH => [
566 $user->getUsername(),
567 $user->pass_raw,
568 ],
569 RequestOptions::HEADERS => [
570 'Content-Type' => 'application/vnd.api+json',
571 ],
572 RequestOptions::BODY => Json::encode($body),
573 ]);
574 $this->assertSame(204, $response->getStatusCode());
575 }
576
577 /**
578 * Ensures that Drupal's page cache is effective.
579 *
580 * @see https://www.drupal.org/project/jsonapi/issues/3009596
581 */
582 public function testPageCacheFromIssue3009596() {
583 $anonymous_role = Role::load(RoleInterface::ANONYMOUS_ID);
584 $anonymous_role->grantPermission('access content');
585 $anonymous_role->trustData()->save();
586
587 NodeType::create(['type' => 'emu_fact'])->save();
588 \Drupal::service('router.builder')->rebuildIfNeeded();
589
590 $node = Node::create([
591 'type' => 'emu_fact',
592 'title' => "Emus don't say moo!",
593 ]);
594 $node->save();
595
596 $request_options = [
597 RequestOptions::HEADERS => ['Accept' => 'application/vnd.api+json'],
598 ];
599 $node_url = Url::fromUri('internal:/jsonapi/node/emu_fact/' . $node->uuid());
600
601 // The first request should be a cache MISS.
602 $response = $this->request('GET', $node_url, $request_options);
603 $this->assertSame(200, $response->getStatusCode());
604 $this->assertSame('MISS', $response->getHeader('X-Drupal-Cache')[0]);
605
606 // The second request should be a cache HIT.
607 $response = $this->request('GET', $node_url, $request_options);
608 $this->assertSame(200, $response->getStatusCode());
609 $this->assertSame('HIT', $response->getHeader('X-Drupal-Cache')[0]);
610 }
611
612 /**
613 * Ensures that filtering by a sequential internal ID named 'id' is possible.
614 *
615 * @see https://www.drupal.org/project/jsonapi/issues/3015759
616 */
617 public function testFilterByIdFromIssue3015759() {
618 // Set up data model.
619 $this->assertTrue($this->container->get('module_installer')->install(['shortcut'], TRUE), 'Installed modules.');
620 $this->rebuildAll();
621
622 // Create data.
623 $shortcut = Shortcut::create([
624 'shortcut_set' => 'default',
625 'title' => $this->randomMachineName(),
626 'weight' => -20,
627 'link' => [
628 'uri' => 'internal:/user/logout',
629 ],
630 ]);
631 $shortcut->save();
632
633 // Test.
634 $user = $this->drupalCreateUser([
635 'access shortcuts',
636 'customize shortcut links',
637 ]);
638 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/shortcut/default?filter[drupal_internal__id]=' . $shortcut->id()), [
639 RequestOptions::AUTH => [
640 $user->getUsername(),
641 $user->pass_raw,
642 ],
643 ]);
644 $this->assertSame(200, $response->getStatusCode());
645 $doc = Json::decode((string) $response->getBody());
646 $this->assertNotEmpty($doc['data']);
647 $this->assertSame($doc['data'][0]['id'], $shortcut->uuid());
648 $this->assertSame($doc['data'][0]['attributes']['drupal_internal__id'], (int) $shortcut->id());
649 $this->assertSame($doc['data'][0]['attributes']['title'], $shortcut->label());
650 }
651
652 /**
653 * Ensures datetime fields are normalized using the correct timezone.
654 *
655 * @see https://www.drupal.org/project/jsonapi/issues/2999438
656 */
657 public function testPatchingDateTimeNormalizedWrongTimeZoneIssue3021194() {
658 // Set up data model.
659 $this->assertTrue($this->container->get('module_installer')->install(['datetime'], TRUE), 'Installed modules.');
660 $this->drupalCreateContentType(['type' => 'page']);
661 $this->rebuildAll();
662 FieldStorageConfig::create([
663 'field_name' => 'when',
664 'type' => 'datetime',
665 'entity_type' => 'node',
666 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME],
667 ])
668 ->save();
669 FieldConfig::create([
670 'field_name' => 'when',
671 'entity_type' => 'node',
672 'bundle' => 'page',
673 ])
674 ->save();
675
676 // Create data.
677 $page = Node::create([
678 'title' => 'Stegosaurus',
679 'type' => 'page',
680 'when' => [
681 'value' => '2018-09-16T12:00:00',
682 ],
683 ]);
684 $page->save();
685
686 // Test.
687 $user = $this->drupalCreateUser([
688 'access content',
689 ]);
690 $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/page/' . $page->uuid()), [
691 RequestOptions::AUTH => [
692 $user->getUsername(),
693 $user->pass_raw,
694 ],
695 ]);
696 $this->assertSame(200, $response->getStatusCode());
697 $doc = Json::decode((string) $response->getBody());
698 $this->assertSame('2018-09-16T22:00:00+10:00', $doc['data']['attributes']['when']);
699 }
700
701 /**
702 * Ensures PATCHing datetime (both date-only & date+time) fields is possible.
703 *
704 * @see https://www.drupal.org/project/jsonapi/issues/3021194
705 */
706 public function testPatchingDateTimeFieldsFromIssue3021194() {
707 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
708
709 // Set up data model.
710 $this->assertTrue($this->container->get('module_installer')->install(['datetime'], TRUE), 'Installed modules.');
711 $this->drupalCreateContentType(['type' => 'page']);
712 $this->rebuildAll();
713 FieldStorageConfig::create([
714 'field_name' => 'when',
715 'type' => 'datetime',
716 'entity_type' => 'node',
717 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATE],
718 ])
719 ->save();
720 FieldConfig::create([
721 'field_name' => 'when',
722 'entity_type' => 'node',
723 'bundle' => 'page',
724 ])
725 ->save();
726 FieldStorageConfig::create([
727 'field_name' => 'when_exactly',
728 'type' => 'datetime',
729 'entity_type' => 'node',
730 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME],
731 ])
732 ->save();
733 FieldConfig::create([
734 'field_name' => 'when_exactly',
735 'entity_type' => 'node',
736 'bundle' => 'page',
737 ])
738 ->save();
739
740 // Create data.
741 $page = Node::create([
742 'title' => 'Stegosaurus',
743 'type' => 'page',
744 'when' => [
745 'value' => '2018-12-19',
746 ],
747 'when_exactly' => [
748 'value' => '2018-12-19T17:00:00',
749 ],
750 ]);
751 $page->save();
752
753 // Test.
754 $user = $this->drupalCreateUser([
755 'access content',
756 'edit any page content',
757 ]);
758 $request_options = [
759 RequestOptions::AUTH => [
760 $user->getUsername(),
761 $user->pass_raw,
762 ],
763 RequestOptions::HEADERS => [
764 'Content-Type' => 'application/vnd.api+json',
765 'Accept' => 'application/vnd.api+json',
766 ],
767 ];
768 $node_url = Url::fromUri('internal:/jsonapi/node/page/' . $page->uuid());
769 $response = $this->request('GET', $node_url, $request_options);
770 $this->assertSame(200, $response->getStatusCode());
771 $doc = Json::decode((string) $response->getBody());
772 $this->assertSame('2018-12-19', $doc['data']['attributes']['when']);
773 $this->assertSame('2018-12-20T04:00:00+11:00', $doc['data']['attributes']['when_exactly']);
774 $doc['data']['attributes']['when'] = '2018-12-20';
775 $doc['data']['attributes']['when_exactly'] = '2018-12-19T19:00:00+01:00';
776 $request_options = $request_options + [RequestOptions::JSON => $doc];
777 $response = $this->request('PATCH', $node_url, $request_options);
778 $this->assertSame(200, $response->getStatusCode());
779 $doc = Json::decode((string) $response->getBody());
780 $this->assertSame('2018-12-20', $doc['data']['attributes']['when']);
781 $this->assertSame('2018-12-20T05:00:00+11:00', $doc['data']['attributes']['when_exactly']);
782 }
783
784 /**
785 * Ensure includes are respected even when POSTing.
786 *
787 * @see https://www.drupal.org/project/jsonapi/issues/3026030
788 */
789 public function testPostToIncludeUrlDoesNotReturnIncludeFromIssue3026030() {
790 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
791
792 // Set up data model.
793 $this->drupalCreateContentType(['type' => 'page']);
794 $this->rebuildAll();
795
796 // Test.
797 $user = $this->drupalCreateUser(['bypass node access']);
798 $url = Url::fromUri('internal:/jsonapi/node/page?include=uid');
799 $request_options = [
800 RequestOptions::HEADERS => [
801 'Content-Type' => 'application/vnd.api+json',
802 'Accept' => 'application/vnd.api+json',
803 ],
804 RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw],
805 RequestOptions::JSON => [
806 'data' => [
807 'type' => 'node--page',
808 'attributes' => [
809 'title' => 'test',
810 ],
811 ],
812 ],
813 ];
814 $response = $this->request('POST', $url, $request_options);
815 $this->assertSame(201, $response->getStatusCode());
816 $doc = Json::decode((string) $response->getBody());
817 $this->assertArrayHasKey('included', $doc);
818 $this->assertSame($user->label(), $doc['included'][0]['attributes']['name']);
819 }
820
821 /**
822 * Ensure includes are respected even when PATCHing.
823 *
824 * @see https://www.drupal.org/project/jsonapi/issues/3026030
825 */
826 public function testPatchToIncludeUrlDoesNotReturnIncludeFromIssue3026030() {
827 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
828
829 // Set up data model.
830 $this->drupalCreateContentType(['type' => 'page']);
831 $this->rebuildAll();
832
833 // Create data.
834 $user = $this->drupalCreateUser(['bypass node access']);
835 $page = Node::create([
836 'title' => 'original',
837 'type' => 'page',
838 'uid' => $user->id(),
839 ]);
840 $page->save();
841
842 // Test.
843 $url = Url::fromUri(sprintf('internal:/jsonapi/node/page/%s/?include=uid', $page->uuid()));
844 $request_options = [
845 RequestOptions::HEADERS => [
846 'Content-Type' => 'application/vnd.api+json',
847 'Accept' => 'application/vnd.api+json',
848 ],
849 RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw],
850 RequestOptions::JSON => [
851 'data' => [
852 'type' => 'node--page',
853 'id' => $page->uuid(),
854 'attributes' => [
855 'title' => 'modified',
856 ],
857 ],
858 ],
859 ];
860 $response = $this->request('PATCH', $url, $request_options);
861 $this->assertSame(200, $response->getStatusCode());
862 $doc = Json::decode((string) $response->getBody());
863 $this->assertArrayHasKey('included', $doc);
864 $this->assertSame($user->label(), $doc['included'][0]['attributes']['name']);
865 }
866
867 /**
868 * Ensure `@FieldType=map` fields are normalized correctly.
869 *
870 * @see https://www.drupal.org/project/jsonapi/issues/3040590
871 */
872 public function testMapFieldTypeNormalizationFromIssue3040590() {
873 $this->assertTrue($this->container->get('module_installer')->install(['entity_test'], TRUE), 'Installed modules.');
874
875 // Create data.
876 $entity = EntityTestMapField::create([
877 'data' => [
878 'foo' => 'bar',
879 'baz' => 'qux',
880 ],
881 ]);
882 $entity->save();
883 $user = $this->drupalCreateUser([
884 'administer entity_test content',
885 ]);
886
887 // Test.
888 $url = Url::fromUri(sprintf('internal:/jsonapi/entity_test_map_field/entity_test_map_field', $entity->uuid()));
889 $request_options = [
890 RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw],
891 ];
892 $response = $this->request('GET', $url, $request_options);
893 $this->assertSame(200, $response->getStatusCode());
894 $data = Json::decode((string) $response->getBody());
895 $this->assertSame([
896 'foo' => 'bar',
897 'baz' => 'qux',
898 ], $data['data'][0]['attributes']['data']);
899 $entity->set('data', [
900 'foo' => 'bar',
901 ])->save();
902 $response = $this->request('GET', $url, $request_options);
903 $this->assertSame(200, $response->getStatusCode());
904 $data = Json::decode((string) $response->getBody());
905 $this->assertSame(['foo' => 'bar'], $data['data'][0]['attributes']['data']);
906 }
907
908 }