1<?php 2 3namespace Drupal\Tests\jsonapi\Unit\Routing; 4 5use Drupal\Core\Entity\EntityInterface; 6use Drupal\jsonapi\ResourceType\ResourceType; 7use Drupal\jsonapi\ResourceType\ResourceTypeRelationship; 8use Drupal\jsonapi\ResourceType\ResourceTypeRepository; 9use Drupal\jsonapi\Routing\Routes; 10use Drupal\Tests\UnitTestCase; 11use Drupal\Core\Routing\RouteObjectInterface; 12use Symfony\Component\DependencyInjection\ContainerInterface; 13 14/** 15 * @coversDefaultClass \Drupal\jsonapi\Routing\Routes 16 * @group jsonapi 17 * 18 * @internal 19 */ 20class RoutesTest extends UnitTestCase { 21 22 /** 23 * List of routes objects for the different scenarios. 24 * 25 * @var \Drupal\jsonapi\Routing\Routes[] 26 */ 27 protected $routes; 28 29 /** 30 * {@inheritdoc} 31 */ 32 protected function setUp(): void { 33 parent::setUp(); 34 $relationship_fields = [ 35 'external' => new ResourceTypeRelationship('external'), 36 'internal' => new ResourceTypeRelationship('internal'), 37 'both' => new ResourceTypeRelationship('both'), 38 ]; 39 $type_1 = new ResourceType('entity_type_1', 'bundle_1_1', EntityInterface::class, FALSE, TRUE, TRUE, FALSE, $relationship_fields); 40 $type_2 = new ResourceType('entity_type_2', 'bundle_2_1', EntityInterface::class, TRUE, TRUE, TRUE, FALSE, $relationship_fields); 41 $relatable_resource_types = [ 42 'external' => [$type_1], 43 'internal' => [$type_2], 44 'both' => [$type_1, $type_2], 45 ]; 46 $type_1->setRelatableResourceTypes($relatable_resource_types); 47 $type_2->setRelatableResourceTypes($relatable_resource_types); 48 // This type ensures that we can create routes for bundle IDs which might be 49 // cast from strings to integers. It should not affect related resource 50 // routing. 51 $type_3 = new ResourceType('entity_type_3', '123', EntityInterface::class, TRUE); 52 $type_3->setRelatableResourceTypes([]); 53 $resource_type_repository = $this->prophesize(ResourceTypeRepository::class); 54 $resource_type_repository->all()->willReturn([$type_1, $type_2, $type_3]); 55 $container = $this->prophesize(ContainerInterface::class); 56 $container->get('jsonapi.resource_type.repository')->willReturn($resource_type_repository->reveal()); 57 $container->getParameter('jsonapi.base_path')->willReturn('/jsonapi'); 58 $container->getParameter('authentication_providers')->willReturn([ 59 'lorem' => [], 60 'ipsum' => [], 61 ]); 62 63 $this->routes['ok'] = Routes::create($container->reveal()); 64 } 65 66 /** 67 * @covers ::routes 68 */ 69 public function testRoutesCollection() { 70 // Get the route collection and start making assertions. 71 $routes = $this->routes['ok']->routes(); 72 73 // - 2 collection routes; GET & POST for the non-internal resource type. 74 // - 3 individual routes; GET, PATCH & DELETE for the non-internal resource 75 // type. 76 // - 2 related routes; GET for the non-internal resource type relationships 77 // fields: external & both. 78 // - 12 relationship routes; 3 fields * 4 HTTP methods. 79 // `relationship` routes are generated even for internal target resource 80 // types (`related` routes are not). 81 // - 1 for the JSON:API entry point. 82 $this->assertEquals(20, $routes->count()); 83 84 $iterator = $routes->getIterator(); 85 // Check the collection route. 86 /** @var \Symfony\Component\Routing\Route $route */ 87 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.collection'); 88 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1', $route->getPath()); 89 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 90 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 91 $this->assertEquals(['GET'], $route->getMethods()); 92 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':getCollection', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 93 // Check the collection POST route. 94 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.collection.post'); 95 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1', $route->getPath()); 96 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 97 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 98 $this->assertEquals(['POST'], $route->getMethods()); 99 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':createIndividual', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 100 } 101 102 /** 103 * @covers ::routes 104 */ 105 public function testRoutesIndividual() { 106 // Get the route collection and start making assertions. 107 $iterator = $this->routes['ok']->routes()->getIterator(); 108 109 // Check the individual route. 110 /** @var \Symfony\Component\Routing\Route $route */ 111 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.individual'); 112 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity}', $route->getPath()); 113 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 114 $this->assertEquals(['GET'], $route->getMethods()); 115 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':getIndividual', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 116 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 117 $this->assertEquals([ 118 'entity' => ['type' => 'entity:entity_type_1'], 119 'resource_type' => ['type' => 'jsonapi_resource_type'], 120 ], $route->getOption('parameters')); 121 122 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.individual.patch'); 123 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity}', $route->getPath()); 124 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 125 $this->assertEquals(['PATCH'], $route->getMethods()); 126 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':patchIndividual', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 127 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 128 $this->assertEquals([ 129 'entity' => ['type' => 'entity:entity_type_1'], 130 'resource_type' => ['type' => 'jsonapi_resource_type'], 131 ], $route->getOption('parameters')); 132 133 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.individual.delete'); 134 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity}', $route->getPath()); 135 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 136 $this->assertEquals(['DELETE'], $route->getMethods()); 137 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':deleteIndividual', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 138 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 139 $this->assertEquals([ 140 'entity' => ['type' => 'entity:entity_type_1'], 141 'resource_type' => ['type' => 'jsonapi_resource_type'], 142 ], $route->getOption('parameters')); 143 } 144 145 /** 146 * @covers ::routes 147 */ 148 public function testRoutesRelated() { 149 // Get the route collection and start making assertions. 150 $iterator = $this->routes['ok']->routes()->getIterator(); 151 152 // Check the related route. 153 /** @var \Symfony\Component\Routing\Route $route */ 154 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.external.related'); 155 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity}/external', $route->getPath()); 156 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 157 $this->assertEquals(['GET'], $route->getMethods()); 158 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':getRelated', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 159 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 160 $this->assertEquals([ 161 'entity' => ['type' => 'entity:entity_type_1'], 162 'resource_type' => ['type' => 'jsonapi_resource_type'], 163 ], $route->getOption('parameters')); 164 } 165 166 /** 167 * @covers ::routes 168 */ 169 public function testRoutesRelationships() { 170 // Get the route collection and start making assertions. 171 $iterator = $this->routes['ok']->routes()->getIterator(); 172 173 // Check the relationships route. 174 /** @var \Symfony\Component\Routing\Route $route */ 175 $route = $iterator->offsetGet('jsonapi.entity_type_1--bundle_1_1.both.relationship.get'); 176 $this->assertSame('/jsonapi/entity_type_1/bundle_1_1/{entity}/relationships/both', $route->getPath()); 177 $this->assertSame('entity_type_1--bundle_1_1', $route->getDefault(Routes::RESOURCE_TYPE_KEY)); 178 $this->assertEquals(['GET'], $route->getMethods()); 179 $this->assertSame(Routes::CONTROLLER_SERVICE_NAME . ':getRelationship', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME)); 180 $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth')); 181 $this->assertEquals([ 182 'entity' => ['type' => 'entity:entity_type_1'], 183 'resource_type' => ['type' => 'jsonapi_resource_type'], 184 ], $route->getOption('parameters')); 185 } 186 187 /** 188 * Ensures that the expected routes are created or not created. 189 * 190 * @dataProvider expectedRoutes 191 */ 192 public function testRoutes($route) { 193 $this->assertArrayHasKey($route, $this->routes['ok']->routes()->all()); 194 } 195 196 /** 197 * Lists routes which should have been created. 198 */ 199 public function expectedRoutes() { 200 return [ 201 ['jsonapi.entity_type_1--bundle_1_1.individual'], 202 ['jsonapi.entity_type_1--bundle_1_1.collection'], 203 ['jsonapi.entity_type_1--bundle_1_1.internal.relationship.get'], 204 ['jsonapi.entity_type_1--bundle_1_1.internal.relationship.post'], 205 ['jsonapi.entity_type_1--bundle_1_1.internal.relationship.patch'], 206 ['jsonapi.entity_type_1--bundle_1_1.internal.relationship.delete'], 207 ['jsonapi.entity_type_1--bundle_1_1.external.related'], 208 ['jsonapi.entity_type_1--bundle_1_1.external.relationship.get'], 209 ['jsonapi.entity_type_1--bundle_1_1.external.relationship.post'], 210 ['jsonapi.entity_type_1--bundle_1_1.external.relationship.patch'], 211 ['jsonapi.entity_type_1--bundle_1_1.external.relationship.delete'], 212 ['jsonapi.entity_type_1--bundle_1_1.both.related'], 213 ['jsonapi.entity_type_1--bundle_1_1.both.relationship.get'], 214 ['jsonapi.entity_type_1--bundle_1_1.both.relationship.post'], 215 ['jsonapi.entity_type_1--bundle_1_1.both.relationship.patch'], 216 ['jsonapi.entity_type_1--bundle_1_1.both.relationship.delete'], 217 ['jsonapi.resource_list'], 218 ]; 219 } 220 221 /** 222 * Ensures that no routes are created for internal resources. 223 * 224 * @dataProvider notExpectedRoutes 225 */ 226 public function testInternalRoutes($route) { 227 $this->assertArrayNotHasKey($route, $this->routes['ok']->routes()->all()); 228 } 229 230 /** 231 * Lists routes which should have been created. 232 */ 233 public function notExpectedRoutes() { 234 return [ 235 ['jsonapi.entity_type_2--bundle_2_1.individual'], 236 ['jsonapi.entity_type_2--bundle_2_1.collection'], 237 ['jsonapi.entity_type_2--bundle_2_1.collection.post'], 238 ['jsonapi.entity_type_2--bundle_2_1.internal.related'], 239 ['jsonapi.entity_type_2--bundle_2_1.internal.relationship'], 240 ]; 241 } 242 243} 244