annotate core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTest.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\jsonapi\Query\OffsetPage;
Chris@18 8 use Drupal\node\Entity\Node;
Chris@18 9
Chris@18 10 /**
Chris@18 11 * General functional test class.
Chris@18 12 *
Chris@18 13 * @group jsonapi
Chris@18 14 * @group legacy
Chris@18 15 *
Chris@18 16 * @internal
Chris@18 17 */
Chris@18 18 class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
Chris@18 19
Chris@18 20 /**
Chris@18 21 * {@inheritdoc}
Chris@18 22 */
Chris@18 23 public static $modules = [
Chris@18 24 'basic_auth',
Chris@18 25 ];
Chris@18 26
Chris@18 27 /**
Chris@18 28 * Test the GET method.
Chris@18 29 */
Chris@18 30 public function testRead() {
Chris@18 31 $this->createDefaultContent(61, 5, TRUE, TRUE, static::IS_NOT_MULTILINGUAL, FALSE);
Chris@18 32 // Unpublish the last entity, so we can check access.
Chris@18 33 $this->nodes[60]->setUnpublished()->save();
Chris@18 34
Chris@18 35 // Different databases have different sort orders, so a sort is required so
Chris@18 36 // test expectations do not need to vary per database.
Chris@18 37 $default_sort = ['sort' => 'drupal_internal__nid'];
Chris@18 38
Chris@18 39 // 0. HEAD request allows a client to verify that JSON:API is installed.
Chris@18 40 $this->httpClient->request('HEAD', $this->buildUrl('/jsonapi/node/article'));
Chris@18 41 $this->assertSession()->statusCodeEquals(200);
Chris@18 42 // 1. Load all articles (1st page).
Chris@18 43 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 44 'query' => $default_sort,
Chris@18 45 ]));
Chris@18 46 $this->assertSession()->statusCodeEquals(200);
Chris@18 47 $this->assertEquals(OffsetPage::SIZE_MAX, count($collection_output['data']));
Chris@18 48 $this->assertSession()
Chris@18 49 ->responseHeaderEquals('Content-Type', 'application/vnd.api+json');
Chris@18 50 // 2. Load all articles (Offset 3).
Chris@18 51 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 52 'query' => ['page' => ['offset' => 3]] + $default_sort,
Chris@18 53 ]));
Chris@18 54 $this->assertSession()->statusCodeEquals(200);
Chris@18 55 $this->assertEquals(OffsetPage::SIZE_MAX, count($collection_output['data']));
Chris@18 56 $this->assertContains('page%5Boffset%5D=53', $collection_output['links']['next']['href']);
Chris@18 57 // 3. Load all articles (1st page, 2 items)
Chris@18 58 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 59 'query' => ['page' => ['limit' => 2]] + $default_sort,
Chris@18 60 ]));
Chris@18 61 $this->assertSession()->statusCodeEquals(200);
Chris@18 62 $this->assertEquals(2, count($collection_output['data']));
Chris@18 63 // 4. Load all articles (2nd page, 2 items).
Chris@18 64 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 65 'query' => [
Chris@18 66 'page' => [
Chris@18 67 'limit' => 2,
Chris@18 68 'offset' => 2,
Chris@18 69 ],
Chris@18 70 ] + $default_sort,
Chris@18 71 ]));
Chris@18 72 $this->assertSession()->statusCodeEquals(200);
Chris@18 73 $this->assertEquals(2, count($collection_output['data']));
Chris@18 74 $this->assertContains('page%5Boffset%5D=4', $collection_output['links']['next']['href']);
Chris@18 75 // 5. Single article.
Chris@18 76 $uuid = $this->nodes[0]->uuid();
Chris@18 77 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid));
Chris@18 78 $this->assertSession()->statusCodeEquals(200);
Chris@18 79 $this->assertArrayHasKey('type', $single_output['data']);
Chris@18 80 $this->assertEquals($this->nodes[0]->getTitle(), $single_output['data']['attributes']['title']);
Chris@18 81
Chris@18 82 // 5.1 Single article with access denied because unauthenticated.
Chris@18 83 Json::decode($this->drupalGet('/jsonapi/node/article/' . $this->nodes[60]->uuid()));
Chris@18 84 $this->assertSession()->statusCodeEquals(401);
Chris@18 85
Chris@18 86 // 5.1 Single article with access denied while authenticated.
Chris@18 87 $this->drupalLogin($this->userCanViewProfiles);
Chris@18 88 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $this->nodes[60]->uuid()));
Chris@18 89 $this->assertSession()->statusCodeEquals(403);
Chris@18 90 $this->assertEquals('/data', $single_output['errors'][0]['source']['pointer']);
Chris@18 91 $this->drupalLogout();
Chris@18 92
Chris@18 93 // 6. Single relationship item.
Chris@18 94 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid . '/relationships/node_type'));
Chris@18 95 $this->assertSession()->statusCodeEquals(200);
Chris@18 96 $this->assertArrayHasKey('type', $single_output['data']);
Chris@18 97 $this->assertArrayNotHasKey('attributes', $single_output['data']);
Chris@18 98 $this->assertArrayHasKey('related', $single_output['links']);
Chris@18 99 // 7. Single relationship image.
Chris@18 100 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid . '/relationships/field_image'));
Chris@18 101 $this->assertSession()->statusCodeEquals(200);
Chris@18 102 $this->assertArrayHasKey('type', $single_output['data']);
Chris@18 103 $this->assertArrayNotHasKey('attributes', $single_output['data']);
Chris@18 104 $this->assertArrayHasKey('related', $single_output['links']);
Chris@18 105 // 8. Multiple relationship item.
Chris@18 106 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid . '/relationships/field_tags'));
Chris@18 107 $this->assertSession()->statusCodeEquals(200);
Chris@18 108 $this->assertArrayHasKey('type', $single_output['data'][0]);
Chris@18 109 $this->assertArrayNotHasKey('attributes', $single_output['data'][0]);
Chris@18 110 $this->assertArrayHasKey('related', $single_output['links']);
Chris@18 111 // 8b. Single related item, empty.
Chris@18 112 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid . '/field_heroless'));
Chris@18 113 $this->assertSession()->statusCodeEquals(200);
Chris@18 114 $this->assertSame(NULL, $single_output['data']);
Chris@18 115 // 9. Related tags with includes.
Chris@18 116 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid . '/field_tags', [
Chris@18 117 'query' => ['include' => 'vid'],
Chris@18 118 ]));
Chris@18 119 $this->assertSession()->statusCodeEquals(200);
Chris@18 120 $this->assertEquals('taxonomy_term--tags', $single_output['data'][0]['type']);
Chris@18 121 $this->assertArrayNotHasKey('tid', $single_output['data'][0]['attributes']);
Chris@18 122 $this->assertContains(
Chris@18 123 '/taxonomy_term/tags/',
Chris@18 124 $single_output['data'][0]['links']['self']['href']
Chris@18 125 );
Chris@18 126 $this->assertEquals(
Chris@18 127 'taxonomy_vocabulary--taxonomy_vocabulary',
Chris@18 128 $single_output['included'][0]['type']
Chris@18 129 );
Chris@18 130 // 10. Single article with includes.
Chris@18 131 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid, [
Chris@18 132 'query' => ['include' => 'uid,field_tags'],
Chris@18 133 ]));
Chris@18 134 $this->assertSession()->statusCodeEquals(200);
Chris@18 135 $this->assertEquals('node--article', $single_output['data']['type']);
Chris@18 136 $first_include = reset($single_output['included']);
Chris@18 137 $this->assertEquals(
Chris@18 138 'user--user',
Chris@18 139 $first_include['type']
Chris@18 140 );
Chris@18 141 $last_include = end($single_output['included']);
Chris@18 142 $this->assertEquals(
Chris@18 143 'taxonomy_term--tags',
Chris@18 144 $last_include['type']
Chris@18 145 );
Chris@18 146
Chris@18 147 // 10b. Single article with nested includes.
Chris@18 148 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid, [
Chris@18 149 'query' => ['include' => 'field_tags,field_tags.vid'],
Chris@18 150 ]));
Chris@18 151 $this->assertSession()->statusCodeEquals(200);
Chris@18 152 $this->assertEquals('node--article', $single_output['data']['type']);
Chris@18 153 $first_include = reset($single_output['included']);
Chris@18 154 $this->assertEquals(
Chris@18 155 'taxonomy_term--tags',
Chris@18 156 $first_include['type']
Chris@18 157 );
Chris@18 158 $last_include = end($single_output['included']);
Chris@18 159 $this->assertEquals(
Chris@18 160 'taxonomy_vocabulary--taxonomy_vocabulary',
Chris@18 161 $last_include['type']
Chris@18 162 );
Chris@18 163
Chris@18 164 // 11. Includes with relationships.
Chris@18 165 $this->drupalGet('/jsonapi/node/article/' . $uuid . '/relationships/uid');
Chris@18 166 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/' . $uuid . '/relationships/uid', [
Chris@18 167 'query' => ['include' => 'uid'],
Chris@18 168 ]));
Chris@18 169 $this->assertSession()->statusCodeEquals(200);
Chris@18 170 $this->assertEquals('user--user', $single_output['data']['type']);
Chris@18 171 $this->assertArrayHasKey('related', $single_output['links']);
Chris@18 172 $this->assertArrayHasKey('included', $single_output);
Chris@18 173 $first_include = reset($single_output['included']);
Chris@18 174 $this->assertEquals(
Chris@18 175 'user--user',
Chris@18 176 $first_include['type']
Chris@18 177 );
Chris@18 178 $this->assertFalse(empty($first_include['attributes']));
Chris@18 179 $this->assertTrue(empty($first_include['attributes']['mail']));
Chris@18 180 $this->assertTrue(empty($first_include['attributes']['pass']));
Chris@18 181 // 12. Collection with one access denied.
Chris@18 182 $this->nodes[1]->set('status', FALSE);
Chris@18 183 $this->nodes[1]->save();
Chris@18 184 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 185 'query' => ['page' => ['limit' => 2]] + $default_sort,
Chris@18 186 ]));
Chris@18 187 $this->assertSession()->statusCodeEquals(200);
Chris@18 188 $this->assertEquals(1, count($single_output['data']));
Chris@18 189 $this->assertEquals(1, count(array_filter(array_keys($single_output['meta']['omitted']['links']), function ($key) {
Chris@18 190 return $key !== 'help';
Chris@18 191 })));
Chris@18 192 $link_keys = array_keys($single_output['meta']['omitted']['links']);
Chris@18 193 $this->assertSame('help', reset($link_keys));
Chris@18 194 $this->assertRegExp('/^item:[a-zA-Z0-9]{7}$/', next($link_keys));
Chris@18 195 $this->nodes[1]->set('status', TRUE);
Chris@18 196 $this->nodes[1]->save();
Chris@18 197 // 13. Test filtering when using short syntax.
Chris@18 198 $filter = [
Chris@18 199 'uid.id' => ['value' => $this->user->uuid()],
Chris@18 200 'field_tags.id' => ['value' => $this->tags[0]->uuid()],
Chris@18 201 ];
Chris@18 202 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 203 'query' => ['filter' => $filter, 'include' => 'uid,field_tags'],
Chris@18 204 ]));
Chris@18 205 $this->assertSession()->statusCodeEquals(200);
Chris@18 206 $this->assertGreaterThan(0, count($single_output['data']));
Chris@18 207 // 14. Test filtering when using long syntax.
Chris@18 208 $filter = [
Chris@18 209 'and_group' => ['group' => ['conjunction' => 'AND']],
Chris@18 210 'filter_user' => [
Chris@18 211 'condition' => [
Chris@18 212 'path' => 'uid.id',
Chris@18 213 'value' => $this->user->uuid(),
Chris@18 214 'memberOf' => 'and_group',
Chris@18 215 ],
Chris@18 216 ],
Chris@18 217 'filter_tags' => [
Chris@18 218 'condition' => [
Chris@18 219 'path' => 'field_tags.id',
Chris@18 220 'value' => $this->tags[0]->uuid(),
Chris@18 221 'memberOf' => 'and_group',
Chris@18 222 ],
Chris@18 223 ],
Chris@18 224 ];
Chris@18 225 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 226 'query' => ['filter' => $filter, 'include' => 'uid,field_tags'],
Chris@18 227 ]));
Chris@18 228 $this->assertSession()->statusCodeEquals(200);
Chris@18 229 $this->assertGreaterThan(0, count($single_output['data']));
Chris@18 230 // 15. Test filtering when using invalid syntax.
Chris@18 231 $filter = [
Chris@18 232 'and_group' => ['group' => ['conjunction' => 'AND']],
Chris@18 233 'filter_user' => [
Chris@18 234 'condition' => [
Chris@18 235 'name-with-a-typo' => 'uid.id',
Chris@18 236 'value' => $this->user->uuid(),
Chris@18 237 'memberOf' => 'and_group',
Chris@18 238 ],
Chris@18 239 ],
Chris@18 240 ];
Chris@18 241 $this->drupalGet('/jsonapi/node/article', [
Chris@18 242 'query' => ['filter' => $filter] + $default_sort,
Chris@18 243 ]);
Chris@18 244 $this->assertSession()->statusCodeEquals(400);
Chris@18 245 // 16. Test filtering on the same field.
Chris@18 246 $filter = [
Chris@18 247 'or_group' => ['group' => ['conjunction' => 'OR']],
Chris@18 248 'filter_tags_1' => [
Chris@18 249 'condition' => [
Chris@18 250 'path' => 'field_tags.id',
Chris@18 251 'value' => $this->tags[0]->uuid(),
Chris@18 252 'memberOf' => 'or_group',
Chris@18 253 ],
Chris@18 254 ],
Chris@18 255 'filter_tags_2' => [
Chris@18 256 'condition' => [
Chris@18 257 'path' => 'field_tags.id',
Chris@18 258 'value' => $this->tags[1]->uuid(),
Chris@18 259 'memberOf' => 'or_group',
Chris@18 260 ],
Chris@18 261 ],
Chris@18 262 ];
Chris@18 263 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 264 'query' => ['filter' => $filter, 'include' => 'field_tags'] + $default_sort,
Chris@18 265 ]));
Chris@18 266 $this->assertSession()->statusCodeEquals(200);
Chris@18 267 $this->assertGreaterThanOrEqual(2, count($single_output['included']));
Chris@18 268 // 17. Single user (check fields lacking 'view' access).
Chris@18 269 $user_url = Url::fromRoute('jsonapi.user--user.individual', [
Chris@18 270 'entity' => $this->user->uuid(),
Chris@18 271 ]);
Chris@18 272 $response = $this->request('GET', $user_url, [
Chris@18 273 'auth' => [
Chris@18 274 $this->userCanViewProfiles->getUsername(),
Chris@18 275 $this->userCanViewProfiles->pass_raw,
Chris@18 276 ],
Chris@18 277 ]);
Chris@18 278 $single_output = Json::decode($response->getBody()->__toString());
Chris@18 279 $this->assertEquals(200, $response->getStatusCode());
Chris@18 280 $this->assertEquals('user--user', $single_output['data']['type']);
Chris@18 281 $this->assertEquals($this->user->get('name')->value, $single_output['data']['attributes']['name']);
Chris@18 282 $this->assertTrue(empty($single_output['data']['attributes']['mail']));
Chris@18 283 $this->assertTrue(empty($single_output['data']['attributes']['pass']));
Chris@18 284 // 18. Test filtering on the column of a link.
Chris@18 285 $filter = [
Chris@18 286 'linkUri' => [
Chris@18 287 'condition' => [
Chris@18 288 'path' => 'field_link.uri',
Chris@18 289 'value' => 'https://',
Chris@18 290 'operator' => 'STARTS_WITH',
Chris@18 291 ],
Chris@18 292 ],
Chris@18 293 ];
Chris@18 294 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 295 'query' => ['filter' => $filter] + $default_sort,
Chris@18 296 ]));
Chris@18 297 $this->assertSession()->statusCodeEquals(200);
Chris@18 298 $this->assertGreaterThanOrEqual(1, count($single_output['data']));
Chris@18 299 // 19. Test non-existing route without 'Accept' header.
Chris@18 300 $this->drupalGet('/jsonapi/node/article/broccoli');
Chris@18 301 $this->assertSession()->statusCodeEquals(404);
Chris@18 302 // Even without the 'Accept' header the 404 error is formatted as JSON:API.
Chris@18 303 $this->assertSession()->responseHeaderEquals('Content-Type', 'application/vnd.api+json');
Chris@18 304 // 20. Test non-existing route with 'Accept' header.
Chris@18 305 $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/broccoli', [], [
Chris@18 306 'Accept' => 'application/vnd.api+json',
Chris@18 307 ]));
Chris@18 308 $this->assertEquals(404, $single_output['errors'][0]['status']);
Chris@18 309 $this->assertSession()->statusCodeEquals(404);
Chris@18 310 // With the 'Accept' header we can know we want the 404 error formatted as
Chris@18 311 // JSON:API.
Chris@18 312 $this->assertSession()->responseHeaderContains('Content-Type', 'application/vnd.api+json');
Chris@18 313 // 22. Test sort criteria on multiple fields: both ASC.
Chris@18 314 $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 315 'query' => [
Chris@18 316 'page[limit]' => 6,
Chris@18 317 'sort' => 'field_sort1,field_sort2',
Chris@18 318 ],
Chris@18 319 ]));
Chris@18 320 $output_uuids = array_map(function ($result) {
Chris@18 321 return $result['id'];
Chris@18 322 }, $output['data']);
Chris@18 323 $this->assertCount(6, $output_uuids);
Chris@18 324 $this->assertSame([
Chris@18 325 Node::load(5)->uuid(),
Chris@18 326 Node::load(4)->uuid(),
Chris@18 327 Node::load(3)->uuid(),
Chris@18 328 Node::load(2)->uuid(),
Chris@18 329 Node::load(1)->uuid(),
Chris@18 330 Node::load(10)->uuid(),
Chris@18 331 ], $output_uuids);
Chris@18 332 // 23. Test sort criteria on multiple fields: first ASC, second DESC.
Chris@18 333 $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 334 'query' => [
Chris@18 335 'page[limit]' => 6,
Chris@18 336 'sort' => 'field_sort1,-field_sort2',
Chris@18 337 ],
Chris@18 338 ]));
Chris@18 339 $output_uuids = array_map(function ($result) {
Chris@18 340 return $result['id'];
Chris@18 341 }, $output['data']);
Chris@18 342 $this->assertCount(6, $output_uuids);
Chris@18 343 $this->assertSame([
Chris@18 344 Node::load(1)->uuid(),
Chris@18 345 Node::load(2)->uuid(),
Chris@18 346 Node::load(3)->uuid(),
Chris@18 347 Node::load(4)->uuid(),
Chris@18 348 Node::load(5)->uuid(),
Chris@18 349 Node::load(6)->uuid(),
Chris@18 350 ], $output_uuids);
Chris@18 351 // 24. Test sort criteria on multiple fields: first DESC, second ASC.
Chris@18 352 $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 353 'query' => [
Chris@18 354 'page[limit]' => 6,
Chris@18 355 'sort' => '-field_sort1,field_sort2',
Chris@18 356 ],
Chris@18 357 ]));
Chris@18 358 $output_uuids = array_map(function ($result) {
Chris@18 359 return $result['id'];
Chris@18 360 }, $output['data']);
Chris@18 361 $this->assertCount(5, $output_uuids);
Chris@18 362 $this->assertCount(2, $output['meta']['omitted']['links']);
Chris@18 363 $this->assertSame([
Chris@18 364 Node::load(60)->uuid(),
Chris@18 365 Node::load(59)->uuid(),
Chris@18 366 Node::load(58)->uuid(),
Chris@18 367 Node::load(57)->uuid(),
Chris@18 368 Node::load(56)->uuid(),
Chris@18 369 ], $output_uuids);
Chris@18 370 // 25. Test sort criteria on multiple fields: both DESC.
Chris@18 371 $output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 372 'query' => [
Chris@18 373 'page[limit]' => 6,
Chris@18 374 'sort' => '-field_sort1,-field_sort2',
Chris@18 375 ],
Chris@18 376 ]));
Chris@18 377 $output_uuids = array_map(function ($result) {
Chris@18 378 return $result['id'];
Chris@18 379 }, $output['data']);
Chris@18 380 $this->assertCount(5, $output_uuids);
Chris@18 381 $this->assertCount(2, $output['meta']['omitted']['links']);
Chris@18 382 $this->assertSame([
Chris@18 383 Node::load(56)->uuid(),
Chris@18 384 Node::load(57)->uuid(),
Chris@18 385 Node::load(58)->uuid(),
Chris@18 386 Node::load(59)->uuid(),
Chris@18 387 Node::load(60)->uuid(),
Chris@18 388 ], $output_uuids);
Chris@18 389 // 25. Test collection count.
Chris@18 390 $this->container->get('module_installer')->install(['jsonapi_test_collection_count']);
Chris@18 391 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article'));
Chris@18 392 $this->assertSession()->statusCodeEquals(200);
Chris@18 393 $this->assertEquals(61, $collection_output['meta']['count']);
Chris@18 394 $this->container->get('module_installer')->uninstall(['jsonapi_test_collection_count']);
Chris@18 395
Chris@18 396 // Test documentation filtering examples.
Chris@18 397 // 1. Only get published nodes.
Chris@18 398 $filter = [
Chris@18 399 'status-filter' => [
Chris@18 400 'condition' => [
Chris@18 401 'path' => 'status',
Chris@18 402 'value' => 1,
Chris@18 403 ],
Chris@18 404 ],
Chris@18 405 ];
Chris@18 406 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 407 'query' => ['filter' => $filter] + $default_sort,
Chris@18 408 ]));
Chris@18 409 $this->assertSession()->statusCodeEquals(200);
Chris@18 410 $this->assertGreaterThanOrEqual(OffsetPage::SIZE_MAX, count($collection_output['data']));
Chris@18 411 // 2. Nested Filters: Get nodes created by user admin.
Chris@18 412 $filter = [
Chris@18 413 'name-filter' => [
Chris@18 414 'condition' => [
Chris@18 415 'path' => 'uid.name',
Chris@18 416 'value' => $this->user->getAccountName(),
Chris@18 417 ],
Chris@18 418 ],
Chris@18 419 ];
Chris@18 420 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 421 'query' => ['filter' => $filter] + $default_sort,
Chris@18 422 ]));
Chris@18 423 $this->assertSession()->statusCodeEquals(200);
Chris@18 424 $this->assertGreaterThanOrEqual(OffsetPage::SIZE_MAX, count($collection_output['data']));
Chris@18 425 // 3. Filtering with arrays: Get nodes created by users [admin, john].
Chris@18 426 $filter = [
Chris@18 427 'name-filter' => [
Chris@18 428 'condition' => [
Chris@18 429 'path' => 'uid.name',
Chris@18 430 'operator' => 'IN',
Chris@18 431 'value' => [
Chris@18 432 $this->user->getAccountName(),
Chris@18 433 $this->getRandomGenerator()->name(),
Chris@18 434 ],
Chris@18 435 ],
Chris@18 436 ],
Chris@18 437 ];
Chris@18 438 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 439 'query' => ['filter' => $filter] + $default_sort,
Chris@18 440 ]));
Chris@18 441 $this->assertSession()->statusCodeEquals(200);
Chris@18 442 $this->assertGreaterThanOrEqual(OffsetPage::SIZE_MAX, count($collection_output['data']));
Chris@18 443 // 4. Grouping filters: Get nodes that are published and create by admin.
Chris@18 444 $filter = [
Chris@18 445 'and-group' => [
Chris@18 446 'group' => [
Chris@18 447 'conjunction' => 'AND',
Chris@18 448 ],
Chris@18 449 ],
Chris@18 450 'name-filter' => [
Chris@18 451 'condition' => [
Chris@18 452 'path' => 'uid.name',
Chris@18 453 'value' => $this->user->getAccountName(),
Chris@18 454 'memberOf' => 'and-group',
Chris@18 455 ],
Chris@18 456 ],
Chris@18 457 'status-filter' => [
Chris@18 458 'condition' => [
Chris@18 459 'path' => 'status',
Chris@18 460 'value' => 1,
Chris@18 461 'memberOf' => 'and-group',
Chris@18 462 ],
Chris@18 463 ],
Chris@18 464 ];
Chris@18 465 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 466 'query' => ['filter' => $filter] + $default_sort,
Chris@18 467 ]));
Chris@18 468 $this->assertSession()->statusCodeEquals(200);
Chris@18 469 $this->assertGreaterThanOrEqual(OffsetPage::SIZE_MAX, count($collection_output['data']));
Chris@18 470 // 5. Grouping grouped filters: Get nodes that are promoted or sticky and
Chris@18 471 // created by admin.
Chris@18 472 $filter = [
Chris@18 473 'and-group' => [
Chris@18 474 'group' => [
Chris@18 475 'conjunction' => 'AND',
Chris@18 476 ],
Chris@18 477 ],
Chris@18 478 'or-group' => [
Chris@18 479 'group' => [
Chris@18 480 'conjunction' => 'OR',
Chris@18 481 'memberOf' => 'and-group',
Chris@18 482 ],
Chris@18 483 ],
Chris@18 484 'admin-filter' => [
Chris@18 485 'condition' => [
Chris@18 486 'path' => 'uid.name',
Chris@18 487 'value' => $this->user->getAccountName(),
Chris@18 488 'memberOf' => 'and-group',
Chris@18 489 ],
Chris@18 490 ],
Chris@18 491 'sticky-filter' => [
Chris@18 492 'condition' => [
Chris@18 493 'path' => 'sticky',
Chris@18 494 'value' => 1,
Chris@18 495 'memberOf' => 'or-group',
Chris@18 496 ],
Chris@18 497 ],
Chris@18 498 'promote-filter' => [
Chris@18 499 'condition' => [
Chris@18 500 'path' => 'promote',
Chris@18 501 'value' => 0,
Chris@18 502 'memberOf' => 'or-group',
Chris@18 503 ],
Chris@18 504 ],
Chris@18 505 ];
Chris@18 506 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [
Chris@18 507 'query' => ['filter' => $filter] + $default_sort,
Chris@18 508 ]));
Chris@18 509 $this->assertSession()->statusCodeEquals(200);
Chris@18 510 $this->assertEquals(0, count($collection_output['data']));
Chris@18 511 }
Chris@18 512
Chris@18 513 /**
Chris@18 514 * Test the GET method on articles referencing the same tag twice.
Chris@18 515 */
Chris@18 516 public function testReferencingTwiceRead() {
Chris@18 517 $this->createDefaultContent(1, 1, FALSE, FALSE, static::IS_NOT_MULTILINGUAL, TRUE);
Chris@18 518
Chris@18 519 // 1. Load all articles (1st page).
Chris@18 520 $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article'));
Chris@18 521 $this->assertSession()->statusCodeEquals(200);
Chris@18 522 $this->assertEquals(1, count($collection_output['data']));
Chris@18 523 $this->assertSession()
Chris@18 524 ->responseHeaderEquals('Content-Type', 'application/vnd.api+json');
Chris@18 525 }
Chris@18 526
Chris@18 527 /**
Chris@18 528 * Test POST, PATCH and DELETE.
Chris@18 529 */
Chris@18 530 public function testWrite() {
Chris@18 531 $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
Chris@18 532
Chris@18 533 $this->createDefaultContent(0, 3, FALSE, FALSE, static::IS_NOT_MULTILINGUAL, FALSE);
Chris@18 534 // 1. Successful post.
Chris@18 535 $collection_url = Url::fromRoute('jsonapi.node--article.collection.post');
Chris@18 536 $body = [
Chris@18 537 'data' => [
Chris@18 538 'type' => 'node--article',
Chris@18 539 'attributes' => [
Chris@18 540 'langcode' => 'en',
Chris@18 541 'title' => 'My custom title',
Chris@18 542 'default_langcode' => '1',
Chris@18 543 'body' => [
Chris@18 544 'value' => 'Custom value',
Chris@18 545 'format' => 'plain_text',
Chris@18 546 'summary' => 'Custom summary',
Chris@18 547 ],
Chris@18 548 ],
Chris@18 549 'relationships' => [
Chris@18 550 'field_tags' => [
Chris@18 551 'data' => [
Chris@18 552 [
Chris@18 553 'type' => 'taxonomy_term--tags',
Chris@18 554 'id' => $this->tags[0]->uuid(),
Chris@18 555 ],
Chris@18 556 [
Chris@18 557 'type' => 'taxonomy_term--tags',
Chris@18 558 'id' => $this->tags[1]->uuid(),
Chris@18 559 ],
Chris@18 560 ],
Chris@18 561 ],
Chris@18 562 ],
Chris@18 563 ],
Chris@18 564 ];
Chris@18 565 $response = $this->request('POST', $collection_url, [
Chris@18 566 'body' => Json::encode($body),
Chris@18 567 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 568 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 569 ]);
Chris@18 570 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 571 $this->assertEquals(201, $response->getStatusCode());
Chris@18 572 $this->assertArrayNotHasKey('uuid', $created_response['data']['attributes']);
Chris@18 573 $uuid = $created_response['data']['id'];
Chris@18 574 $this->assertEquals(2, count($created_response['data']['relationships']['field_tags']['data']));
Chris@18 575 $this->assertEquals($created_response['data']['links']['self']['href'], $response->getHeader('Location')[0]);
Chris@18 576
Chris@18 577 // 2. Authorization error.
Chris@18 578 $response = $this->request('POST', $collection_url, [
Chris@18 579 'body' => Json::encode($body),
Chris@18 580 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 581 ]);
Chris@18 582 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 583 $this->assertEquals(401, $response->getStatusCode());
Chris@18 584 $this->assertNotEmpty($created_response['errors']);
Chris@18 585 $this->assertEquals('Unauthorized', $created_response['errors'][0]['title']);
Chris@18 586
Chris@18 587 // 2.1 Authorization error with a user without create permissions.
Chris@18 588 $response = $this->request('POST', $collection_url, [
Chris@18 589 'body' => Json::encode($body),
Chris@18 590 'auth' => [$this->userCanViewProfiles->getUsername(), $this->userCanViewProfiles->pass_raw],
Chris@18 591 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 592 ]);
Chris@18 593 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 594 $this->assertEquals(403, $response->getStatusCode());
Chris@18 595 $this->assertNotEmpty($created_response['errors']);
Chris@18 596 $this->assertEquals('Forbidden', $created_response['errors'][0]['title']);
Chris@18 597
Chris@18 598 // 3. Missing Content-Type error.
Chris@18 599 $response = $this->request('POST', $collection_url, [
Chris@18 600 'body' => Json::encode($body),
Chris@18 601 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 602 'headers' => ['Accept' => 'application/vnd.api+json'],
Chris@18 603 ]);
Chris@18 604 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 605 $this->assertEquals(415, $response->getStatusCode());
Chris@18 606
Chris@18 607 // 4. Article with a duplicate ID.
Chris@18 608 $invalid_body = $body;
Chris@18 609 $invalid_body['data']['id'] = Node::load(1)->uuid();
Chris@18 610 $response = $this->request('POST', $collection_url, [
Chris@18 611 'body' => Json::encode($invalid_body),
Chris@18 612 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 613 'headers' => [
Chris@18 614 'Accept' => 'application/vnd.api+json',
Chris@18 615 'Content-Type' => 'application/vnd.api+json',
Chris@18 616 ],
Chris@18 617 ]);
Chris@18 618 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 619 $this->assertEquals(409, $response->getStatusCode());
Chris@18 620 $this->assertNotEmpty($created_response['errors']);
Chris@18 621 $this->assertEquals('Conflict', $created_response['errors'][0]['title']);
Chris@18 622 // 5. Article with wrong reference UUIDs for tags.
Chris@18 623 $body_invalid_tags = $body;
Chris@18 624 $body_invalid_tags['data']['relationships']['field_tags']['data'][0]['id'] = 'lorem';
Chris@18 625 $body_invalid_tags['data']['relationships']['field_tags']['data'][1]['id'] = 'ipsum';
Chris@18 626 $response = $this->request('POST', $collection_url, [
Chris@18 627 'body' => Json::encode($body_invalid_tags),
Chris@18 628 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 629 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 630 ]);
Chris@18 631 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 632 $this->assertEquals(404, $response->getStatusCode());
Chris@18 633 // 6. Decoding error.
Chris@18 634 $response = $this->request('POST', $collection_url, [
Chris@18 635 'body' => '{"bad json",,,}',
Chris@18 636 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 637 'headers' => [
Chris@18 638 'Content-Type' => 'application/vnd.api+json',
Chris@18 639 'Accept' => 'application/vnd.api+json',
Chris@18 640 ],
Chris@18 641 ]);
Chris@18 642 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 643 $this->assertEquals(400, $response->getStatusCode());
Chris@18 644 $this->assertNotEmpty($created_response['errors']);
Chris@18 645 $this->assertEquals('Bad Request', $created_response['errors'][0]['title']);
Chris@18 646 // 6.1 Denormalizing error.
Chris@18 647 $response = $this->request('POST', $collection_url, [
Chris@18 648 'body' => '{"data":{"type":"something"},"valid yet nonsensical json":[]}',
Chris@18 649 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 650 'headers' => [
Chris@18 651 'Content-Type' => 'application/vnd.api+json',
Chris@18 652 'Accept' => 'application/vnd.api+json',
Chris@18 653 ],
Chris@18 654 ]);
Chris@18 655 $created_response = Json::decode($response->getBody()->__toString());
Chris@18 656 $this->assertEquals(422, $response->getStatusCode());
Chris@18 657 $this->assertNotEmpty($created_response['errors']);
Chris@18 658 $this->assertEquals('Unprocessable Entity', $created_response['errors'][0]['title']);
Chris@18 659 // 6.2 Relationships are not included in "data".
Chris@18 660 $malformed_body = $body;
Chris@18 661 unset($malformed_body['data']['relationships']);
Chris@18 662 $malformed_body['relationships'] = $body['data']['relationships'];
Chris@18 663 $response = $this->request('POST', $collection_url, [
Chris@18 664 'body' => Json::encode($malformed_body),
Chris@18 665 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 666 'headers' => [
Chris@18 667 'Accept' => 'application/vnd.api+json',
Chris@18 668 'Content-Type' => 'application/vnd.api+json',
Chris@18 669 ],
Chris@18 670 ]);
Chris@18 671 $created_response = Json::decode((string) $response->getBody());
Chris@18 672 $this->assertSame(400, $response->getStatusCode());
Chris@18 673 $this->assertNotEmpty($created_response['errors']);
Chris@18 674 $this->assertSame("Bad Request", $created_response['errors'][0]['title']);
Chris@18 675 $this->assertSame("Found \"relationships\" within the document's top level. The \"relationships\" key must be within resource object.", $created_response['errors'][0]['detail']);
Chris@18 676 // 6.2 "type" not included in "data".
Chris@18 677 $missing_type = $body;
Chris@18 678 unset($missing_type['data']['type']);
Chris@18 679 $response = $this->request('POST', $collection_url, [
Chris@18 680 'body' => Json::encode($missing_type),
Chris@18 681 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 682 'headers' => [
Chris@18 683 'Accept' => 'application/vnd.api+json',
Chris@18 684 'Content-Type' => 'application/vnd.api+json',
Chris@18 685 ],
Chris@18 686 ]);
Chris@18 687 $created_response = Json::decode((string) $response->getBody());
Chris@18 688 $this->assertSame(400, $response->getStatusCode());
Chris@18 689 $this->assertNotEmpty($created_response['errors']);
Chris@18 690 $this->assertSame("Bad Request", $created_response['errors'][0]['title']);
Chris@18 691 $this->assertSame("Resource object must include a \"type\".", $created_response['errors'][0]['detail']);
Chris@18 692 // 7. Successful PATCH.
Chris@18 693 $body = [
Chris@18 694 'data' => [
Chris@18 695 'id' => $uuid,
Chris@18 696 'type' => 'node--article',
Chris@18 697 'attributes' => ['title' => 'My updated title'],
Chris@18 698 ],
Chris@18 699 ];
Chris@18 700 $individual_url = Url::fromRoute('jsonapi.node--article.individual', [
Chris@18 701 'entity' => $uuid,
Chris@18 702 ]);
Chris@18 703 $response = $this->request('PATCH', $individual_url, [
Chris@18 704 'body' => Json::encode($body),
Chris@18 705 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 706 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 707 ]);
Chris@18 708 $updated_response = Json::decode($response->getBody()->__toString());
Chris@18 709 $this->assertEquals(200, $response->getStatusCode());
Chris@18 710 $this->assertEquals('My updated title', $updated_response['data']['attributes']['title']);
Chris@18 711
Chris@18 712 // 7.1 Unsuccessful PATCH due to access restrictions.
Chris@18 713 $body = [
Chris@18 714 'data' => [
Chris@18 715 'id' => $uuid,
Chris@18 716 'type' => 'node--article',
Chris@18 717 'attributes' => ['title' => 'My updated title'],
Chris@18 718 ],
Chris@18 719 ];
Chris@18 720 $individual_url = Url::fromRoute('jsonapi.node--article.individual', [
Chris@18 721 'entity' => $uuid,
Chris@18 722 ]);
Chris@18 723 $response = $this->request('PATCH', $individual_url, [
Chris@18 724 'body' => Json::encode($body),
Chris@18 725 'auth' => [$this->userCanViewProfiles->getUsername(), $this->userCanViewProfiles->pass_raw],
Chris@18 726 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 727 ]);
Chris@18 728 $this->assertEquals(403, $response->getStatusCode());
Chris@18 729
Chris@18 730 // 8. Field access forbidden check.
Chris@18 731 $body = [
Chris@18 732 'data' => [
Chris@18 733 'id' => $uuid,
Chris@18 734 'type' => 'node--article',
Chris@18 735 'attributes' => [
Chris@18 736 'title' => 'My updated title',
Chris@18 737 'status' => 0,
Chris@18 738 ],
Chris@18 739 ],
Chris@18 740 ];
Chris@18 741 $response = $this->request('PATCH', $individual_url, [
Chris@18 742 'body' => Json::encode($body),
Chris@18 743 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 744 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 745 ]);
Chris@18 746 $updated_response = Json::decode($response->getBody()->__toString());
Chris@18 747 $this->assertEquals(403, $response->getStatusCode());
Chris@18 748 $this->assertEquals("The current user is not allowed to PATCH the selected field (status). The 'administer nodes' permission is required.",
Chris@18 749 $updated_response['errors'][0]['detail']);
Chris@18 750
Chris@18 751 $node = \Drupal::entityManager()->loadEntityByUuid('node', $uuid);
Chris@18 752 $this->assertEquals(1, $node->get('status')->value, 'Node status was not changed.');
Chris@18 753 // 9. Successful POST to related endpoint.
Chris@18 754 $body = [
Chris@18 755 'data' => [
Chris@18 756 [
Chris@18 757 'id' => $this->tags[2]->uuid(),
Chris@18 758 'type' => 'taxonomy_term--tags',
Chris@18 759 ],
Chris@18 760 ],
Chris@18 761 ];
Chris@18 762 $relationship_url = Url::fromRoute('jsonapi.node--article.field_tags.relationship.post', [
Chris@18 763 'entity' => $uuid,
Chris@18 764 ]);
Chris@18 765 $response = $this->request('POST', $relationship_url, [
Chris@18 766 'body' => Json::encode($body),
Chris@18 767 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 768 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 769 ]);
Chris@18 770 $updated_response = Json::decode($response->getBody()->__toString());
Chris@18 771 $this->assertEquals(200, $response->getStatusCode());
Chris@18 772 $this->assertEquals(3, count($updated_response['data']));
Chris@18 773 $this->assertEquals('taxonomy_term--tags', $updated_response['data'][2]['type']);
Chris@18 774 $this->assertEquals($this->tags[2]->uuid(), $updated_response['data'][2]['id']);
Chris@18 775 // 10. Successful PATCH to related endpoint.
Chris@18 776 $body = [
Chris@18 777 'data' => [
Chris@18 778 [
Chris@18 779 'id' => $this->tags[1]->uuid(),
Chris@18 780 'type' => 'taxonomy_term--tags',
Chris@18 781 ],
Chris@18 782 ],
Chris@18 783 ];
Chris@18 784 $response = $this->request('PATCH', $relationship_url, [
Chris@18 785 'body' => Json::encode($body),
Chris@18 786 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 787 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 788 ]);
Chris@18 789 $this->assertEquals(204, $response->getStatusCode());
Chris@18 790 $this->assertEmpty($response->getBody()->__toString());
Chris@18 791 // 11. Successful DELETE to related endpoint.
Chris@18 792 $response = $this->request('DELETE', $relationship_url, [
Chris@18 793 // Send a request with no body.
Chris@18 794 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 795 'headers' => [
Chris@18 796 'Content-Type' => 'application/vnd.api+json',
Chris@18 797 'Accept' => 'application/vnd.api+json',
Chris@18 798 ],
Chris@18 799 ]);
Chris@18 800 $updated_response = Json::decode($response->getBody()->__toString());
Chris@18 801 $this->assertEquals(
Chris@18 802 'You need to provide a body for DELETE operations on a relationship (field_tags).',
Chris@18 803 $updated_response['errors'][0]['detail']
Chris@18 804 );
Chris@18 805 $this->assertEquals(400, $response->getStatusCode());
Chris@18 806 $response = $this->request('DELETE', $relationship_url, [
Chris@18 807 // Send a request with no authentication.
Chris@18 808 'body' => Json::encode($body),
Chris@18 809 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 810 ]);
Chris@18 811 $this->assertEquals(401, $response->getStatusCode());
Chris@18 812 $response = $this->request('DELETE', $relationship_url, [
Chris@18 813 // Remove the existing relationship item.
Chris@18 814 'body' => Json::encode($body),
Chris@18 815 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 816 'headers' => ['Content-Type' => 'application/vnd.api+json'],
Chris@18 817 ]);
Chris@18 818 $this->assertEquals(204, $response->getStatusCode());
Chris@18 819 $this->assertEmpty($response->getBody()->__toString());
Chris@18 820 // 12. PATCH with invalid title and body format.
Chris@18 821 $body = [
Chris@18 822 'data' => [
Chris@18 823 'id' => $uuid,
Chris@18 824 'type' => 'node--article',
Chris@18 825 'attributes' => [
Chris@18 826 'title' => '',
Chris@18 827 'body' => [
Chris@18 828 'value' => 'Custom value',
Chris@18 829 'format' => 'invalid_format',
Chris@18 830 'summary' => 'Custom summary',
Chris@18 831 ],
Chris@18 832 ],
Chris@18 833 ],
Chris@18 834 ];
Chris@18 835 $response = $this->request('PATCH', $individual_url, [
Chris@18 836 'body' => Json::encode($body),
Chris@18 837 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 838 'headers' => [
Chris@18 839 'Content-Type' => 'application/vnd.api+json',
Chris@18 840 'Accept' => 'application/vnd.api+json',
Chris@18 841 ],
Chris@18 842 ]);
Chris@18 843 $updated_response = Json::decode($response->getBody()->__toString());
Chris@18 844 $this->assertEquals(422, $response->getStatusCode());
Chris@18 845 $this->assertCount(2, $updated_response['errors']);
Chris@18 846 for ($i = 0; $i < 2; $i++) {
Chris@18 847 $this->assertEquals("Unprocessable Entity", $updated_response['errors'][$i]['title']);
Chris@18 848 $this->assertEquals(422, $updated_response['errors'][$i]['status']);
Chris@18 849 }
Chris@18 850 $this->assertEquals("title: This value should not be null.", $updated_response['errors'][0]['detail']);
Chris@18 851 $this->assertEquals("body.0.format: The value you selected is not a valid choice.", $updated_response['errors'][1]['detail']);
Chris@18 852 $this->assertEquals("/data/attributes/title", $updated_response['errors'][0]['source']['pointer']);
Chris@18 853 $this->assertEquals("/data/attributes/body/format", $updated_response['errors'][1]['source']['pointer']);
Chris@18 854 // 13. PATCH with field that doesn't exist on Entity.
Chris@18 855 $body = [
Chris@18 856 'data' => [
Chris@18 857 'id' => $uuid,
Chris@18 858 'type' => 'node--article',
Chris@18 859 'attributes' => [
Chris@18 860 'field_that_doesnt_exist' => 'foobar',
Chris@18 861 ],
Chris@18 862 ],
Chris@18 863 ];
Chris@18 864 $response = $this->request('PATCH', $individual_url, [
Chris@18 865 'body' => Json::encode($body),
Chris@18 866 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 867 'headers' => [
Chris@18 868 'Content-Type' => 'application/vnd.api+json',
Chris@18 869 'Accept' => 'application/vnd.api+json',
Chris@18 870 ],
Chris@18 871 ]);
Chris@18 872 $updated_response = Json::decode($response->getBody()->__toString());
Chris@18 873 $this->assertEquals(422, $response->getStatusCode());
Chris@18 874 $this->assertEquals("The attribute field_that_doesnt_exist does not exist on the node--article resource type.",
Chris@18 875 $updated_response['errors']['0']['detail']);
Chris@18 876 // 14. Successful DELETE.
Chris@18 877 $response = $this->request('DELETE', $individual_url, [
Chris@18 878 'auth' => [$this->user->getUsername(), $this->user->pass_raw],
Chris@18 879 ]);
Chris@18 880 $this->assertEquals(204, $response->getStatusCode());
Chris@18 881 $response = $this->request('GET', $individual_url, []);
Chris@18 882 $this->assertEquals(404, $response->getStatusCode());
Chris@18 883 }
Chris@18 884
Chris@18 885 }