OtWebTestCase.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. <?php
  2. namespace App\Tests\Application;
  3. use DateTime;
  4. use Zenstruck\Foundry\Proxy;
  5. use App\Entity\Access\Access;
  6. use App\Enum\Core\TimeZoneEnum;
  7. use App\Enum\Education\CycleEnum;
  8. use App\Enum\Booking\VisibilityEnum;
  9. use App\Enum\Organization\LegalEnum;
  10. use App\Enum\Core\ContactPointTypeEnum;
  11. use App\Enum\Education\PeriodicityEnum;
  12. use Doctrine\ORM\EntityManagerInterface;
  13. use ApiPlatform\Symfony\Bundle\Test\Client;
  14. use App\Enum\Organization\PrincipalTypeEnum;
  15. use App\Enum\Organization\BulletinOutputEnum;
  16. use App\Enum\Organization\BulletinPeriodEnum;
  17. use App\Enum\Organization\SendToBulletinEnum;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use App\Enum\Organization\SettingsProductEnum;
  20. use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
  21. use Doctrine\Common\DataFixtures\Purger\ORMPurger;
  22. use App\Tests\Fixture\Factory\Access\AccessFactory;
  23. use App\Tests\Fixture\Factory\Booking\EventFactory;
  24. use App\Tests\Fixture\Factory\Network\NetworkFactory;
  25. use App\Tests\Fixture\Factory\Person\PersonFactory;
  26. use Symfony\Contracts\HttpClient\ResponseInterface;
  27. use App\Tests\Fixture\Factory\Education\CycleFactory;
  28. use App\Tests\Fixture\Factory\Core\ContactPointFactory;
  29. use App\Enum\Education\AdvancedEducationNotationTypeEnum;
  30. use App\Tests\Fixture\Factory\Billing\ResidenceAreaFactory;
  31. use App\Tests\Fixture\Factory\Mobyt\MobytUserStatusFactory;
  32. use App\Tests\Fixture\Factory\Organization\SettingsFactory;
  33. use App\Tests\Fixture\Factory\Billing\BillingSettingFactory;
  34. use App\Tests\Fixture\Factory\Organization\SubdomainFactory;
  35. use App\Tests\Fixture\Factory\Organization\ParametersFactory;
  36. use App\Tests\Fixture\Factory\Education\EducationTimingFactory;
  37. use App\Tests\Fixture\Factory\Network\NetworkOrganizationFactory;
  38. use App\Tests\Fixture\Factory\Organization\OrganizationFactory;
  39. /**
  40. * Base class for applicative tests.
  41. */
  42. abstract class OtWebTestCase extends ApiTestCase
  43. {
  44. protected EntityManagerInterface $em;
  45. protected Client $client;
  46. protected Access|Proxy|null $user = null;
  47. protected ?string $securityToken = null;
  48. /**
  49. * Executed before each test.
  50. *
  51. * @throws \Exception
  52. */
  53. public function setup(): void
  54. {
  55. // Boot le kernel symfony et récupère l'entity manager
  56. // @see https://symfony.com/doc/current/testing.html#retrieving-services-in-the-test
  57. self::bootKernel();
  58. $this->em = static::getContainer()->get(EntityManagerInterface::class);
  59. // Purge DB before populating new fixtures
  60. $this->purgeDb();
  61. // Définit les fixtures et flush
  62. $this->loadFixture();
  63. $this->em->flush();
  64. // Instancie le client qui exécutera les requêtes à l'api
  65. // @see https://symfony.com/doc/current/testing.html#making-requests
  66. $this->client = static::createClient();
  67. }
  68. public function loadSchema()
  69. {
  70. $command = 'bin/console --env=staging doctrine:schema:update --force';
  71. $output = shell_exec($command);
  72. echo $output;
  73. }
  74. private function purgeDb()
  75. {
  76. if (!preg_match('/.*test.*/', $this->em->getConnection()->getDatabase())) {
  77. throw new \RuntimeException("The DB name shall contain 'test' in its name to be purge");
  78. }
  79. $this->em->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 0;');
  80. $purger = new ORMPurger($this->em);
  81. $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
  82. $purger->purge();
  83. $this->resetAutoIncrement();
  84. $this->em->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 1;');
  85. }
  86. private function resetAutoIncrement()
  87. {
  88. $connection = $this->em->getConnection();
  89. $schemaManager = $connection->getSchemaManager();
  90. foreach ($schemaManager->listTableNames() as $tableName) {
  91. $connection->executeStatement("ALTER TABLE $tableName AUTO_INCREMENT = 1;");
  92. }
  93. }
  94. private function resetAutoIncrement()
  95. {
  96. $connection = $this->em->getConnection();
  97. $schemaManager = $connection->getSchemaManager();
  98. foreach ($schemaManager->listTableNames() as $tableName) {
  99. $connection->executeStatement("ALTER TABLE $tableName AUTO_INCREMENT = 1;");
  100. }
  101. }
  102. /**
  103. * Create and persist the fixtures (do not flush).
  104. *
  105. * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#same-entities-used-in-these-docs
  106. */
  107. protected function loadFixture(): void
  108. {
  109. $person = PersonFactory::createOne(
  110. [
  111. 'username' => 'username',
  112. 'password' => 'password'
  113. ]
  114. );
  115. $personOfOtherOrganization = PersonFactory::createOne(
  116. [
  117. 'username' => 'intruOfRoot',
  118. 'password' => 'password'
  119. ]
  120. );
  121. $contactPoint = ContactPointFactory::createOne([
  122. 'contactType' => ContactPointTypeEnum::PRINCIPAL
  123. ]);
  124. $parameters = ParametersFactory::createOne([
  125. 'educationPeriodicity' => PeriodicityEnum::MONTHLY,
  126. 'financialDate' => new DateTime(),
  127. 'musicalDate' => new DateTime(),
  128. 'startCourseDate' => new DateTime(),
  129. 'endCourseDate' => new DateTime(),
  130. 'average' => 20,
  131. 'editCriteriaNotationByAdminOnly' => true,
  132. 'smsSenderName' => 'MySender',
  133. 'logoDonorsMove' => false,
  134. 'subDomain' => 'subdomain',
  135. 'website' => 'https://www.example.com',
  136. 'otherWebsite' => 'https://www.otherwebsite.com',
  137. 'customDomain' => 'https://www.customdomain.com',
  138. 'desactivateOpentalentSiteWeb' => false,
  139. 'bulletinPeriod' => BulletinPeriodEnum::YEAR,
  140. 'bulletinWithTeacher' => false,
  141. 'bulletinPrintAddress' => false,
  142. 'bulletinSignatureDirector' => true,
  143. 'bulletinDisplayLevelAcquired' => true,
  144. 'bulletinShowEducationWithoutEvaluation' => false,
  145. 'bulletinViewTestResults' => false,
  146. 'bulletinShowAbsences' => false,
  147. 'bulletinShowAverages' => true,
  148. 'bulletinOutput' => BulletinOutputEnum::SEND_BY_EMAIL,
  149. 'bulletinEditWithoutEvaluation' => true,
  150. 'bulletinReceiver' => SendToBulletinEnum::STUDENTS_AND_THEIR_GUARDIANS,
  151. 'usernameSMS' => '2iosinterne',
  152. 'passwordSMS' => '2iosot74',
  153. 'showAdherentList' => true,
  154. 'studentsAreAdherents' => false,
  155. 'timezone' => TimeZoneEnum::EUROPE_PARIS,
  156. 'advancedEducationNotationType' => AdvancedEducationNotationTypeEnum::BY_EDUCATION,
  157. 'sendAttendanceEmail' => true,
  158. 'sendAttendanceSms' => true,
  159. 'generateAttendanceReport' => true,
  160. 'consultPedagogicResult' => true,
  161. 'consultTeacherListing' => true,
  162. 'periodValidation' => true,
  163. ]);
  164. $parameters2 = ParametersFactory::createOne([
  165. 'educationPeriodicity' => PeriodicityEnum::MONTHLY,
  166. 'financialDate' => new DateTime(),
  167. 'musicalDate' => new DateTime(),
  168. 'startCourseDate' => new DateTime(),
  169. 'endCourseDate' => new DateTime(),
  170. 'average' => 20,
  171. 'editCriteriaNotationByAdminOnly' => true,
  172. 'smsSenderName' => 'toto',
  173. 'logoDonorsMove' => false,
  174. 'subDomain' => 'subdomain',
  175. 'website' => 'https://www.toto.com',
  176. 'otherWebsite' => 'https://www.toto.com',
  177. 'customDomain' => 'https://www.toto.com',
  178. 'desactivateOpentalentSiteWeb' => false,
  179. 'bulletinPeriod' => BulletinPeriodEnum::YEAR,
  180. 'bulletinWithTeacher' => false,
  181. 'bulletinPrintAddress' => false,
  182. 'bulletinSignatureDirector' => true,
  183. 'bulletinDisplayLevelAcquired' => true,
  184. 'bulletinShowEducationWithoutEvaluation' => false,
  185. 'bulletinViewTestResults' => false,
  186. 'bulletinShowAbsences' => false,
  187. 'bulletinShowAverages' => true,
  188. 'bulletinOutput' => BulletinOutputEnum::SEND_BY_EMAIL,
  189. 'bulletinEditWithoutEvaluation' => true,
  190. 'bulletinReceiver' => SendToBulletinEnum::STUDENTS_AND_THEIR_GUARDIANS,
  191. 'usernameSMS' => 'toto',
  192. 'passwordSMS' => 'toto',
  193. 'showAdherentList' => true,
  194. 'studentsAreAdherents' => false,
  195. 'timezone' => TimeZoneEnum::EUROPE_PARIS,
  196. 'advancedEducationNotationType' => AdvancedEducationNotationTypeEnum::BY_EDUCATION,
  197. 'sendAttendanceEmail' => true,
  198. 'sendAttendanceSms' => true,
  199. 'generateAttendanceReport' => true,
  200. 'consultPedagogicResult' => true,
  201. 'consultTeacherListing' => true,
  202. 'periodValidation' => true,
  203. ]);
  204. $organization = OrganizationFactory::createOne([
  205. 'legalStatus' => LegalEnum::ASSOCIATION_LAW_1901,
  206. 'principalType' => PrincipalTypeEnum::NATIONAL_FEDERATION,
  207. 'name' => 'Root',
  208. 'parameters' => $parameters,
  209. 'siretNumber' => '34919841600035',
  210. 'identifier' => 'FR042100000050',
  211. ]);
  212. $network1 = NetworkFactory::createOne([
  213. 'name' => 'Network 1',
  214. 'logo' => 'logo',
  215. 'url' => 'https://www.network1.com',
  216. ]);
  217. $network2 = NetworkFactory::createOne([
  218. 'name' => 'Network 2',
  219. 'logo' => 'logo',
  220. 'url' => 'https://www.network2.com',
  221. ]);
  222. $cmfNetwork = NetworkFactory::createOne([
  223. 'name' => 'CMF',
  224. 'logo' => 'logo',
  225. 'url' => 'https://www.cmf.com',
  226. ]);
  227. $networkOrganization = NetworkOrganizationFactory::createOne([
  228. 'network' => $cmfNetwork,
  229. 'organization' => $organization,
  230. 'startDate' => new DateTime('2001-01-01'),
  231. 'endDate' => new DateTime('2031-12-31'),
  232. 'leadingCause' => 'leadingCause'
  233. ]);
  234. $billingSetting = BillingSettingFactory::createOne([
  235. 'organization' => $organization,
  236. ]);
  237. $residenceArea = ResidenceAreaFactory::createOne([
  238. 'label' => 'Résidence 1',
  239. 'billingSetting' => $billingSetting,
  240. ]);
  241. $organization2 = OrganizationFactory::createOne([
  242. 'legalStatus' => LegalEnum::ASSOCIATION_LAW_1901,
  243. 'principalType' => PrincipalTypeEnum::NATIONAL_FEDERATION,
  244. 'name' => 'Other Organization',
  245. 'parameters' => $parameters2,
  246. ]);
  247. $settings2 = SettingsFactory::createOne([
  248. 'product' => SettingsProductEnum::SCHOOL_PREMIUM,
  249. 'organization' => $organization2,
  250. 'modules' => ['BillingAdministration']
  251. ]);
  252. $mobyteUserStatus = MobytUserStatusFactory::createOne([
  253. 'organizationId' => $organization->getId(),
  254. 'active' => true,
  255. 'amount' => 100,
  256. 'money' => 100
  257. ]);
  258. $cycle = CycleFactory::createOne([
  259. 'organization' => $organization,
  260. 'label' => 'Cycle 1',
  261. 'cycleEnum' => CycleEnum::CYCLE_1
  262. ]);
  263. $settings = SettingsFactory::createOne([
  264. 'product' => SettingsProductEnum::SCHOOL_PREMIUM,
  265. 'organization' => $organization,
  266. 'modules' => [
  267. 'Sms' => true,
  268. // 'BillingAdministration' => true,
  269. ]
  270. ]);
  271. $educationTimings = EducationTimingFactory::createOne([
  272. 'organization' => $organization,
  273. 'timing' => 45
  274. ]);
  275. $educationTimings2 = EducationTimingFactory::createOne([
  276. 'organization' => $organization2,
  277. 'timing' => 60
  278. ]);
  279. $subdomain = SubdomainFactory::createOne([
  280. 'organization' => $organization,
  281. 'subdomain' => 'subdomain',
  282. 'active' => true
  283. ]);
  284. $event = EventFactory::createOne([
  285. 'organization' => $organization,
  286. 'name' => 'My event',
  287. 'datetimeStart' => new \DateTime(),
  288. 'datetimeEnd' => new \DateTime(),
  289. 'visibility' => VisibilityEnum::PUBLIC_VISIBILITY
  290. ]);
  291. $this->user = AccessFactory::createOne([
  292. 'person' => $person,
  293. 'organization' => $organization,
  294. 'roles' => ["ROLE_ADMIN","ROLE_ADMIN_CORE","ROLE_SUPER_ADMIN", "ROLE_ORGANIZATION_VIEW","ROLE_ORGANIZATION" ],
  295. 'adminAccess' => true,
  296. 'activityYear' => 2021
  297. ]);
  298. $student = PersonFactory::createOne([
  299. 'username' => 'student',
  300. 'password' => 'password'
  301. ]);
  302. $accessWithNoRole = AccessFactory::createOne([
  303. 'person' => $student,
  304. 'organization' => $organization,
  305. 'roles' => ["ROLE_STUDENT"],
  306. 'adminAccess' => false
  307. ]);
  308. $acccesFromOtherOrganization = AccessFactory::createOne([
  309. 'person' => $personOfOtherOrganization,
  310. 'organization' => $organization2,
  311. 'roles' => ["ROLE_ADMIN","ROLE_ADMIN_CORE","ROLE_SUPER_ADMIN", "ROLE_ORGANIZATION_VIEW","ROLE_ORGANIZATION" ],
  312. 'adminAccess' => true,
  313. ]);
  314. }
  315. /**
  316. * Send a requests, parse the hydra response and return an object or a Collection.
  317. *
  318. * @param array<mixed> $data
  319. * @param array<mixed> $headers
  320. */
  321. protected function request(string $method, string $route, array | null $data = null, array $headers = []): ResponseInterface
  322. {
  323. if ($this->user) {
  324. $headers = array_merge(
  325. ['x-accessid' => $this->user->getId(), 'authorization' => 'BEARER '.$this->securityToken],
  326. $headers
  327. );
  328. }
  329. $parameters = ['headers' => $headers];
  330. if ($data) {
  331. $parameters['json'] = $data;
  332. }
  333. return $this->client->request(
  334. $method,
  335. $route,
  336. $parameters
  337. );
  338. }
  339. /**
  340. * Send a GET request and return the response parsed content.
  341. *
  342. * @param array<mixed> $headers
  343. */
  344. protected function get(string $route, array $headers = []): ResponseInterface
  345. {
  346. return $this->request(
  347. Request::METHOD_GET,
  348. $route,
  349. null,
  350. $headers
  351. );
  352. }
  353. /**
  354. * Send a PUT request and return the response parsed content.
  355. *
  356. * @param array<mixed> $data
  357. * @param array<mixed> $headers
  358. */
  359. protected function put(string $route, array $data, array $headers = []): ResponseInterface
  360. {
  361. return $this->request(
  362. Request::METHOD_PUT,
  363. $route,
  364. $data,
  365. $headers
  366. );
  367. }
  368. /**
  369. * Send a POST request and return the response parsed content.
  370. *
  371. * @param array<mixed> $data
  372. * @param array<mixed> $headers
  373. */
  374. protected function post(string $route, array $data, array $headers = []): ResponseInterface
  375. {
  376. return $this->request(
  377. Request::METHOD_POST,
  378. $route,
  379. $data,
  380. $headers
  381. );
  382. }
  383. /**
  384. * Send a DELETE request and return the response parsed content.
  385. *
  386. * @param array<mixed> $headers
  387. */
  388. protected function delete(string $route, array $headers = []): ResponseInterface
  389. {
  390. return $this->request(
  391. Request::METHOD_DELETE,
  392. $route,
  393. null,
  394. $headers
  395. );
  396. }
  397. /**
  398. * Login as the given Access user.
  399. *
  400. * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
  401. * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
  402. * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
  403. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
  404. */
  405. public function loginAs()
  406. {
  407. $access = $this->em->getRepository(Access::class)->find(1);
  408. $person = $access->getPerson();
  409. $response = $this->post(
  410. '/login_check',
  411. ['username' => $person->getUsername(), 'password' => $person->getPassword()]
  412. );
  413. $content = $response->getContent();
  414. $decodedContent = json_decode($content);
  415. // Vérifier que le token est présent
  416. if (!isset($decodedContent->token)) {
  417. throw new \Exception("Token not found in response");
  418. }
  419. $this->securityToken = $decodedContent->token;
  420. $this->user = $access;
  421. }
  422. /**
  423. * Login as the given Access user
  424. *
  425. * @param Proxy|Access $access
  426. * @return void
  427. * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
  428. * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
  429. * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
  430. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
  431. */
  432. public function loginAsStudent()
  433. {
  434. // on récupère l'access qui a l'id 641003 dans l'entity manager
  435. $access = $this->em->getRepository(Access::class)->find(2);
  436. $person = $access->getPerson();
  437. $response = $this->post(
  438. '/login_check',
  439. ['username' => $person->getUsername(), 'password' => $person->getPassword()]
  440. );
  441. $content = $response->getContent();
  442. $this->securityToken = json_decode($content)->token;
  443. $this->user = $access;
  444. }
  445. /**
  446. * Login as the given Access user
  447. *
  448. * @param Proxy|Access $access
  449. * @return void
  450. * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
  451. * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
  452. * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
  453. * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
  454. */
  455. public function loginAsintruOfRoot()
  456. {
  457. $access = $this->em->getRepository(Access::class)->find(3);
  458. $person = $access->getPerson();
  459. $response = $this->post(
  460. '/login_check',
  461. ['username' => $person->getUsername(), 'password' => $person->getPassword()]
  462. );
  463. $content = $response->getContent();
  464. $this->securityToken = json_decode($content)->token;
  465. $this->user = $access;
  466. }
  467. /**
  468. * Assert that the response has the expected status code and is well formated
  469. *
  470. * @param string $resourceClass
  471. * @param int $expectedStatus
  472. * @return void
  473. */
  474. protected function validateCollectionSchema(string $resourceClass, int $expectedStatus = 200): void
  475. {
  476. $this->assertResponseStatusCodeSame($expectedStatus);
  477. if ($expectedStatus == 200) {
  478. $this->assertResponseIsSuccessful();
  479. }
  480. // Asserts that the returned content type is JSON-LD (the default)
  481. $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
  482. // Asserts that the returned JSON is validated by the JSON Schema generated for this resource by API Platform
  483. // >>> Issue with the json typed PublicStructure::addresses properties
  484. // $this->assertMatchesResourceCollectionJsonSchema($resourceClass);
  485. }
  486. }