annotate core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalMultilingualTest.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@18 1 <?php
Chris@18 2
Chris@18 3 namespace Drupal\Tests\jsonapi\Functional;
Chris@18 4
Chris@18 5 use Drupal\Component\Serialization\Json;
Chris@18 6 use Drupal\Core\Url;
Chris@18 7 use Drupal\language\Entity\ConfigurableLanguage;
Chris@18 8 use Drupal\language\Entity\ContentLanguageSettings;
Chris@18 9 use Drupal\node\Entity\Node;
Chris@18 10 use Drupal\user\Entity\Role;
Chris@18 11 use Drupal\user\RoleInterface;
Chris@18 12 use GuzzleHttp\RequestOptions;
Chris@18 13
Chris@18 14 /**
Chris@18 15 * Tests JSON:API multilingual support.
Chris@18 16 *
Chris@18 17 * @group jsonapi
Chris@18 18 * @group legacy
Chris@18 19 *
Chris@18 20 * @internal
Chris@18 21 */
Chris@18 22 class JsonApiFunctionalMultilingualTest extends JsonApiFunctionalTestBase {
Chris@18 23
Chris@18 24 /**
Chris@18 25 * {@inheritdoc}
Chris@18 26 */
Chris@18 27 public static $modules = [
Chris@18 28 'language',
Chris@18 29 ];
Chris@18 30
Chris@18 31 /**
Chris@18 32 * {@inheritdoc}
Chris@18 33 */
Chris@18 34 protected function setUp() {
Chris@18 35 parent::setUp();
Chris@18 36 $language = ConfigurableLanguage::createFromLangcode('ca');
Chris@18 37 $language->save();
Chris@18 38 ConfigurableLanguage::createFromLangcode('ca-fr')->save();
Chris@18 39
Chris@18 40 // In order to reflect the changes for a multilingual site in the container
Chris@18 41 // we have to rebuild it.
Chris@18 42 $this->rebuildContainer();
Chris@18 43
Chris@18 44 \Drupal::configFactory()->getEditable('language.negotiation')
Chris@18 45 ->set('url.prefixes.ca', 'ca')
Chris@18 46 ->set('url.prefixes.ca-fr', 'ca-fr')
Chris@18 47 ->save();
Chris@18 48
Chris@18 49 $this->createDefaultContent(5, 5, TRUE, TRUE, static::IS_MULTILINGUAL, FALSE);
Chris@18 50 }
Chris@18 51
Chris@18 52 /**
Chris@18 53 * Tests reading multilingual content.
Chris@18 54 */
Chris@18 55 public function testReadMultilingual() {
Chris@18 56 // Different databases have different sort orders, so a sort is required so
Chris@18 57 // test expectations do not need to vary per database.
Chris@18 58 $default_sort = ['sort' => 'drupal_internal__nid'];
Chris@18 59
Chris@18 60 // Test reading an individual entity translation.
Chris@18 61 $output = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $this->nodes[0]->uuid(), ['query' => ['include' => 'field_tags,field_image'] + $default_sort]));
Chris@18 62 $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data']['attributes']['title']);
Chris@18 63 $this->assertSame('ca', $output['data']['attributes']['langcode']);
Chris@18 64 $included_tags = array_filter($output['included'], function ($entry) {
Chris@18 65 return $entry['type'] === 'taxonomy_term--tags';
Chris@18 66 });
Chris@18 67 $tag_name = $this->nodes[0]->get('field_tags')->entity
Chris@18 68 ->getTranslation('ca')->getName();
Chris@18 69 $this->assertEquals($tag_name, reset($included_tags)['attributes']['name']);
Chris@18 70 $alt = $this->nodes[0]->getTranslation('ca')->get('field_image')->alt;
Chris@18 71 $this->assertSame($alt, $output['data']['relationships']['field_image']['data']['meta']['alt']);
Chris@18 72
Chris@18 73 // Test reading an individual entity fallback.
Chris@18 74 $output = Json::decode($this->drupalGet('/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()));
Chris@18 75 $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data']['attributes']['title']);
Chris@18 76
Chris@18 77 $output = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $this->nodes[0]->uuid(), ['query' => $default_sort]));
Chris@18 78 $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data']['attributes']['title']);
Chris@18 79
Chris@18 80 // Test reading a collection of entities.
Chris@18 81 $output = Json::decode($this->drupalGet('/ca/jsonapi/node/article', ['query' => $default_sort]));
Chris@18 82 $this->assertEquals($this->nodes[0]->getTranslation('ca')->getTitle(), $output['data'][0]['attributes']['title']);
Chris@18 83 }
Chris@18 84
Chris@18 85 /**
Chris@18 86 * Tests updating a translation.
Chris@18 87 */
Chris@18 88 public function testPatchTranslation() {
Chris@18 89 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 90 $node = $this->nodes[0];
Chris@18 91 $uuid = $node->uuid();
Chris@18 92
Chris@18 93 // Assert the precondition: the 'ca' translation has a different title.
Chris@18 94 $document = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid));
Chris@18 95 $document_ca = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $uuid));
Chris@18 96 $this->assertSame('en', $document['data']['attributes']['langcode']);
Chris@18 97 $this->assertSame('ca', $document_ca['data']['attributes']['langcode']);
Chris@18 98 $this->assertSame($node->getTitle(), $document['data']['attributes']['title']);
Chris@18 99 $this->assertSame($node->getTitle() . ' (ca)', $document_ca['data']['attributes']['title']);
Chris@18 100
Chris@18 101 // PATCH the 'ca' translation.
Chris@18 102 $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [
Chris@18 103 'bypass node access',
Chris@18 104 ]);
Chris@18 105 $request_options = [];
Chris@18 106 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
Chris@18 107 $request_options[RequestOptions::BODY] = Json::encode([
Chris@18 108 'data' => [
Chris@18 109 'type' => 'node--article',
Chris@18 110 'id' => $uuid,
Chris@18 111 'attributes' => [
Chris@18 112 'title' => $document_ca['data']['attributes']['title'] . ' UPDATED',
Chris@18 113 ],
Chris@18 114 ],
Chris@18 115 ]);
Chris@18 116 $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 117 $this->assertSame(200, $response->getStatusCode());
Chris@18 118
Chris@18 119 // Assert the postcondition: only the 'ca' translation has an updated title.
Chris@18 120 $document_updated = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid));
Chris@18 121 $document_ca_updated = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $uuid));
Chris@18 122 $this->assertSame('en', $document_updated['data']['attributes']['langcode']);
Chris@18 123 $this->assertSame('ca', $document_ca_updated['data']['attributes']['langcode']);
Chris@18 124 $this->assertSame($node->getTitle(), $document_updated['data']['attributes']['title']);
Chris@18 125 $this->assertSame($node->getTitle() . ' (ca) UPDATED', $document_ca_updated['data']['attributes']['title']);
Chris@18 126
Chris@18 127 // Specifying a langcode is not allowed by default.
Chris@18 128 $request_options[RequestOptions::BODY] = Json::encode([
Chris@18 129 'data' => [
Chris@18 130 'type' => 'node--article',
Chris@18 131 'id' => $uuid,
Chris@18 132 'attributes' => [
Chris@18 133 'langcode' => 'ca-fr',
Chris@18 134 ],
Chris@18 135 ],
Chris@18 136 ]);
Chris@18 137 $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 138 $this->assertSame(403, $response->getStatusCode());
Chris@18 139
Chris@18 140 // Specifying a langcode is allowed once configured to be alterable. But
Chris@18 141 // modifying the language of a non-default translation is still not allowed.
Chris@18 142 ContentLanguageSettings::create([
Chris@18 143 'target_entity_type_id' => 'node',
Chris@18 144 'target_bundle' => 'article',
Chris@18 145 ])->setLanguageAlterable(TRUE)
Chris@18 146 ->save();
Chris@18 147 $response = $this->request('PATCH', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 148 $this->assertSame(500, $response->getStatusCode());
Chris@18 149 $document = Json::decode((string) $response->getBody());
Chris@18 150 $this->assertSame('The translation language cannot be changed (ca).', $document['errors'][0]['detail']);
Chris@18 151
Chris@18 152 // Changing the langcode of the default ('en') translation is possible:
Chris@18 153 // first verify that it currently is 'en', then change it to 'ca-fr', and
Chris@18 154 // verify that the the title is unchanged, but the langcode is updated.
Chris@18 155 $response = $this->request('GET', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 156 $this->assertSame(200, $response->getStatusCode());
Chris@18 157 $document = Json::decode((string) $response->getBody());
Chris@18 158 $this->assertSame($node->getTitle(), $document['data']['attributes']['title']);
Chris@18 159 $this->assertSame('en', $document['data']['attributes']['langcode']);
Chris@18 160 $response = $this->request('PATCH', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 161 $this->assertSame(200, $response->getStatusCode());
Chris@18 162 $document = Json::decode((string) $response->getBody());
Chris@18 163 $this->assertSame($node->getTitle(), $document['data']['attributes']['title']);
Chris@18 164 $this->assertSame('ca-fr', $document['data']['attributes']['langcode']);
Chris@18 165
Chris@18 166 // Finally: assert the postcondition of all installed languages.
Chris@18 167 // - When GETting the 'en' translation, we get 'ca-fr', since the 'en'
Chris@18 168 // translation doesn't exist anymore.
Chris@18 169 $response = $this->request('GET', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 170 $document = Json::decode((string) $response->getBody());
Chris@18 171 $this->assertSame('ca-fr', $document['data']['attributes']['langcode']);
Chris@18 172 $this->assertSame($node->getTitle(), $document['data']['attributes']['title']);
Chris@18 173 // - When GETting the 'ca' translation, we still get the 'ca' one.
Chris@18 174 $response = $this->request('GET', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 175 $document = Json::decode((string) $response->getBody());
Chris@18 176 $this->assertSame('ca', $document['data']['attributes']['langcode']);
Chris@18 177 $this->assertSame($node->getTitle() . ' (ca) UPDATED', $document['data']['attributes']['title']);
Chris@18 178 // - When GETting the 'ca-fr' translation, we now get the default
Chris@18 179 // translation.
Chris@18 180 $response = $this->request('GET', Url::fromUri('base:/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 181 $document = Json::decode((string) $response->getBody());
Chris@18 182 $this->assertSame('ca-fr', $document['data']['attributes']['langcode']);
Chris@18 183 $this->assertSame($node->getTitle(), $document['data']['attributes']['title']);
Chris@18 184 }
Chris@18 185
Chris@18 186 /**
Chris@18 187 * Tests updating a translation fallback.
Chris@18 188 */
Chris@18 189 public function testPatchTranslationFallback() {
Chris@18 190 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 191 $node = $this->nodes[0];
Chris@18 192 $uuid = $node->uuid();
Chris@18 193
Chris@18 194 // Assert the precondition: 'ca-fr' falls back to the 'ca' translation which
Chris@18 195 // has a different title.
Chris@18 196 $document = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid));
Chris@18 197 $document_ca = Json::decode($this->drupalGet('/ca/jsonapi/node/article/' . $uuid));
Chris@18 198 $document_cafr = Json::decode($this->drupalGet('/ca-fr/jsonapi/node/article/' . $uuid));
Chris@18 199 $this->assertSame('en', $document['data']['attributes']['langcode']);
Chris@18 200 $this->assertSame('ca', $document_ca['data']['attributes']['langcode']);
Chris@18 201 $this->assertSame('ca', $document_cafr['data']['attributes']['langcode']);
Chris@18 202 $this->assertSame($node->getTitle(), $document['data']['attributes']['title']);
Chris@18 203 $this->assertSame($node->getTitle() . ' (ca)', $document_ca['data']['attributes']['title']);
Chris@18 204 $this->assertSame($node->getTitle() . ' (ca)', $document_cafr['data']['attributes']['title']);
Chris@18 205
Chris@18 206 // PATCH the 'ca-fr' translation.
Chris@18 207 $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [
Chris@18 208 'bypass node access',
Chris@18 209 ]);
Chris@18 210 $request_options = [];
Chris@18 211 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
Chris@18 212 $request_options[RequestOptions::BODY] = Json::encode([
Chris@18 213 'data' => [
Chris@18 214 'type' => 'node--article',
Chris@18 215 'id' => $uuid,
Chris@18 216 'attributes' => [
Chris@18 217 'title' => $document_cafr['data']['attributes']['title'] . ' UPDATED',
Chris@18 218 ],
Chris@18 219 ],
Chris@18 220 ]);
Chris@18 221 $response = $this->request('PATCH', Url::fromUri('base:/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()), $request_options);
Chris@18 222 $this->assertSame(405, $response->getStatusCode());
Chris@18 223 $document = Json::decode((string) $response->getBody());
Chris@18 224 $this->assertSame('The requested translation of the resource object does not exist, instead modify one of the translations that do exist: ca, en.', $document['errors'][0]['detail']);
Chris@18 225 }
Chris@18 226
Chris@18 227 /**
Chris@18 228 * Tests creating a translation.
Chris@18 229 */
Chris@18 230 public function testPostTranslation() {
Chris@18 231 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 232 $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [
Chris@18 233 'bypass node access',
Chris@18 234 ]);
Chris@18 235
Chris@18 236 $title = 'Llamas FTW (ca)';
Chris@18 237 $request_document = [
Chris@18 238 'data' => [
Chris@18 239 'type' => 'node--article',
Chris@18 240 'attributes' => [
Chris@18 241 'title' => $title,
Chris@18 242 'langcode' => 'ca',
Chris@18 243 ],
Chris@18 244 ],
Chris@18 245 ];
Chris@18 246
Chris@18 247 $request_options = [];
Chris@18 248 $request_options[RequestOptions::HEADERS]['Content-Type'] = 'application/vnd.api+json';
Chris@18 249
Chris@18 250 // Specifying a langcode is forbidden by language_entity_field_access().
Chris@18 251 $request_options[RequestOptions::BODY] = Json::encode($request_document);
Chris@18 252 $response = $this->request('POST', Url::fromUri('base:/ca/jsonapi/node/article/'), $request_options);
Chris@18 253 $this->assertSame(403, $response->getStatusCode());
Chris@18 254 $document = Json::decode((string) $response->getBody());
Chris@18 255 $this->assertSame('The current user is not allowed to POST the selected field (langcode).', $document['errors'][0]['detail']);
Chris@18 256
Chris@18 257 // Omitting a langcode results in an entity in 'en': the default language of
Chris@18 258 // the site.
Chris@18 259 unset($request_document['data']['attributes']['langcode']);
Chris@18 260 $request_options[RequestOptions::BODY] = Json::encode($request_document);
Chris@18 261 $response = $this->request('POST', Url::fromUri('base:/ca/jsonapi/node/article/'), $request_options);
Chris@18 262 $this->assertSame(201, $response->getStatusCode());
Chris@18 263 $document = Json::decode((string) $response->getBody());
Chris@18 264 $this->assertSame($title, $document['data']['attributes']['title']);
Chris@18 265 $this->assertSame('en', $document['data']['attributes']['langcode']);
Chris@18 266 $this->assertSame(['en'], array_keys(Node::load($document['data']['attributes']['drupal_internal__nid'])->getTranslationLanguages()));
Chris@18 267
Chris@18 268 // Specifying a langcode is allowed once configured to be alterable. Now an
Chris@18 269 // entity can be created with the specified langcode.
Chris@18 270 ContentLanguageSettings::create([
Chris@18 271 'target_entity_type_id' => 'node',
Chris@18 272 'target_bundle' => 'article',
Chris@18 273 ])->setLanguageAlterable(TRUE)
Chris@18 274 ->save();
Chris@18 275 $request_document['data']['attributes']['langcode'] = 'ca';
Chris@18 276 $request_options[RequestOptions::BODY] = Json::encode($request_document);
Chris@18 277 $response = $this->request('POST', Url::fromUri('base:/ca/jsonapi/node/article/'), $request_options);
Chris@18 278 $this->assertSame(201, $response->getStatusCode());
Chris@18 279 $document = Json::decode((string) $response->getBody());
Chris@18 280 $this->assertSame($title, $document['data']['attributes']['title']);
Chris@18 281 $this->assertSame('ca', $document['data']['attributes']['langcode']);
Chris@18 282 $this->assertSame(['ca'], array_keys(Node::load($document['data']['attributes']['drupal_internal__nid'])->getTranslationLanguages()));
Chris@18 283
Chris@18 284 // Same request, but sent to the URL without the language prefix.
Chris@18 285 $response = $this->request('POST', Url::fromUri('base:/jsonapi/node/article/'), $request_options);
Chris@18 286 $this->assertSame(201, $response->getStatusCode());
Chris@18 287 $document = Json::decode((string) $response->getBody());
Chris@18 288 $this->assertSame($title, $document['data']['attributes']['title']);
Chris@18 289 $this->assertSame('ca', $document['data']['attributes']['langcode']);
Chris@18 290 $this->assertSame(['ca'], array_keys(Node::load($document['data']['attributes']['drupal_internal__nid'])->getTranslationLanguages()));
Chris@18 291 }
Chris@18 292
Chris@18 293 /**
Chris@18 294 * Tests deleting multilingual content.
Chris@18 295 */
Chris@18 296 public function testDeleteMultilingual() {
Chris@18 297 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 298 $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), [
Chris@18 299 'bypass node access',
Chris@18 300 ]);
Chris@18 301
Chris@18 302 $response = $this->request('DELETE', Url::fromUri('base:/ca/jsonapi/node/article/' . $this->nodes[0]->uuid()), []);
Chris@18 303 $this->assertSame(405, $response->getStatusCode());
Chris@18 304 $document = Json::decode((string) $response->getBody());
Chris@18 305 $this->assertSame('Deleting a resource object translation is not yet supported. See https://www.drupal.org/docs/8/modules/jsonapi/translations.', $document['errors'][0]['detail']);
Chris@18 306
Chris@18 307 $response = $this->request('DELETE', Url::fromUri('base:/ca-fr/jsonapi/node/article/' . $this->nodes[0]->uuid()), []);
Chris@18 308 $this->assertSame(405, $response->getStatusCode());
Chris@18 309 $document = Json::decode((string) $response->getBody());
Chris@18 310 $this->assertSame('Deleting a resource object translation is not yet supported. See https://www.drupal.org/docs/8/modules/jsonapi/translations.', $document['errors'][0]['detail']);
Chris@18 311
Chris@18 312 $response = $this->request('DELETE', Url::fromUri('base:/jsonapi/node/article/' . $this->nodes[0]->uuid()), []);
Chris@18 313 $this->assertSame(204, $response->getStatusCode());
Chris@18 314 $this->assertFalse(Node::load($this->nodes[0]->id()));
Chris@18 315 }
Chris@18 316
Chris@18 317 }