function EntityResourceTestBase::testPost

Same name in other branches
  1. 9 core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPost()
  2. 8.9.x core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPost()
  3. 11.x core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase::testPost()

Tests a POST request for an entity, plus edge cases to ensure good DX.

1 call to EntityResourceTestBase::testPost()
MediaResourceTestBase::testPost in core/modules/media/tests/src/Functional/Rest/MediaResourceTestBase.php
Tests a POST request for an entity, plus edge cases to ensure good DX.
2 methods override EntityResourceTestBase::testPost()
EntityTestComputedFieldNormalizerTest::testPost in core/modules/system/tests/modules/entity_test/tests/src/Functional/Rest/EntityTestComputedFieldNormalizerTest.php
Tests a POST request for an entity, plus edge cases to ensure good DX.
MediaResourceTestBase::testPost in core/modules/media/tests/src/Functional/Rest/MediaResourceTestBase.php
Tests a POST request for an entity, plus edge cases to ensure good DX.

File

core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php, line 697

Class

EntityResourceTestBase
Defines a base class for testing all entity resources.

Namespace

Drupal\Tests\rest\Functional\EntityResource

Code

public function testPost() : void {
    // @todo Remove this in https://www.drupal.org/node/2300677.
    if ($this->entity instanceof ConfigEntityInterface) {
        $this->markTestSkipped('POSTing config entities is not yet supported.');
    }
    $this->initAuthentication();
    $has_canonical_url = $this->entity
        ->hasLinkTemplate('canonical');
    // Try with all of the following request bodies.
    $not_parseable_request_body = '!{>}<';
    $parseable_valid_request_body = $this->serializer
        ->encode($this->getNormalizedPostEntity(), static::$format);
    $parseable_invalid_request_body = $this->serializer
        ->encode($this->makeNormalizationInvalid($this->getNormalizedPostEntity(), 'label'), static::$format);
    $parseable_invalid_request_body_2 = $this->serializer
        ->encode($this->getNormalizedPostEntity() + [
        'uuid' => [
            $this->randomMachineName(129),
        ],
    ], static::$format);
    $parseable_invalid_request_body_3 = $this->serializer
        ->encode($this->getNormalizedPostEntity() + [
        'field_rest_test' => [
            [
                'value' => $this->randomString(),
            ],
        ],
    ], static::$format);
    // 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 = $this->getEntityResourcePostUrl();
    $request_options = [];
    // DX: 404 when resource not provisioned. HTML response because missing
    // ?_format query string.
    $response = $this->request('POST', $url, $request_options);
    $this->assertSame(404, $response->getStatusCode());
    $this->assertSame([
        'text/html; charset=UTF-8',
    ], $response->getHeader('Content-Type'));
    $url->setOption('query', [
        '_format' => static::$format,
    ]);
    // DX: 404 when resource not provisioned.
    $response = $this->request('POST', $url, $request_options);
    $this->assertResourceErrorResponse(404, 'No route found for "POST ' . $this->getEntityResourcePostUrl()
        ->setAbsolute()
        ->toString() . '"', $response);
    $this->provisionEntityResource();
    // Simulate the developer again forgetting the ?_format query string.
    $url->setOption('query', []);
    // 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->assertStringContainsString('A client error happened', (string) $response->getBody());
    $url->setOption('query', [
        '_format' => static::$format,
    ]);
    // 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);
    $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
    if (static::$auth) {
        // DX: forgetting authentication: authentication provider-specific error
        // response.
        $response = $this->request('POST', $url, $request_options);
        $this->assertResponseWhenMissingAuthentication('POST', $response);
    }
    $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('POST'));
    // DX: 403 when unauthorized.
    $response = $this->request('POST', $url, $request_options);
    $this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response);
    $this->setUpAuthorization('POST');
    // DX: 400 when no request body.
    $response = $this->request('POST', $url, $request_options);
    $this->assertResourceErrorResponse(400, 'No entity content received.', $response);
    $request_options[RequestOptions::BODY] = $not_parseable_request_body;
    // DX: 400 when un-parseable request body.
    $response = $this->request('POST', $url, $request_options);
    $this->assertResourceErrorResponse(400, 'Syntax error', $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);
    if ($label_field = $this->entity
        ->getEntityType()
        ->hasKey('label') ? $this->entity
        ->getEntityType()
        ->getKey('label') : static::$labelFieldName) {
        $label_field_capitalized = $this->entity
            ->getFieldDefinition($label_field)
            ->getLabel();
        $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n{$label_field}: {$label_field_capitalized}: this field cannot hold more than 1 values.\n", $response);
    }
    $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2;
    // DX: 422 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, "Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\n", $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, "Access denied on creating field 'field_rest_test'.", $response);
    $request_options[RequestOptions::BODY] = $parseable_valid_request_body;
    // Before sending a well-formed request, allow the normalization and
    // authentication provider edge cases to also be tested.
    $this->assertNormalizationEdgeCases('POST', $url, $request_options);
    $this->assertAuthenticationEdgeCases('POST', $url, $request_options);
    $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);
    $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
    // 201 for well-formed request.
    $response = $this->request('POST', $url, $request_options);
    $this->assertResourceResponse(201, FALSE, $response);
    if ($has_canonical_url) {
        $location = $this->entityStorage
            ->load(static::$firstCreatedEntityId)
            ->toUrl('canonical')
            ->setAbsolute(TRUE)
            ->toString();
        $this->assertSame([
            $location,
        ], $response->getHeader('Location'));
    }
    else {
        $this->assertSame([], $response->getHeader('Location'));
    }
    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
    // If the entity is stored, perform extra checks.
    if (get_class($this->entityStorage) !== ContentEntityNullStorage::class) {
        // 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_normalization = $this->serializer
            ->normalize($created_entity, static::$format, [
            'account' => $this->account,
        ]);
        $this->assertSame($created_entity_normalization, $this->serializer
            ->decode((string) $response->getBody(), static::$format));
        $this->assertStoredEntityMatchesSentNormalization($this->getNormalizedPostEntity(), $created_entity);
    }
    if ($this->entity
        ->getEntityType()
        ->getStorageClass() !== ContentEntityNullStorage::class && $this->entity
        ->getEntityType()
        ->hasKey('uuid')) {
        // 500 when creating an entity with a duplicate UUID.
        $normalized_entity = $this->getModifiedEntityForPostTesting();
        $normalized_entity[$created_entity->getEntityType()
            ->getKey('uuid')] = [
            [
                'value' => $created_entity->uuid(),
            ],
        ];
        if ($label_field) {
            $normalized_entity[$label_field] = [
                [
                    'value' => $this->randomMachineName(),
                ],
            ];
        }
        $request_options[RequestOptions::BODY] = $this->serializer
            ->encode($normalized_entity, static::$format);
        $response = $this->request('POST', $url, $request_options);
        $this->assertSame(500, $response->getStatusCode());
        $this->assertStringContainsString('Internal Server Error', (string) $response->getBody());
        // 201 when successfully creating an entity with a new UUID.
        $normalized_entity = $this->getModifiedEntityForPostTesting();
        $new_uuid = \Drupal::service('uuid')->generate();
        $normalized_entity[$created_entity->getEntityType()
            ->getKey('uuid')] = [
            [
                'value' => $new_uuid,
            ],
        ];
        if ($label_field) {
            $normalized_entity[$label_field] = [
                [
                    'value' => $this->randomMachineName(),
                ],
            ];
        }
        $request_options[RequestOptions::BODY] = $this->serializer
            ->encode($normalized_entity, static::$format);
        $response = $this->request('POST', $url, $request_options);
        $this->assertResourceResponse(201, FALSE, $response);
        $entities = $this->entityStorage
            ->loadByProperties([
            $created_entity->getEntityType()
                ->getKey('uuid') => $new_uuid,
        ]);
        $new_entity = reset($entities);
        $this->assertNotNull($new_entity);
        $new_entity->delete();
    }
}

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