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. 10 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 571

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.