annotate core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php @ 19:fa3358dc1485 tip

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