OtWebTestCase.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. namespace App\Tests\Application;
  3. use App\Entity\Access\Access;
  4. use App\Entity\Person\Person;
  5. use App\Entity\Public\FederationStructure;
  6. use App\Enum\Organization\LegalEnum;
  7. use App\Enum\Organization\PrincipalTypeEnum;
  8. use App\Enum\Organization\SettingsProductEnum;
  9. use App\Tests\Fixture\Factory\Access\AccessFactory;
  10. use App\Tests\Fixture\Factory\Organization\OrganizationFactory;
  11. use App\Tests\Fixture\Factory\Organization\SettingsFactory;
  12. use App\Tests\Fixture\Factory\Person\PersonFactory;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Doctrine\Common\DataFixtures\Purger\ORMPurger;
  15. use Symfony\Bundle\SecurityBundle\Security;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\DomCrawler\Crawler;
  18. use ApiPlatform\Symfony\Bundle\Test\Client;
  19. use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
  20. use Symfony\Contracts\HttpClient\ResponseInterface;
  21. use Zenstruck\Foundry\Proxy;
  22. /**
  23. * Base class for applicative tests
  24. */
  25. abstract class OtWebTestCase extends ApiTestCase
  26. {
  27. protected EntityManagerInterface $em;
  28. protected Client $client;
  29. protected Access | Proxy | null $user = null;
  30. protected ?string $securityToken = null;
  31. /**
  32. * Executed before each test
  33. *
  34. * @return void
  35. * @throws \Exception
  36. */
  37. public function setup(): void
  38. {
  39. // Boot le kernel symfony et récupère l'entity manager
  40. // @see https://symfony.com/doc/current/testing.html#retrieving-services-in-the-test
  41. self::bootKernel();
  42. $this->em = static::getContainer()->get(EntityManagerInterface::class);
  43. // Purge DB before populating new fixtures
  44. $this->purgeDb();
  45. // Définit les fixtures et flush
  46. $this->loadFixture();
  47. $this->em->flush();
  48. // Instancie le client qui exécutera les requêtes à l'api
  49. // @see https://symfony.com/doc/current/testing.html#making-requests
  50. $this->client = static::createClient();
  51. }
  52. /**
  53. * Delete all DB records before populating fixtures.
  54. *
  55. * @return void
  56. * @throws \Doctrine\DBAL\Exception
  57. */
  58. private function purgeDb()
  59. {
  60. if (!preg_match('/.*test.*/', $this->em->getConnection()->getDatabase())) {
  61. throw new \RuntimeException("The DB name shall contain 'test' in its name to be purge");
  62. }
  63. $this->em->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 0;');
  64. $purger = new ORMPurger($this->em);
  65. $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
  66. $purger->purge();
  67. $this->em->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 1;');
  68. }
  69. /**
  70. * Create and persist the fixtures (do not flush)
  71. *
  72. * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#same-entities-used-in-these-docs
  73. *
  74. * @return void
  75. */
  76. protected function loadFixture(): void
  77. {
  78. $person = PersonFactory::createOne(
  79. [
  80. 'username' => 'username',
  81. 'password' => 'password'
  82. ]
  83. );
  84. $organization = OrganizationFactory::createOne([
  85. 'legalStatus' => LegalEnum::ASSOCIATION_LAW_1901()->getValue(),
  86. 'principalType' => PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY()->getValue(),
  87. 'name' => 'My Organization'
  88. ]);
  89. SettingsFactory::createOne([
  90. 'product' => SettingsProductEnum::ARTIST(),
  91. 'organization' => $organization
  92. ]);
  93. $this->user = AccessFactory::createOne([
  94. 'person' => $person,
  95. 'organization' => $organization,
  96. 'roles' => ['ROLE_USERS_VIEW']
  97. ]);
  98. }
  99. /**
  100. * Send a requests, parse the hydra response and return an object or a Collection
  101. *
  102. * @param string $method
  103. * @param string $route
  104. * @param array<mixed> $data
  105. * @param array<mixed> $headers
  106. * @return ResponseInterface
  107. */
  108. protected function request(string $method, string $route, array | null $data = null, array $headers = []): ResponseInterface
  109. {
  110. if ($this->user) {
  111. $headers = array_merge(
  112. ['x-accessid' => $this->user->getId(), 'authorization' => 'BEARER ' . $this->securityToken],
  113. $headers
  114. );
  115. }
  116. $parameters = ['headers' => $headers];
  117. if ($data) {
  118. $parameters['json'] = $data;
  119. }
  120. return $this->client->request(
  121. $method,
  122. $route,
  123. $parameters
  124. );
  125. }
  126. /**
  127. * Send a GET request and return the response parsed content
  128. *
  129. * @param string $route
  130. * @param array<mixed> $headers
  131. * @return ResponseInterface
  132. */
  133. protected function get(string $route, array $headers = []): ResponseInterface
  134. {
  135. return $this->request(
  136. Request::METHOD_GET,
  137. $route,
  138. null,
  139. $headers
  140. );
  141. }
  142. /**
  143. * Send a PUT request and return the response parsed content
  144. *
  145. * @param string $route
  146. * @param array<mixed> $data
  147. * @param array<mixed> $headers
  148. * @return ResponseInterface
  149. */
  150. protected function put(string $route, array $data, array $headers = []): ResponseInterface
  151. {
  152. return $this->request(
  153. Request::METHOD_PUT,
  154. $route,
  155. $data,
  156. $headers
  157. );
  158. }
  159. /**
  160. * Send a POST request and return the response parsed content
  161. *
  162. * @param string $route
  163. * @param array<mixed> $data
  164. * @param array<mixed> $headers
  165. * @return ResponseInterface
  166. */
  167. protected function post(string $route, array $data, array $headers = []): ResponseInterface
  168. {
  169. return $this->request(
  170. Request::METHOD_POST,
  171. $route,
  172. $data,
  173. $headers
  174. );
  175. }
  176. /**
  177. * Send a DELETE request and return the response parsed content
  178. *
  179. * @param string $route
  180. * @param array<mixed> $headers
  181. * @return ResponseInterface
  182. */
  183. protected function delete(string $route, array $headers = []): ResponseInterface
  184. {
  185. return $this->request(
  186. Request::METHOD_DELETE,
  187. $route,
  188. null,
  189. $headers
  190. );
  191. }
  192. /**
  193. * Login as the given Access user
  194. *
  195. * @param Proxy|Access $access
  196. * @return void
  197. * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
  198. * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
  199. * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
  200. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
  201. */
  202. protected function loginAs(Proxy | Access $access): void
  203. {
  204. $person = $access->getPerson();
  205. $response = $this->post(
  206. '/login_check',
  207. ['username' => $person->getUsername(), 'password' => $person->getPassword()]
  208. );
  209. $content = $response->getContent();
  210. $this->securityToken = json_decode($content)->token;
  211. $this->user = $access;
  212. }
  213. /**
  214. * Assert that the response has the expected status code and is well formated
  215. *
  216. * @param string $resourceClass
  217. * @param int $expectedStatus
  218. * @return void
  219. */
  220. protected function validateCollectionSchema(string $resourceClass, int $expectedStatus = 200): void
  221. {
  222. $this->assertResponseStatusCodeSame($expectedStatus);
  223. if ($expectedStatus == 200) {
  224. $this->assertResponseIsSuccessful();
  225. }
  226. // Asserts that the returned content type is JSON-LD (the default)
  227. $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
  228. // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform
  229. // >>> Issue with the json typed PublicStructure::addresses properties
  230. // $this->assertMatchesResourceCollectionJsonSchema($resourceClass);
  231. }
  232. }