CRUDControllerTest.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. <?php
  2. /*
  3. * This file is part of the Sonata package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Sonata\AdminBundle\Tests\Controller;
  11. use Symfony\Component\DependencyInjection\ContainerInterface;
  12. use Symfony\Bundle\FrameworkBundle\Controller\Controller;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Sonata\AdminBundle\Controller\CRUDController;
  15. use Sonata\AdminBundle\Admin\Pool;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Bridge\Twig\Extension\FormExtension;
  18. use Symfony\Component\HttpFoundation\Session\Session;
  19. use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
  20. use Sonata\AdminBundle\Exception\ModelManagerException;
  21. /**
  22. * Test for CRUDController
  23. *
  24. * @author Andrej Hudec <pulzarraider@gmail.com>
  25. */
  26. class CRUDControllerTest extends \PHPUnit_Framework_TestCase
  27. {
  28. /**
  29. * @var CRUDController
  30. */
  31. private $controller;
  32. /**
  33. * @var Request
  34. */
  35. private $request;
  36. /**
  37. * @var Sonata\AdminBundle\Admin\AdminInterface
  38. */
  39. private $admin;
  40. /**
  41. * @var Pool
  42. */
  43. private $pool;
  44. /**
  45. * @var array
  46. */
  47. private $parameters;
  48. /**
  49. *
  50. * @var Session
  51. */
  52. private $session;
  53. /**
  54. * {@inheritDoc}
  55. */
  56. protected function setUp()
  57. {
  58. $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
  59. $this->request = new Request();
  60. $this->pool = new Pool($container, 'title', 'logo.png');
  61. $this->request->attributes->set('_sonata_admin', 'foo.admin');
  62. $this->admin = $this->getMock('Sonata\AdminBundle\Admin\AdminInterface');
  63. $this->parameters = array();
  64. // php 5.3 BC
  65. $params = &$this->parameters;
  66. $templating = $this->getMock('Symfony\Bundle\FrameworkBundle\Templating\DelegatingEngine', array(), array($container, array()));
  67. $templating->expects($this->any())
  68. ->method('renderResponse')
  69. ->will($this->returnCallback(function($view, array $parameters = array(), Response $response = null) use (&$params) {
  70. if (null === $response) {
  71. $response = new Response();
  72. }
  73. $params = $parameters;
  74. return $response;
  75. }));
  76. $this->session = new Session(new MockArraySessionStorage());
  77. // php 5.3 BC
  78. $pool = $this->pool;
  79. $request = $this->request;
  80. $admin = $this->admin;
  81. $session = $this->session;
  82. $twig = $this->getMockBuilder('Twig_Environment')
  83. ->disableOriginalConstructor()
  84. ->getMock();
  85. $twigRenderer = $this->getMock('Symfony\Bridge\Twig\Form\TwigRendererInterface');
  86. $formExtension = new FormExtension($twigRenderer);
  87. $twig->expects($this->any())
  88. ->method('getExtension')
  89. ->will($this->returnCallback(function($name) use ($formExtension) {
  90. switch ($name) {
  91. case 'form':
  92. return $formExtension;
  93. }
  94. return null;
  95. }));
  96. $container->expects($this->any())
  97. ->method('get')
  98. ->will($this->returnCallback(function($id) use ($pool, $request, $admin, $templating, $twig, $session) {
  99. switch ($id) {
  100. case 'sonata.admin.pool':
  101. return $pool;
  102. case 'request':
  103. return $request;
  104. case 'foo.admin':
  105. return $admin;
  106. case 'templating':
  107. return $templating;
  108. case 'twig':
  109. return $twig;
  110. case 'session':
  111. return $session;
  112. }
  113. return null;
  114. }));
  115. $this->admin->expects($this->any())
  116. ->method('getTemplate')
  117. ->will($this->returnCallback(function($name) {
  118. switch ($name) {
  119. case 'ajax':
  120. return 'SonataAdminBundle::ajax_layout.html.twig';
  121. case 'layout':
  122. return 'SonataAdminBundle::standard_layout.html.twig';
  123. case 'show':
  124. return 'SonataAdminBundle:CRUD:show.html.twig';
  125. }
  126. return null;
  127. }));
  128. $this->admin->expects($this->any())
  129. ->method('getIdParameter')
  130. ->will($this->returnValue('id'));
  131. $this->admin->expects($this->any())
  132. ->method('generateUrl')
  133. ->will($this->returnCallback(function($name, array $parameters = array(), $absolute = false) {
  134. $result = $name;
  135. if (!empty($parameters)) {
  136. $result .= '?'.http_build_query($parameters);
  137. }
  138. return $result;
  139. }));
  140. $this->controller = new CRUDController();
  141. $this->controller->setContainer($container);
  142. }
  143. public function testRenderJson1()
  144. {
  145. $data = array('example'=>'123', 'foo'=>'bar');
  146. $this->request->headers->set('Content-Type', 'application/x-www-form-urlencoded');
  147. $response = $this->controller->renderJson($data);
  148. $this->assertEquals($response->headers->get('Content-Type'), 'application/json');
  149. $this->assertEquals(json_encode($data), $response->getContent());
  150. }
  151. public function testRenderJson2()
  152. {
  153. $data = array('example'=>'123', 'foo'=>'bar');
  154. $this->request->headers->set('Content-Type', 'multipart/form-data');
  155. $response = $this->controller->renderJson($data);
  156. $this->assertEquals($response->headers->get('Content-Type'), 'application/json');
  157. $this->assertEquals(json_encode($data), $response->getContent());
  158. }
  159. public function testRenderJsonAjax()
  160. {
  161. $data = array('example'=>'123', 'foo'=>'bar');
  162. $this->request->attributes->set('_xml_http_request', true);
  163. $this->request->headers->set('Content-Type', 'multipart/form-data');
  164. $response = $this->controller->renderJson($data);
  165. $this->assertEquals($response->headers->get('Content-Type'), 'text/plain');
  166. $this->assertEquals(json_encode($data), $response->getContent());
  167. }
  168. public function testIsXmlHttpRequest()
  169. {
  170. $this->assertFalse($this->controller->isXmlHttpRequest());
  171. $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
  172. $this->assertTrue($this->controller->isXmlHttpRequest());
  173. $this->request->headers->remove('X-Requested-With');
  174. $this->assertFalse($this->controller->isXmlHttpRequest());
  175. $this->request->attributes->set('_xml_http_request', true);
  176. $this->assertTrue($this->controller->isXmlHttpRequest());
  177. }
  178. public function testConfigureWithException()
  179. {
  180. $this->setExpectedException('RuntimeException', 'There is no `_sonata_admin` defined for the controller `Sonata\AdminBundle\Controller\CRUDController`');
  181. $this->request->attributes->remove('_sonata_admin');
  182. $this->controller->configure();
  183. }
  184. public function testConfigureWithException2()
  185. {
  186. $this->setExpectedException('RuntimeException', 'Unable to find the admin class related to the current controller (Sonata\AdminBundle\Controller\CRUDController)');
  187. $this->request->attributes->set('_sonata_admin', 'nonexistent.admin');
  188. $this->controller->configure();
  189. }
  190. public function testGetBaseTemplate()
  191. {
  192. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->controller->getBaseTemplate());
  193. $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
  194. $this->assertEquals('SonataAdminBundle::ajax_layout.html.twig', $this->controller->getBaseTemplate());
  195. $this->request->headers->remove('X-Requested-With');
  196. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->controller->getBaseTemplate());
  197. $this->request->attributes->set('_xml_http_request', true);
  198. $this->assertEquals('SonataAdminBundle::ajax_layout.html.twig', $this->controller->getBaseTemplate());
  199. }
  200. public function testRender()
  201. {
  202. $this->parameters = array();
  203. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->render('FooAdminBundle::foo.html.twig', array()));
  204. $this->assertEquals($this->admin, $this->parameters['admin']);
  205. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
  206. $this->assertEquals($this->pool, $this->parameters['admin_pool']);
  207. }
  208. public function testRenderCustomParams()
  209. {
  210. $this->parameters = array();
  211. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->render('FooAdminBundle::foo.html.twig', array('foo'=>'bar')));
  212. $this->assertEquals($this->admin, $this->parameters['admin']);
  213. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
  214. $this->assertEquals($this->pool, $this->parameters['admin_pool']);
  215. $this->assertEquals('bar', $this->parameters['foo']);
  216. }
  217. public function testRenderAjax()
  218. {
  219. $this->parameters = array();
  220. $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
  221. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->render('FooAdminBundle::foo.html.twig', array('foo'=>'bar')));
  222. $this->assertEquals($this->admin, $this->parameters['admin']);
  223. $this->assertEquals('SonataAdminBundle::ajax_layout.html.twig', $this->parameters['base_template']);
  224. $this->assertEquals($this->pool, $this->parameters['admin_pool']);
  225. $this->assertEquals('bar', $this->parameters['foo']);
  226. }
  227. public function testListActionAccessDenied()
  228. {
  229. $this->setExpectedException('Symfony\Component\Security\Core\Exception\AccessDeniedException');
  230. $this->admin->expects($this->once())
  231. ->method('isGranted')
  232. ->with($this->equalTo('LIST'))
  233. ->will($this->returnValue(false));
  234. $this->controller->listAction();
  235. }
  236. public function testListAction()
  237. {
  238. $datagrid = $this->getMock('Sonata\AdminBundle\Datagrid\DatagridInterface');
  239. $this->admin->expects($this->once())
  240. ->method('isGranted')
  241. ->with($this->equalTo('LIST'))
  242. ->will($this->returnValue(true));
  243. $form = $this->getMockBuilder('Symfony\Component\Form\Form')
  244. ->disableOriginalConstructor()
  245. ->getMock();
  246. $form->expects($this->once())
  247. ->method('createView')
  248. ->will($this->returnValue($this->getMock('Symfony\Component\Form\FormView')));
  249. $this->admin->expects($this->once())
  250. ->method('getDatagrid')
  251. ->will($this->returnValue($datagrid));
  252. $datagrid->expects($this->once())
  253. ->method('getForm')
  254. ->will($this->returnValue($form));
  255. $this->parameters = array();
  256. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->listAction());
  257. $this->assertEquals($this->admin, $this->parameters['admin']);
  258. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
  259. $this->assertEquals($this->pool, $this->parameters['admin_pool']);
  260. $this->assertEquals('list', $this->parameters['action']);
  261. $this->assertInstanceOf('Symfony\Component\Form\FormView', $this->parameters['form']);
  262. $this->assertInstanceOf('Sonata\AdminBundle\Datagrid\DatagridInterface', $this->parameters['datagrid']);
  263. $this->assertEquals('', $this->parameters['csrf_token']);
  264. }
  265. public function testBatchActionDeleteAccessDenied()
  266. {
  267. $this->setExpectedException('Symfony\Component\Security\Core\Exception\AccessDeniedException');
  268. $this->admin->expects($this->once())
  269. ->method('isGranted')
  270. ->with($this->equalTo('DELETE'))
  271. ->will($this->returnValue(false));
  272. $this->controller->batchActionDelete($this->getMock('Sonata\AdminBundle\Datagrid\ProxyQueryInterface'));
  273. }
  274. public function testBatchActionDelete()
  275. {
  276. $modelManager = $this->getMock('Sonata\AdminBundle\Model\ModelManagerInterface');
  277. $this->admin->expects($this->once())
  278. ->method('isGranted')
  279. ->with($this->equalTo('DELETE'))
  280. ->will($this->returnValue(true));
  281. $this->admin->expects($this->once())
  282. ->method('getModelManager')
  283. ->will($this->returnValue($modelManager));
  284. $this->admin->expects($this->once())
  285. ->method('getFilterParameters')
  286. ->will($this->returnValue(array('foo'=>'bar')));
  287. $result = $this->controller->batchActionDelete($this->getMock('Sonata\AdminBundle\Datagrid\ProxyQueryInterface'));
  288. $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result);
  289. $this->assertSame(array('flash_batch_delete_success'), $this->session->getFlashBag()->get('sonata_flash_success'));
  290. $this->assertEquals('list?filter%5Bfoo%5D=bar', $result->getTargetUrl());
  291. }
  292. public function testBatchActionDeleteWithModelManagerException()
  293. {
  294. $modelManager = $this->getMock('Sonata\AdminBundle\Model\ModelManagerInterface');
  295. $modelManager->expects($this->once())
  296. ->method('batchDelete')
  297. ->will($this->returnCallback(function() {
  298. throw new ModelManagerException();
  299. }));
  300. $this->admin->expects($this->once())
  301. ->method('getModelManager')
  302. ->will($this->returnValue($modelManager));
  303. $this->admin->expects($this->once())
  304. ->method('getFilterParameters')
  305. ->will($this->returnValue(array('foo'=>'bar')));
  306. $result = $this->controller->batchActionDelete($this->getMock('Sonata\AdminBundle\Datagrid\ProxyQueryInterface'));
  307. $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result);
  308. $this->assertSame(array('flash_batch_delete_error'), $this->session->getFlashBag()->get('sonata_flash_error'));
  309. $this->assertEquals('list?filter%5Bfoo%5D=bar', $result->getTargetUrl());
  310. }
  311. public function testShowActionNotFoundException()
  312. {
  313. $this->setExpectedException('Symfony\Component\HttpKernel\Exception\NotFoundHttpException');
  314. $this->admin->expects($this->once())
  315. ->method('getObject')
  316. ->will($this->returnValue(false));
  317. $this->controller->showAction();
  318. }
  319. public function testShowActionAccessDenied()
  320. {
  321. $this->setExpectedException('Symfony\Component\Security\Core\Exception\AccessDeniedException');
  322. $this->admin->expects($this->once())
  323. ->method('getObject')
  324. ->will($this->returnValue(new \stdClass()));
  325. $this->admin->expects($this->once())
  326. ->method('isGranted')
  327. ->with($this->equalTo('VIEW'))
  328. ->will($this->returnValue(false));
  329. $this->controller->showAction();
  330. }
  331. public function testShowAction()
  332. {
  333. $object = new \stdClass();
  334. $this->admin->expects($this->once())
  335. ->method('getObject')
  336. ->will($this->returnValue($object));
  337. $this->admin->expects($this->once())
  338. ->method('isGranted')
  339. ->with($this->equalTo('VIEW'))
  340. ->will($this->returnValue(true));
  341. $show = $this->getMock('Sonata\AdminBundle\Admin\FieldDescriptionCollection');
  342. $this->admin->expects($this->once())
  343. ->method('getShow')
  344. ->will($this->returnValue($show));
  345. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->showAction());
  346. $this->assertEquals($this->admin, $this->parameters['admin']);
  347. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
  348. $this->assertEquals($this->pool, $this->parameters['admin_pool']);
  349. $this->assertEquals('show', $this->parameters['action']);
  350. $this->assertInstanceOf('Sonata\AdminBundle\Admin\FieldDescriptionCollection', $this->parameters['elements']);
  351. $this->assertEquals($object, $this->parameters['object']);
  352. }
  353. /**
  354. * @dataProvider getRedirectToTests
  355. */
  356. public function testRedirectTo($expected, $queryParams, $hasActiveSubclass)
  357. {
  358. $this->admin->expects($this->any())
  359. ->method('generateObjectUrl')
  360. ->will($this->returnCallback(function($name, $object, array $parameters = array(), $absolute = false) {
  361. return get_class($object).'_'.$name;
  362. }));
  363. $this->admin->expects($this->any())
  364. ->method('hasActiveSubclass')
  365. ->will($this->returnValue($hasActiveSubclass));
  366. $object = new \stdClass();
  367. foreach ($queryParams as $key => $value) {
  368. $this->request->query->set($key, $value);
  369. }
  370. $response = $this->controller->redirectTo($object);
  371. $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
  372. $this->assertEquals($expected, $response->getTargetUrl());
  373. }
  374. public function getRedirectToTests()
  375. {
  376. return array(
  377. array('stdClass_edit', array(), false),
  378. array('list', array('btn_update_and_list'=>true), false),
  379. array('list', array('btn_create_and_list'=>true), false),
  380. array('create', array('btn_create_and_create'=>true), false),
  381. array('create?subclass=foo', array('btn_create_and_create'=>true, 'subclass'=>'foo'), true),
  382. );
  383. }
  384. public function testAddFlash()
  385. {
  386. $this->controller->addFlash('foo', 'bar');
  387. $this->assertSame(array('bar'), $this->session->getFlashBag()->get('foo'));
  388. }
  389. public function testDeleteActionNotFoundException()
  390. {
  391. $this->setExpectedException('Symfony\Component\HttpKernel\Exception\NotFoundHttpException');
  392. $this->admin->expects($this->once())
  393. ->method('getObject')
  394. ->will($this->returnValue(false));
  395. $this->controller->deleteAction(1);
  396. }
  397. public function testDeleteActionAccessDenied()
  398. {
  399. $this->setExpectedException('Symfony\Component\Security\Core\Exception\AccessDeniedException');
  400. $this->admin->expects($this->once())
  401. ->method('getObject')
  402. ->will($this->returnValue(new \stdClass()));
  403. $this->admin->expects($this->once())
  404. ->method('isGranted')
  405. ->with($this->equalTo('DELETE'))
  406. ->will($this->returnValue(false));
  407. $this->controller->deleteAction(1);
  408. }
  409. public function testDeleteAction()
  410. {
  411. $object = new \stdClass();
  412. $this->admin->expects($this->once())
  413. ->method('getObject')
  414. ->will($this->returnValue($object));
  415. $this->admin->expects($this->once())
  416. ->method('isGranted')
  417. ->with($this->equalTo('DELETE'))
  418. ->will($this->returnValue(true));
  419. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->deleteAction(1));
  420. $this->assertEquals($this->admin, $this->parameters['admin']);
  421. $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
  422. $this->assertEquals($this->pool, $this->parameters['admin_pool']);
  423. $this->assertEquals('delete', $this->parameters['action']);
  424. $this->assertEquals($object, $this->parameters['object']);
  425. $this->assertEquals('', $this->parameters['csrf_token']);
  426. }
  427. public function testDeleteActionAjaxSuccess()
  428. {
  429. $object = new \stdClass();
  430. $this->admin->expects($this->once())
  431. ->method('getObject')
  432. ->will($this->returnValue($object));
  433. $this->admin->expects($this->once())
  434. ->method('isGranted')
  435. ->with($this->equalTo('DELETE'))
  436. ->will($this->returnValue(true));
  437. $this->request->setMethod('DELETE');
  438. $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
  439. $response = $this->controller->deleteAction(1);
  440. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
  441. $this->assertEquals(json_encode(array('result'=>'ok')), $response->getContent());
  442. }
  443. public function testDeleteActionAjaxError()
  444. {
  445. $object = new \stdClass();
  446. $this->admin->expects($this->once())
  447. ->method('getObject')
  448. ->will($this->returnValue($object));
  449. $this->admin->expects($this->once())
  450. ->method('isGranted')
  451. ->with($this->equalTo('DELETE'))
  452. ->will($this->returnValue(true));
  453. $this->admin->expects($this->once())
  454. ->method('delete')
  455. ->will($this->returnCallback(function() {
  456. throw new ModelManagerException();
  457. }));
  458. $this->request->setMethod('DELETE');
  459. $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
  460. $response = $this->controller->deleteAction(1);
  461. $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
  462. $this->assertEquals(json_encode(array('result'=>'error')), $response->getContent());
  463. }
  464. public function testDeleteActionSuccess()
  465. {
  466. $object = new \stdClass();
  467. $this->admin->expects($this->once())
  468. ->method('getObject')
  469. ->will($this->returnValue($object));
  470. $this->admin->expects($this->once())
  471. ->method('isGranted')
  472. ->with($this->equalTo('DELETE'))
  473. ->will($this->returnValue(true));
  474. $this->request->setMethod('DELETE');
  475. $response = $this->controller->deleteAction(1);
  476. $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
  477. $this->assertSame(array('flash_delete_success'), $this->session->getFlashBag()->get('sonata_flash_success'));
  478. $this->assertEquals('list', $response->getTargetUrl());
  479. }
  480. public function testDeleteActionError()
  481. {
  482. $object = new \stdClass();
  483. $this->admin->expects($this->once())
  484. ->method('getObject')
  485. ->will($this->returnValue($object));
  486. $this->admin->expects($this->once())
  487. ->method('isGranted')
  488. ->with($this->equalTo('DELETE'))
  489. ->will($this->returnValue(true));
  490. $this->admin->expects($this->once())
  491. ->method('delete')
  492. ->will($this->returnCallback(function() {
  493. throw new ModelManagerException();
  494. }));
  495. $this->request->setMethod('DELETE');
  496. $response = $this->controller->deleteAction(1);
  497. $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
  498. $this->assertSame(array('flash_delete_error'), $this->session->getFlashBag()->get('sonata_flash_error'));
  499. $this->assertEquals('list', $response->getTargetUrl());
  500. }
  501. }