comparison core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalMultilingualTest.php @ 5:12f9dff5fda9 tip

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