Chris@18: assertTrue($this->container->get('module_installer')->install(['comment'], TRUE), 'Installed modules.'); Chris@18: $this->addDefaultCommentField('taxonomy_term', 'tags', 'comment', CommentItemInterface::OPEN, 'tcomment'); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: Term::create([ Chris@18: 'name' => 'foobar', Chris@18: 'vid' => 'tags', Chris@18: ])->save(); Chris@18: Comment::create([ Chris@18: 'subject' => 'Llama', Chris@18: 'entity_id' => 1, Chris@18: 'entity_type' => 'taxonomy_term', Chris@18: 'field_name' => 'comment', Chris@18: ])->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access comments', Chris@18: ]); Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/comment/tcomment?include=entity_id&filter[entity_id.name]=foobar'), [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensure deep nested include works on multi target entity type field. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2973681 Chris@18: */ Chris@18: public function testDeepNestedIncludeMultiTargetEntityTypeFieldFromIssue2973681() { Chris@18: // Set up data model. Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['comment'], TRUE), 'Installed modules.'); Chris@18: $this->addDefaultCommentField('node', 'article'); Chris@18: $this->addDefaultCommentField('taxonomy_term', 'tags', 'comment', CommentItemInterface::OPEN, 'tcomment'); Chris@18: $this->drupalCreateContentType(['type' => 'page']); Chris@18: $this->createEntityReferenceField( Chris@18: 'node', Chris@18: 'page', Chris@18: 'field_comment', Chris@18: NULL, Chris@18: 'comment', Chris@18: 'default', Chris@18: [ Chris@18: 'target_bundles' => [ Chris@18: 'comment' => 'comment', Chris@18: 'tcomment' => 'tcomment', Chris@18: ], Chris@18: ], Chris@18: FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED Chris@18: ); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: $node = Node::create([ Chris@18: 'title' => 'test article', Chris@18: 'type' => 'article', Chris@18: ]); Chris@18: $node->save(); Chris@18: $comment = Comment::create([ Chris@18: 'subject' => 'Llama', Chris@18: 'entity_id' => 1, Chris@18: 'entity_type' => 'node', Chris@18: 'field_name' => 'comment', Chris@18: ]); Chris@18: $comment->save(); Chris@18: $page = Node::create([ Chris@18: 'title' => 'test node', Chris@18: 'type' => 'page', Chris@18: 'field_comment' => [ Chris@18: 'entity' => $comment, Chris@18: ], Chris@18: ]); Chris@18: $page->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: 'access comments', Chris@18: ]); Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/page?include=field_comment,field_comment.entity_id,field_comment.entity_id.uid'), [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensure POST and PATCH works for bundle-less relationship routes. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2976371 Chris@18: */ Chris@18: public function testBundlelessRelationshipMutationFromIssue2973681() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // Set up data model. Chris@18: $this->drupalCreateContentType(['type' => 'page']); Chris@18: $this->createEntityReferenceField( Chris@18: 'node', Chris@18: 'page', Chris@18: 'field_test', Chris@18: NULL, Chris@18: 'user', Chris@18: 'default', Chris@18: [ Chris@18: 'target_bundles' => NULL, Chris@18: ], Chris@18: FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED Chris@18: ); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: $node = Node::create([ Chris@18: 'title' => 'test article', Chris@18: 'type' => 'page', Chris@18: ]); Chris@18: $node->save(); Chris@18: $target = $this->createUser(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser(['bypass node access']); Chris@18: $url = Url::fromRoute('jsonapi.node--page.field_test.relationship.post', ['entity' => $node->uuid()]); Chris@18: $request_options = [ Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: 'Accept' => 'application/vnd.api+json', Chris@18: ], Chris@18: RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], Chris@18: RequestOptions::JSON => [ Chris@18: 'data' => [ Chris@18: ['type' => 'user--user', 'id' => $target->uuid()], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: $response = $this->request('POST', $url, $request_options); Chris@18: $this->assertSame(204, $response->getStatusCode(), (string) $response->getBody()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures GETting terms works when multiple vocabularies exist. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2977879 Chris@18: */ Chris@18: public function testGetTermWhenMultipleVocabulariesExistFromIssue2977879() { Chris@18: // Set up data model. Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['taxonomy'], TRUE), 'Installed modules.'); Chris@18: Vocabulary::create([ Chris@18: 'name' => 'one', Chris@18: 'vid' => 'one', Chris@18: ])->save(); Chris@18: Vocabulary::create([ Chris@18: 'name' => 'two', Chris@18: 'vid' => 'two', Chris@18: ])->save(); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: Term::create(['vid' => 'one']) Chris@18: ->setName('Test') Chris@18: ->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: ]); Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/taxonomy_term/one'), [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Cannot PATCH an entity with dangling references in an ER field. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2968972 Chris@18: */ Chris@18: public function testDanglingReferencesInAnEntityReferenceFieldFromIssue2968972() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // Set up data model. Chris@18: $this->drupalCreateContentType(['type' => 'journal_issue']); Chris@18: $this->drupalCreateContentType(['type' => 'journal_article']); Chris@18: $this->createEntityReferenceField( Chris@18: 'node', Chris@18: 'journal_article', Chris@18: 'field_issue', Chris@18: NULL, Chris@18: 'node', Chris@18: 'default', Chris@18: [ Chris@18: 'target_bundles' => [ Chris@18: 'journal_issue' => 'journal_issue', Chris@18: ], Chris@18: ], Chris@18: FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED Chris@18: ); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: $issue_node = Node::create([ Chris@18: 'title' => 'Test Journal Issue', Chris@18: 'type' => 'journal_issue', Chris@18: ]); Chris@18: $issue_node->save(); Chris@18: Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: 'edit own journal_article content', Chris@18: ]); Chris@18: $article_node = Node::create([ Chris@18: 'title' => 'Test Journal Article', Chris@18: 'type' => 'journal_article', Chris@18: 'field_issue' => [ Chris@18: 'target_id' => $issue_node->id(), Chris@18: ], Chris@18: ]); Chris@18: $article_node->setOwner($user); Chris@18: $article_node->save(); Chris@18: Chris@18: // Test. Chris@18: $url = Url::fromUri(sprintf('internal:/jsonapi/node/journal_article/%s', $article_node->uuid())); Chris@18: $request_options = [ Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: 'Accept' => 'application/vnd.api+json', Chris@18: ], Chris@18: RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], Chris@18: RequestOptions::JSON => [ Chris@18: 'data' => [ Chris@18: 'type' => 'node--journal_article', Chris@18: 'id' => $article_node->uuid(), Chris@18: 'attributes' => [ Chris@18: 'title' => 'My New Article Title', Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: $issue_node->delete(); Chris@18: $response = $this->request('PATCH', $url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode(), (string) $response->getBody()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures GETting node collection + hook_node_grants() implementations works. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2984964 Chris@18: */ Chris@18: public function testGetNodeCollectionWithHookNodeGrantsImplementationsFromIssue2984964() { Chris@18: // Set up data model. Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['node_access_test'], TRUE), 'Installed modules.'); Chris@18: node_access_rebuild(); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: Node::create([ Chris@18: 'title' => 'test article', Chris@18: 'type' => 'article', Chris@18: ])->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: ]); Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/article'), [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $this->assertTrue(in_array('user.node_grants:view', explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0]), TRUE)); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Cannot GET an entity with dangling references in an ER field. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2984647 Chris@18: */ Chris@18: public function testDanglingReferencesInAnEntityReferenceFieldFromIssue2984647() { Chris@18: // Set up data model. Chris@18: $this->drupalCreateContentType(['type' => 'journal_issue']); Chris@18: $this->drupalCreateContentType(['type' => 'journal_conference']); Chris@18: $this->drupalCreateContentType(['type' => 'journal_article']); Chris@18: $this->createEntityReferenceField( Chris@18: 'node', Chris@18: 'journal_article', Chris@18: 'field_issue', Chris@18: NULL, Chris@18: 'node', Chris@18: 'default', Chris@18: [ Chris@18: 'target_bundles' => [ Chris@18: 'journal_issue' => 'journal_issue', Chris@18: ], Chris@18: ], Chris@18: FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED Chris@18: ); Chris@18: $this->createEntityReferenceField( Chris@18: 'node', Chris@18: 'journal_article', Chris@18: 'field_mentioned_in', Chris@18: NULL, Chris@18: 'node', Chris@18: 'default', Chris@18: [ Chris@18: 'target_bundles' => [ Chris@18: 'journal_issue' => 'journal_issue', Chris@18: 'journal_conference' => 'journal_conference', Chris@18: ], Chris@18: ], Chris@18: FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED Chris@18: ); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: $issue_node = Node::create([ Chris@18: 'title' => 'Test Journal Issue', Chris@18: 'type' => 'journal_issue', Chris@18: ]); Chris@18: $issue_node->save(); Chris@18: $conference_node = Node::create([ Chris@18: 'title' => 'First Journal Conference!', Chris@18: 'type' => 'journal_conference', Chris@18: ]); Chris@18: $conference_node->save(); Chris@18: Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: 'edit own journal_article content', Chris@18: ]); Chris@18: $article_node = Node::create([ Chris@18: 'title' => 'Test Journal Article', Chris@18: 'type' => 'journal_article', Chris@18: 'field_issue' => [ Chris@18: ['target_id' => $issue_node->id()], Chris@18: ], Chris@18: 'field_mentioned_in' => [ Chris@18: ['target_id' => $issue_node->id()], Chris@18: ['target_id' => $conference_node->id()], Chris@18: ], Chris@18: ]); Chris@18: $article_node->setOwner($user); Chris@18: $article_node->save(); Chris@18: Chris@18: // Test. Chris@18: $url = Url::fromUri(sprintf('internal:/jsonapi/node/journal_article/%s', $article_node->uuid())); Chris@18: $request_options = [ Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: 'Accept' => 'application/vnd.api+json', Chris@18: ], Chris@18: RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], Chris@18: ]; Chris@18: $issue_node->delete(); Chris@18: $response = $this->request('GET', $url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: Chris@18: // Entity reference field allowing a single bundle: dangling reference's Chris@18: // resource type is deduced. Chris@18: $this->assertSame([ Chris@18: [ Chris@18: 'type' => 'node--journal_issue', Chris@18: 'id' => 'missing', Chris@18: 'meta' => [ Chris@18: 'links' => [ Chris@18: 'help' => [ Chris@18: 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#missing', Chris@18: 'meta' => [ Chris@18: 'about' => "Usage and meaning of the 'missing' resource identifier.", Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Json::decode((string) $response->getBody())['data']['relationships']['field_issue']['data']); Chris@18: Chris@18: // Entity reference field allowing multiple bundles: dangling reference's Chris@18: // resource type is NOT deduced. Chris@18: $this->assertSame([ Chris@18: [ Chris@18: 'type' => 'unknown', Chris@18: 'id' => 'missing', Chris@18: 'meta' => [ Chris@18: 'links' => [ Chris@18: 'help' => [ Chris@18: 'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#missing', Chris@18: 'meta' => [ Chris@18: 'about' => "Usage and meaning of the 'missing' resource identifier.", Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: [ Chris@18: 'type' => 'node--journal_conference', Chris@18: 'id' => $conference_node->uuid(), Chris@18: ], Chris@18: ], Json::decode((string) $response->getBody())['data']['relationships']['field_mentioned_in']['data']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures that JSON:API routes are caches are dynamically rebuilt. Chris@18: * Chris@18: * Adding a new relationship field should cause new routes to be immediately Chris@18: * regenerated. The site builder should not need to manually rebuild caches. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2984886 Chris@18: */ Chris@18: public function testThatRoutesAreRebuiltAfterDataModelChangesFromIssue2984886() { Chris@18: $user = $this->drupalCreateUser(['access content']); Chris@18: $request_options = [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]; Chris@18: Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog'), $request_options); Chris@18: $this->assertSame(404, $response->getStatusCode()); Chris@18: Chris@18: $node_type_dog = NodeType::create(['type' => 'dog']); Chris@18: $node_type_dog->save(); Chris@18: NodeType::create(['type' => 'cat'])->save(); Chris@18: \Drupal::service('router.builder')->rebuildIfNeeded(); Chris@18: Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog'), $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: Chris@18: $this->createEntityReferenceField('node', 'dog', 'field_test', NULL, 'node'); Chris@18: \Drupal::service('router.builder')->rebuildIfNeeded(); Chris@18: Chris@18: $dog = Node::create(['type' => 'dog', 'title' => 'Rosie P. Mosie']); Chris@18: $dog->save(); Chris@18: Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog/' . $dog->uuid() . '/field_test'), $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: Chris@18: $this->createEntityReferenceField('node', 'cat', 'field_test', NULL, 'node'); Chris@18: \Drupal::service('router.builder')->rebuildIfNeeded(); Chris@18: Chris@18: $cat = Node::create(['type' => 'cat', 'title' => 'E. Napoleon']); Chris@18: $cat->save(); Chris@18: Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/cat/' . $cat->uuid() . '/field_test'), $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: Chris@18: FieldConfig::loadByName('node', 'cat', 'field_test')->delete(); Chris@18: \Drupal::service('router.builder')->rebuildIfNeeded(); Chris@18: Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/cat/' . $cat->uuid() . '/field_test'), $request_options); Chris@18: $this->assertSame(404, $response->getStatusCode()); Chris@18: Chris@18: $node_type_dog->delete(); Chris@18: \Drupal::service('router.builder')->rebuildIfNeeded(); Chris@18: Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/dog'), $request_options); Chris@18: $this->assertSame(404, $response->getStatusCode()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures denormalizing relationships with aliased field names works. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3007113 Chris@18: * @see https://www.drupal.org/project/jsonapi_extras/issues/3004582#comment-12817261 Chris@18: */ Chris@18: public function testDenormalizeAliasedRelationshipFromIssue2953207() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // Since the JSON:API module does not have an explicit mechanism to set up Chris@18: // field aliases, create a strange data model so that automatic aliasing Chris@18: // allows us to test aliased relationships. Chris@18: // @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFieldMapping() Chris@18: $internal_relationship_field_name = 'type'; Chris@18: $public_relationship_field_name = 'taxonomy_term_' . $internal_relationship_field_name; Chris@18: Chris@18: // Set up data model. Chris@18: $this->createEntityReferenceField( Chris@18: 'taxonomy_term', Chris@18: 'tags', Chris@18: $internal_relationship_field_name, Chris@18: NULL, Chris@18: 'user' Chris@18: ); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: Term::create([ Chris@18: 'name' => 'foobar', Chris@18: 'vid' => 'tags', Chris@18: 'type' => ['target_id' => 1], Chris@18: ])->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'edit terms in tags', Chris@18: ]); Chris@18: $body = [ Chris@18: 'data' => [ Chris@18: 'type' => 'user--user', Chris@18: 'id' => User::load(0)->uuid(), Chris@18: ], Chris@18: ]; Chris@18: Chris@18: // Test. Chris@18: $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: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: ], Chris@18: RequestOptions::BODY => Json::encode($body), Chris@18: ]); Chris@18: $this->assertSame(204, $response->getStatusCode()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures that Drupal's page cache is effective. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3009596 Chris@18: */ Chris@18: public function testPageCacheFromIssue3009596() { Chris@18: $anonymous_role = Role::load(RoleInterface::ANONYMOUS_ID); Chris@18: $anonymous_role->grantPermission('access content'); Chris@18: $anonymous_role->trustData()->save(); Chris@18: Chris@18: NodeType::create(['type' => 'emu_fact'])->save(); Chris@18: \Drupal::service('router.builder')->rebuildIfNeeded(); Chris@18: Chris@18: $node = Node::create([ Chris@18: 'type' => 'emu_fact', Chris@18: 'title' => "Emus don't say moo!", Chris@18: ]); Chris@18: $node->save(); Chris@18: Chris@18: $request_options = [ Chris@18: RequestOptions::HEADERS => ['Accept' => 'application/vnd.api+json'], Chris@18: ]; Chris@18: $node_url = Url::fromUri('internal:/jsonapi/node/emu_fact/' . $node->uuid()); Chris@18: Chris@18: // The first request should be a cache MISS. Chris@18: $response = $this->request('GET', $node_url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $this->assertSame('MISS', $response->getHeader('X-Drupal-Cache')[0]); Chris@18: Chris@18: // The second request should be a cache HIT. Chris@18: $response = $this->request('GET', $node_url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $this->assertSame('HIT', $response->getHeader('X-Drupal-Cache')[0]); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures that filtering by a sequential internal ID named 'id' is possible. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3015759 Chris@18: */ Chris@18: public function testFilterByIdFromIssue3015759() { Chris@18: // Set up data model. Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['shortcut'], TRUE), 'Installed modules.'); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: $shortcut = Shortcut::create([ Chris@18: 'shortcut_set' => 'default', Chris@18: 'title' => $this->randomMachineName(), Chris@18: 'weight' => -20, Chris@18: 'link' => [ Chris@18: 'uri' => 'internal:/user/logout', Chris@18: ], Chris@18: ]); Chris@18: $shortcut->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access shortcuts', Chris@18: 'customize shortcut links', Chris@18: ]); Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/shortcut/default?filter[drupal_internal__id]=' . $shortcut->id()), [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $doc = Json::decode((string) $response->getBody()); Chris@18: $this->assertNotEmpty($doc['data']); Chris@18: $this->assertSame($doc['data'][0]['id'], $shortcut->uuid()); Chris@18: $this->assertSame($doc['data'][0]['attributes']['drupal_internal__id'], (int) $shortcut->id()); Chris@18: $this->assertSame($doc['data'][0]['attributes']['title'], $shortcut->label()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures datetime fields are normalized using the correct timezone. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/2999438 Chris@18: */ Chris@18: public function testPatchingDateTimeNormalizedWrongTimeZoneIssue3021194() { Chris@18: // Set up data model. Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['datetime'], TRUE), 'Installed modules.'); Chris@18: $this->drupalCreateContentType(['type' => 'page']); Chris@18: $this->rebuildAll(); Chris@18: FieldStorageConfig::create([ Chris@18: 'field_name' => 'when', Chris@18: 'type' => 'datetime', Chris@18: 'entity_type' => 'node', Chris@18: 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME], Chris@18: ]) Chris@18: ->save(); Chris@18: FieldConfig::create([ Chris@18: 'field_name' => 'when', Chris@18: 'entity_type' => 'node', Chris@18: 'bundle' => 'page', Chris@18: ]) Chris@18: ->save(); Chris@18: Chris@18: // Create data. Chris@18: $page = Node::create([ Chris@18: 'title' => 'Stegosaurus', Chris@18: 'type' => 'page', Chris@18: 'when' => [ Chris@18: 'value' => '2018-09-16T12:00:00', Chris@18: ], Chris@18: ]); Chris@18: $page->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: ]); Chris@18: $response = $this->request('GET', Url::fromUri('internal:/jsonapi/node/page/' . $page->uuid()), [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: ]); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $doc = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('2018-09-16T22:00:00+10:00', $doc['data']['attributes']['when']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures PATCHing datetime (both date-only & date+time) fields is possible. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3021194 Chris@18: */ Chris@18: public function testPatchingDateTimeFieldsFromIssue3021194() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // Set up data model. Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['datetime'], TRUE), 'Installed modules.'); Chris@18: $this->drupalCreateContentType(['type' => 'page']); Chris@18: $this->rebuildAll(); Chris@18: FieldStorageConfig::create([ Chris@18: 'field_name' => 'when', Chris@18: 'type' => 'datetime', Chris@18: 'entity_type' => 'node', Chris@18: 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATE], Chris@18: ]) Chris@18: ->save(); Chris@18: FieldConfig::create([ Chris@18: 'field_name' => 'when', Chris@18: 'entity_type' => 'node', Chris@18: 'bundle' => 'page', Chris@18: ]) Chris@18: ->save(); Chris@18: FieldStorageConfig::create([ Chris@18: 'field_name' => 'when_exactly', Chris@18: 'type' => 'datetime', Chris@18: 'entity_type' => 'node', Chris@18: 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME], Chris@18: ]) Chris@18: ->save(); Chris@18: FieldConfig::create([ Chris@18: 'field_name' => 'when_exactly', Chris@18: 'entity_type' => 'node', Chris@18: 'bundle' => 'page', Chris@18: ]) Chris@18: ->save(); Chris@18: Chris@18: // Create data. Chris@18: $page = Node::create([ Chris@18: 'title' => 'Stegosaurus', Chris@18: 'type' => 'page', Chris@18: 'when' => [ Chris@18: 'value' => '2018-12-19', Chris@18: ], Chris@18: 'when_exactly' => [ Chris@18: 'value' => '2018-12-19T17:00:00', Chris@18: ], Chris@18: ]); Chris@18: $page->save(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'access content', Chris@18: 'edit any page content', Chris@18: ]); Chris@18: $request_options = [ Chris@18: RequestOptions::AUTH => [ Chris@18: $user->getUsername(), Chris@18: $user->pass_raw, Chris@18: ], Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: 'Accept' => 'application/vnd.api+json', Chris@18: ], Chris@18: ]; Chris@18: $node_url = Url::fromUri('internal:/jsonapi/node/page/' . $page->uuid()); Chris@18: $response = $this->request('GET', $node_url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $doc = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('2018-12-19', $doc['data']['attributes']['when']); Chris@18: $this->assertSame('2018-12-20T04:00:00+11:00', $doc['data']['attributes']['when_exactly']); Chris@18: $doc['data']['attributes']['when'] = '2018-12-20'; Chris@18: $doc['data']['attributes']['when_exactly'] = '2018-12-19T19:00:00+01:00'; Chris@18: $request_options = $request_options + [RequestOptions::JSON => $doc]; Chris@18: $response = $this->request('PATCH', $node_url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $doc = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame('2018-12-20', $doc['data']['attributes']['when']); Chris@18: $this->assertSame('2018-12-20T05:00:00+11:00', $doc['data']['attributes']['when_exactly']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensure includes are respected even when POSTing. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3026030 Chris@18: */ Chris@18: public function testPostToIncludeUrlDoesNotReturnIncludeFromIssue3026030() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // Set up data model. Chris@18: $this->drupalCreateContentType(['type' => 'page']); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Test. Chris@18: $user = $this->drupalCreateUser(['bypass node access']); Chris@18: $url = Url::fromUri('internal:/jsonapi/node/page?include=uid'); Chris@18: $request_options = [ Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: 'Accept' => 'application/vnd.api+json', Chris@18: ], Chris@18: RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], Chris@18: RequestOptions::JSON => [ Chris@18: 'data' => [ Chris@18: 'type' => 'node--page', Chris@18: 'attributes' => [ Chris@18: 'title' => 'test', Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: $response = $this->request('POST', $url, $request_options); Chris@18: $this->assertSame(201, $response->getStatusCode()); Chris@18: $doc = Json::decode((string) $response->getBody()); Chris@18: $this->assertArrayHasKey('included', $doc); Chris@18: $this->assertSame($user->label(), $doc['included'][0]['attributes']['name']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensure includes are respected even when PATCHing. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3026030 Chris@18: */ Chris@18: public function testPatchToIncludeUrlDoesNotReturnIncludeFromIssue3026030() { Chris@18: $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE); Chris@18: Chris@18: // Set up data model. Chris@18: $this->drupalCreateContentType(['type' => 'page']); Chris@18: $this->rebuildAll(); Chris@18: Chris@18: // Create data. Chris@18: $user = $this->drupalCreateUser(['bypass node access']); Chris@18: $page = Node::create([ Chris@18: 'title' => 'original', Chris@18: 'type' => 'page', Chris@18: 'uid' => $user->id(), Chris@18: ]); Chris@18: $page->save(); Chris@18: Chris@18: // Test. Chris@18: $url = Url::fromUri(sprintf('internal:/jsonapi/node/page/%s/?include=uid', $page->uuid())); Chris@18: $request_options = [ Chris@18: RequestOptions::HEADERS => [ Chris@18: 'Content-Type' => 'application/vnd.api+json', Chris@18: 'Accept' => 'application/vnd.api+json', Chris@18: ], Chris@18: RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], Chris@18: RequestOptions::JSON => [ Chris@18: 'data' => [ Chris@18: 'type' => 'node--page', Chris@18: 'id' => $page->uuid(), Chris@18: 'attributes' => [ Chris@18: 'title' => 'modified', Chris@18: ], Chris@18: ], Chris@18: ], Chris@18: ]; Chris@18: $response = $this->request('PATCH', $url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $doc = Json::decode((string) $response->getBody()); Chris@18: $this->assertArrayHasKey('included', $doc); Chris@18: $this->assertSame($user->label(), $doc['included'][0]['attributes']['name']); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensure `@FieldType=map` fields are normalized correctly. Chris@18: * Chris@18: * @see https://www.drupal.org/project/jsonapi/issues/3040590 Chris@18: */ Chris@18: public function testMapFieldTypeNormalizationFromIssue3040590() { Chris@18: $this->assertTrue($this->container->get('module_installer')->install(['entity_test'], TRUE), 'Installed modules.'); Chris@18: Chris@18: // Create data. Chris@18: $entity = EntityTestMapField::create([ Chris@18: 'data' => [ Chris@18: 'foo' => 'bar', Chris@18: 'baz' => 'qux', Chris@18: ], Chris@18: ]); Chris@18: $entity->save(); Chris@18: $user = $this->drupalCreateUser([ Chris@18: 'administer entity_test content', Chris@18: ]); Chris@18: Chris@18: // Test. Chris@18: $url = Url::fromUri(sprintf('internal:/jsonapi/entity_test_map_field/entity_test_map_field', $entity->uuid())); Chris@18: $request_options = [ Chris@18: RequestOptions::AUTH => [$user->getUsername(), $user->pass_raw], Chris@18: ]; Chris@18: $response = $this->request('GET', $url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $data = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame([ Chris@18: 'foo' => 'bar', Chris@18: 'baz' => 'qux', Chris@18: ], $data['data'][0]['attributes']['data']); Chris@18: $entity->set('data', [ Chris@18: 'foo' => 'bar', Chris@18: ])->save(); Chris@18: $response = $this->request('GET', $url, $request_options); Chris@18: $this->assertSame(200, $response->getStatusCode()); Chris@18: $data = Json::decode((string) $response->getBody()); Chris@18: $this->assertSame(['foo' => 'bar'], $data['data'][0]['attributes']['data']); Chris@18: } Chris@18: Chris@18: }