function JsonApiFunctionalTest::testWrite

Same name and namespace in other branches
  1. 9 core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php \Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTest::testWrite()
  2. 8.9.x core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php \Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTest::testWrite()
  3. 11.x core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php \Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTest::testWrite()

Tests POST, PATCH and DELETE.

File

core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php, line 573

Class

JsonApiFunctionalTest
General functional test class.

Namespace

Drupal\Tests\jsonapi\Functional

Code

public function testWrite() : void {
  $this->config('jsonapi.settings')
    ->set('read_only', FALSE)
    ->save(TRUE);
  $this->createDefaultContent(0, 3, FALSE, FALSE, static::IS_NOT_MULTILINGUAL, FALSE);
  // 1. Successful post.
  $collection_url = Url::fromRoute('jsonapi.node--article.collection.post');
  $body = [
    'data' => [
      'type' => 'node--article',
      'attributes' => [
        'langcode' => 'en',
        'title' => 'My custom title',
        'default_langcode' => '1',
        'body' => [
          'value' => 'Custom value',
          'format' => 'plain_text',
          'summary' => 'Custom summary',
        ],
      ],
      'relationships' => [
        'field_tags' => [
          'data' => [
            [
              'type' => 'taxonomy_term--tags',
              'id' => $this->tags[0]
                ->uuid(),
            ],
            [
              'type' => 'taxonomy_term--tags',
              'id' => $this->tags[1]
                ->uuid(),
            ],
          ],
        ],
      ],
    ],
  ];
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response);
  $this->assertEquals(201, $response->getStatusCode());
  $this->assertArrayNotHasKey('uuid', $document['data']['attributes']);
  $uuid = $document['data']['id'];
  $this->assertCount(2, $document['data']['relationships']['field_tags']['data']);
  $this->assertEquals($document['data']['links']['self']['href'], $response->getHeader('Location')[0]);
  // 2. Authorization error.
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($body),
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(401, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertEquals('Unauthorized', $document['errors'][0]['title']);
  // 2.1 Authorization error with a user without create permissions.
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->userCanViewProfiles
        ->getAccountName(),
      $this->userCanViewProfiles->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(403, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertEquals('Forbidden', $document['errors'][0]['title']);
  // 3. Missing Content-Type error.
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Accept' => 'application/vnd.api+json',
    ],
  ]);
  $this->assertEquals(415, $response->getStatusCode());
  // 4. Article with a duplicate ID.
  $invalid_body = $body;
  $invalid_body['data']['id'] = Node::load(1)->uuid();
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($invalid_body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Accept' => 'application/vnd.api+json',
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(409, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertEquals('Conflict', $document['errors'][0]['title']);
  // 5. Article with wrong reference UUIDs for tags.
  $body_invalid_tags = $body;
  $body_invalid_tags['data']['relationships']['field_tags']['data'][0]['id'] = 'lorem';
  $body_invalid_tags['data']['relationships']['field_tags']['data'][1]['id'] = 'ipsum';
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($body_invalid_tags),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $this->assertEquals(404, $response->getStatusCode());
  // 6. Decoding error.
  $response = $this->request('POST', $collection_url, [
    'body' => '{"bad json",,,}',
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
      'Accept' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(400, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertEquals('Bad Request', $document['errors'][0]['title']);
  // 6.1 Denormalizing error.
  $response = $this->request('POST', $collection_url, [
    'body' => '{"data":{"type":"something"},"valid yet nonsensical json":[]}',
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
      'Accept' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(422, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertStringStartsWith('Unprocessable', $document['errors'][0]['title']);
  // 6.2 Relationships are not included in "data".
  $malformed_body = $body;
  unset($malformed_body['data']['relationships']);
  $malformed_body['relationships'] = $body['data']['relationships'];
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($malformed_body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Accept' => 'application/vnd.api+json',
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertSame(400, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertSame("Bad Request", $document['errors'][0]['title']);
  $this->assertSame("Found \"relationships\" within the document's top level. The \"relationships\" key must be within resource object.", $document['errors'][0]['detail']);
  // 6.2 "type" not included in "data".
  $missing_type = $body;
  unset($missing_type['data']['type']);
  $response = $this->request('POST', $collection_url, [
    'body' => Json::encode($missing_type),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Accept' => 'application/vnd.api+json',
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $document = $this->getDocumentFromResponse($response, FALSE);
  $this->assertSame(400, $response->getStatusCode());
  $this->assertNotEmpty($document['errors']);
  $this->assertSame("Bad Request", $document['errors'][0]['title']);
  $this->assertSame("Resource object must include a \"type\".", $document['errors'][0]['detail']);
  // 7. Successful PATCH.
  $body = [
    'data' => [
      'id' => $uuid,
      'type' => 'node--article',
      'attributes' => [
        'title' => 'My updated title',
      ],
    ],
  ];
  $individual_url = Url::fromRoute('jsonapi.node--article.individual', [
    'entity' => $uuid,
  ]);
  $response = $this->request('PATCH', $individual_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $updated_response = $this->getDocumentFromResponse($response);
  $this->assertEquals(200, $response->getStatusCode());
  $this->assertEquals('My updated title', $updated_response['data']['attributes']['title']);
  // 7.1 Unsuccessful PATCH due to access restrictions.
  $body = [
    'data' => [
      'id' => $uuid,
      'type' => 'node--article',
      'attributes' => [
        'title' => 'My updated title',
      ],
    ],
  ];
  $individual_url = Url::fromRoute('jsonapi.node--article.individual', [
    'entity' => $uuid,
  ]);
  $response = $this->request('PATCH', $individual_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->userCanViewProfiles
        ->getAccountName(),
      $this->userCanViewProfiles->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $this->assertEquals(403, $response->getStatusCode());
  // 8. Field access forbidden check.
  $body = [
    'data' => [
      'id' => $uuid,
      'type' => 'node--article',
      'attributes' => [
        'title' => 'My updated title',
        'status' => 0,
      ],
    ],
  ];
  $response = $this->request('PATCH', $individual_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $updated_response = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(403, $response->getStatusCode());
  $this->assertEquals("The current user is not allowed to PATCH the selected field (status). The 'administer nodes' permission is required.", $updated_response['errors'][0]['detail']);
  $node = \Drupal::service('entity.repository')->loadEntityByUuid('node', $uuid);
  $this->assertEquals(1, $node->get('status')->value, 'Node status was not changed.');
  // 9. Successful POST to related endpoint.
  $body = [
    'data' => [
      [
        'id' => $this->tags[2]
          ->uuid(),
        'type' => 'taxonomy_term--tags',
      ],
    ],
  ];
  $relationship_url = Url::fromRoute('jsonapi.node--article.field_tags.relationship.post', [
    'entity' => $uuid,
  ]);
  $response = $this->request('POST', $relationship_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $updated_response = $this->getDocumentFromResponse($response);
  $this->assertEquals(200, $response->getStatusCode());
  $this->assertCount(3, $updated_response['data']);
  $this->assertEquals('taxonomy_term--tags', $updated_response['data'][2]['type']);
  $this->assertEquals($this->tags[2]
    ->uuid(), $updated_response['data'][2]['id']);
  // 10. Successful PATCH to related endpoint.
  $body = [
    'data' => [
      [
        'id' => $this->tags[1]
          ->uuid(),
        'type' => 'taxonomy_term--tags',
      ],
    ],
  ];
  $response = $this->request('PATCH', $relationship_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $this->assertEquals(204, $response->getStatusCode());
  $this->assertEmpty($response->getBody()
    ->__toString());
  // 11. Successful DELETE to related endpoint.
  $response = $this->request('DELETE', $relationship_url, [
    // Send a request with no body.
'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
      'Accept' => 'application/vnd.api+json',
    ],
  ]);
  $updated_response = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals('You need to provide a body for DELETE operations on a relationship (field_tags).', $updated_response['errors'][0]['detail']);
  $this->assertEquals(400, $response->getStatusCode());
  $response = $this->request('DELETE', $relationship_url, [
    // Send a request with no authentication.
'body' => Json::encode($body),
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $this->assertEquals(401, $response->getStatusCode());
  $response = $this->request('DELETE', $relationship_url, [
    // Remove the existing relationship item.
'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
    ],
  ]);
  $this->assertEquals(204, $response->getStatusCode());
  $this->assertEmpty($response->getBody()
    ->__toString());
  // 12. PATCH with invalid title and body format.
  $body = [
    'data' => [
      'id' => $uuid,
      'type' => 'node--article',
      'attributes' => [
        'title' => '',
        'body' => [
          'value' => 'Custom value',
          'format' => 'invalid_format',
          'summary' => 'Custom summary',
        ],
      ],
    ],
  ];
  $response = $this->request('PATCH', $individual_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
      'Accept' => 'application/vnd.api+json',
    ],
  ]);
  $updated_response = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(422, $response->getStatusCode());
  $this->assertCount(2, $updated_response['errors']);
  for ($i = 0; $i < 2; $i++) {
    $this->assertStringStartsWith('Unprocessable', $updated_response['errors'][$i]['title']);
    $this->assertEquals(422, $updated_response['errors'][$i]['status']);
  }
  $this->assertEquals("title: This value should not be null.", $updated_response['errors'][0]['detail']);
  $this->assertEquals("body.0.format: The value you selected is not a valid choice.", $updated_response['errors'][1]['detail']);
  $this->assertEquals("/data/attributes/title", $updated_response['errors'][0]['source']['pointer']);
  $this->assertEquals("/data/attributes/body/format", $updated_response['errors'][1]['source']['pointer']);
  // 13. PATCH with field that doesn't exist on Entity.
  $body = [
    'data' => [
      'id' => $uuid,
      'type' => 'node--article',
      'attributes' => [
        'field_that_does_not_exist' => 'foobar',
      ],
    ],
  ];
  $response = $this->request('PATCH', $individual_url, [
    'body' => Json::encode($body),
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
    'headers' => [
      'Content-Type' => 'application/vnd.api+json',
      'Accept' => 'application/vnd.api+json',
    ],
  ]);
  $updated_response = $this->getDocumentFromResponse($response, FALSE);
  $this->assertEquals(422, $response->getStatusCode());
  $this->assertEquals("The attribute field_that_does_not_exist does not exist on the node--article resource type.", $updated_response['errors']['0']['detail']);
  // 14. Successful DELETE.
  $response = $this->request('DELETE', $individual_url, [
    'auth' => [
      $this->user
        ->getAccountName(),
      $this->user->pass_raw,
    ],
  ]);
  $this->assertEquals(204, $response->getStatusCode());
  $response = $this->request('GET', $individual_url, []);
  $this->assertEquals(404, $response->getStatusCode());
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.