function JsonApiFunctionalTest::testWrite
Same name in other branches
- 9 core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php \Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTest::testWrite()
- 8.9.x core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.php \Drupal\Tests\jsonapi\Functional\JsonApiFunctionalTest::testWrite()
- 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\FunctionalCode
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.