OtWebTestCase.php 7.2 KB

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