public function ResourceTestBase::testPostIndividual in JSON:API 8
Same name and namespace in other branches
- 8.2 tests/src/Functional/ResourceTestBase.php \Drupal\Tests\jsonapi\Functional\ResourceTestBase::testPostIndividual()
Tests POSTing an individual resource, plus edge cases to ensure good DX.
1 call to ResourceTestBase::testPostIndividual()
- MediaTest::testPostIndividual in tests/
src/ Functional/ MediaTest.php - Tests POSTing an individual resource, plus edge cases to ensure good DX.
5 methods override ResourceTestBase::testPostIndividual()
- BlockContentTest::testPostIndividual in tests/
src/ Functional/ BlockContentTest.php - Tests POSTing an individual resource, plus edge cases to ensure good DX.
- FileTest::testPostIndividual in tests/
src/ Functional/ FileTest.php - Tests POSTing an individual resource, plus edge cases to ensure good DX.
- ItemTest::testPostIndividual in tests/
src/ Functional/ ItemTest.php - Tests POSTing an individual resource, plus edge cases to ensure good DX.
- MediaTest::testPostIndividual in tests/
src/ Functional/ MediaTest.php - Tests POSTing an individual resource, plus edge cases to ensure good DX.
- ShortcutTest::testPostIndividual in tests/
src/ Functional/ ShortcutTest.php - Tests POSTing an individual resource, plus edge cases to ensure good DX.
File
- tests/
src/ Functional/ ResourceTestBase.php, line 1794
Class
- ResourceTestBase
- Subclass this for every JSON API resource type.
Namespace
Drupal\Tests\jsonapi\FunctionalCode
public function testPostIndividual() {
// @todo Remove this in https://www.drupal.org/node/2300677.
if ($this->entity instanceof ConfigEntityInterface) {
$this
->assertTrue(TRUE, 'POSTing config entities is not yet supported.');
return;
}
// Try with all of the following request bodies.
$unparseable_request_body = '!{>}<';
$parseable_valid_request_body = Json::encode($this
->getPostDocument());
/* $parseable_valid_request_body_2 = Json::encode($this->getNormalizedPostEntity()); */
$parseable_invalid_request_body_missing_type = Json::encode($this
->removeResourceTypeFromDocument($this
->getPostDocument(), 'type'));
$parseable_invalid_request_body = Json::encode($this
->makeNormalizationInvalid($this
->getPostDocument(), 'label'));
$parseable_invalid_request_body_2 = Json::encode(NestedArray::mergeDeep([
'data' => [
'id' => $this
->randomMachineName(129),
],
], $this
->getPostDocument()));
$parseable_invalid_request_body_3 = Json::encode(NestedArray::mergeDeep([
'data' => [
'attributes' => [
'field_rest_test' => $this
->randomString(),
],
],
], $this
->getPostDocument()));
$parseable_invalid_request_body_4 = Json::encode(NestedArray::mergeDeep([
'data' => [
'attributes' => [
'field_nonexistent' => $this
->randomString(),
],
],
], $this
->getPostDocument()));
// The URL and Guzzle request options that will be used in this test. The
// request options will be modified/expanded throughout this test:
// - to first test all mistakes a developer might make, and assert that the
// error responses provide a good DX
// - to eventually result in a well-formed request that succeeds.
$url = Url::fromRoute(sprintf('jsonapi.%s.collection', static::$resourceTypeName));
$request_options = [];
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$request_options = NestedArray::mergeDeep($request_options, $this
->getAuthenticationRequestOptions());
// @todo Uncomment in https://www.drupal.org/project/jsonapi/issues/2934149.
// @codingStandardsIgnoreStart
/*
// DX: 415 when no Content-Type request header. HTML response because
// missing ?_format query string.
$response = $this->request('POST', $url, $request_options);
$this->assertSame(415, $response->getStatusCode());
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
$this->assertContains('A client error happened', (string) $response->getBody());
$url->setOption('query', ['_format' => 'api_json']);
// DX: 415 when no Content-Type request header.
$response = $this->request('POST', $url, $request_options);
$this->assertResourceErrorResponse(415, '…', 'No "Content-Type" request header specified', $response);
*/
// @codingStandardsIgnoreEnd
$request_options[RequestOptions::HEADERS]['Content-Type'] = '';
// DX: 400 when no request body.
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(400, 'Empty request body.', $response);
$request_options[RequestOptions::BODY] = $unparseable_request_body;
// DX: 400 when unparseable request body.
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(400, 'Syntax error', $response);
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
// DX: 403 when unauthorized.
$response = $this
->request('POST', $url, $request_options);
$reason = $this
->getExpectedUnauthorizedAccessMessage('POST');
$message = trim("The current user is not allowed to POST the selected resource. {$reason}");
$this
->assertResourceErrorResponse(403, $message, $response, '/data');
$this
->setUpAuthorization('POST');
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_missing_type;
// DX: 400 when invalid JSON API request body.
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(400, 'Resource object must include a "type".', $response);
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
// DX: 422 when invalid entity: multiple values sent for single-value field.
$response = $this
->request('POST', $url, $request_options);
$label_field = $this->entity
->getEntityType()
->hasKey('label') ? $this->entity
->getEntityType()
->getKey('label') : static::$labelFieldName;
$label_field_capitalized = $this->entity
->getFieldDefinition($label_field)
->getLabel();
// @todo Remove $expected + assertResourceResponse() in favor of the commented line below once https://www.drupal.org/project/jsonapi/issues/2943176 lands.
$expected_document = [
'errors' => [
[
'title' => 'Unprocessable Entity',
'status' => 422,
'detail' => "{$label_field}: {$label_field_capitalized}: this field cannot hold more than 1 values.",
'code' => 0,
'source' => [
'pointer' => '/data/attributes/' . $label_field,
],
],
],
];
$this
->assertResourceResponse(422, $expected_document, $response);
/* $this->assertResourceErrorResponse(422, "$label_field: $label_field_capitalized: this field cannot hold more than 1 values.", $response, '/data/attributes/' . $label_field); */
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2;
// DX: 403 when invalid entity: UUID field too long.
// @todo Fix this in https://www.drupal.org/node/2149851.
if ($this->entity
->getEntityType()
->hasKey('uuid')) {
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(422, "IDs should be properly generated and formatted UUIDs as described in RFC 4122.", $response);
}
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_3;
// DX: 403 when entity contains field without 'edit' access.
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(403, "The current user is not allowed to POST the selected field (field_rest_test).", $response, '/data/attributes/field_rest_test');
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_4;
// DX: 422 when request document contains non-existent field.
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(422, sprintf("The attribute field_nonexistent does not exist on the %s resource type.", static::$resourceTypeName), $response);
$request_options[RequestOptions::BODY] = $parseable_valid_request_body;
// @todo Uncomment when https://www.drupal.org/project/jsonapi/issues/2934149 lands.
// @codingStandardsIgnoreStart
/*
$request_options[RequestOptions::HEADERS]['Content-Type'] = 'text/xml';
// DX: 415 when request body in existing but not allowed format.
$response = $this->request('POST', $url, $request_options);
$this->assertResourceErrorResponse(415, 'No route found that matches "Content-Type: text/xml"', $response);
*/
// @codingStandardsIgnoreEnd
$request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
// 201 for well-formed request.
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceResponse(201, FALSE, $response);
$this
->assertFalse($response
->hasHeader('X-Drupal-Cache'));
// If the entity is stored, perform extra checks.
if (get_class($this->entityStorage) !== ContentEntityNullStorage::class) {
$uuid = $this->entityStorage
->load(static::$firstCreatedEntityId)
->uuid();
// @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
$location = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
static::$entityTypeId => $uuid,
])
->setAbsolute(TRUE)
->toString();
/* $location = $this->entityStorage->load(static::$firstCreatedEntityId)->toUrl('jsonapi')->setAbsolute(TRUE)->toString(); */
$this
->assertSame([
$location,
], $response
->getHeader('Location'));
// Assert that the entity was indeed created, and that the response body
// contains the serialized created entity.
$created_entity = $this->entityStorage
->loadUnchanged(static::$firstCreatedEntityId);
$created_entity_document = $this
->normalize($created_entity, $url);
// @todo Remove this if-test in https://www.drupal.org/node/2543726: execute
// its body unconditionally.
if (static::$entityTypeId !== 'taxonomy_term') {
$decoded_response_body = Json::decode((string) $response
->getBody());
$this
->assertSame($created_entity_document, $decoded_response_body);
}
// Assert that the entity was indeed created using the POSTed values.
foreach ($this
->getPostDocument()['data']['attributes'] as $field_name => $field_normalization) {
// If the value is an array of properties, only verify that the sent
// properties are present, the server could be computing additional
// properties.
if (is_array($field_normalization)) {
$this
->assertArraySubset($field_normalization, $created_entity_document['data']['attributes'][$field_name]);
}
else {
$this
->assertSame($field_normalization, $created_entity_document['data']['attributes'][$field_name]);
}
}
if (isset($this
->getPostDocument()['data']['relationships'])) {
foreach ($this
->getPostDocument()['data']['relationships'] as $field_name => $relationship_field_normalization) {
// POSTing relationships: 'data' is required, 'links' is optional.
static::recursiveKsort($relationship_field_normalization);
static::recursiveKsort($created_entity_document['data']['relationships'][$field_name]);
$this
->assertSame($relationship_field_normalization, array_diff_key($created_entity_document['data']['relationships'][$field_name], [
'links' => TRUE,
]));
}
}
}
else {
$this
->assertFalse($response
->hasHeader('Location'));
}
// 201 for well-formed request that creates another entity.
// If the entity is stored, delete the first created entity (in case there
// is a uniqueness constraint).
if (get_class($this->entityStorage) !== ContentEntityNullStorage::class) {
$this->entityStorage
->load(static::$firstCreatedEntityId)
->delete();
}
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceResponse(201, FALSE, $response);
$this
->assertFalse($response
->hasHeader('X-Drupal-Cache'));
if ($this->entity
->getEntityType()
->getStorageClass() !== ContentEntityNullStorage::class && $this->entity
->getEntityType()
->hasKey('uuid')) {
$uuid = $this->entityStorage
->load(static::$secondCreatedEntityId)
->uuid();
// @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
$location = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), [
static::$entityTypeId => $uuid,
])
->setAbsolute(TRUE)
->toString();
/* $location = $this->entityStorage->load(static::$secondCreatedEntityId)->toUrl('jsonapi')->setAbsolute(TRUE)->toString(); */
$this
->assertSame([
$location,
], $response
->getHeader('Location'));
// 500 when creating an entity with a duplicate UUID.
$doc = $this
->getModifiedEntityForPostTesting();
$doc['data']['id'] = $uuid;
$doc['data']['attributes'][$label_field] = [
[
'value' => $this
->randomMachineName(),
],
];
$request_options[RequestOptions::BODY] = Json::encode($doc);
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceErrorResponse(409, 'Conflict: Entity already exists.', $response);
// 201 when successfully creating an entity with a new UUID.
$doc = $this
->getModifiedEntityForPostTesting();
$new_uuid = \Drupal::service('uuid')
->generate();
$doc['data']['id'] = $new_uuid;
$doc['data']['attributes'][$label_field] = [
[
'value' => $this
->randomMachineName(),
],
];
$request_options[RequestOptions::BODY] = Json::encode($doc);
$response = $this
->request('POST', $url, $request_options);
$this
->assertResourceResponse(201, FALSE, $response);
$entities = $this->entityStorage
->loadByProperties([
'uuid' => $new_uuid,
]);
$new_entity = reset($entities);
$this
->assertNotNull($new_entity);
$new_entity
->delete();
}
else {
$this
->assertFalse($response
->hasHeader('Location'));
}
}