HelloAssoServiceTest.php 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533
  1. <?php
  2. namespace App\Tests\Unit\Service\HelloAsso;
  3. use App\ApiResources\HelloAsso\AuthUrl;
  4. use App\ApiResources\HelloAsso\EventForm;
  5. use App\ApiResources\HelloAsso\HelloAssoProfile;
  6. use App\Entity\Booking\Event;
  7. use App\Entity\HelloAsso\HelloAsso;
  8. use App\Entity\Organization\Organization;
  9. use App\Repository\Booking\EventRepository;
  10. use App\Repository\Organization\OrganizationRepository;
  11. use App\Service\HelloAsso\HelloAssoService;
  12. use App\Service\Security\OAuthPkceGenerator;
  13. use App\Service\Utils\DatesUtils;
  14. use App\Service\Utils\UrlBuilder;
  15. use Doctrine\ORM\EntityManagerInterface;
  16. use PHPUnit\Framework\MockObject\MockObject;
  17. use PHPUnit\Framework\TestCase;
  18. use Psr\Log\LoggerInterface;
  19. use Symfony\Contracts\HttpClient\HttpClientInterface;
  20. use Symfony\Contracts\HttpClient\ResponseInterface;
  21. // Classe testable pour accéder aux méthodes protégées
  22. class TestableHelloAssoService extends HelloAssoService
  23. {
  24. public function getHelloAssoEntityFor(int $organizationId, bool $shallHaveToken = true): HelloAsso
  25. {
  26. return parent::getHelloAssoEntityFor($organizationId, $shallHaveToken);
  27. }
  28. public function makeHelloAssoEventForm(array $formData): EventForm
  29. {
  30. return parent::makeHelloAssoEventForm($formData);
  31. }
  32. public function getCallbackUrl(): string
  33. {
  34. return parent::getCallbackUrl();
  35. }
  36. public function fetchAccessToken(?string $authorizationCode = null, ?string $challengeVerifier = null): array
  37. {
  38. return parent::fetchAccessToken($authorizationCode, $challengeVerifier);
  39. }
  40. public function updateDomain(array|string $accessToken, string $domain): void
  41. {
  42. if (is_array($accessToken)) {
  43. $accessToken = $accessToken['access_token'];
  44. }
  45. parent::updateDomain($accessToken, $domain);
  46. }
  47. public function refreshTokenIfNeeded(HelloAsso $helloAssoEntity): HelloAsso
  48. {
  49. return parent::refreshTokenIfNeeded($helloAssoEntity);
  50. }
  51. public function refreshTokens(HelloAsso $helloAssoEntity): HelloAsso
  52. {
  53. return parent::refreshTokens($helloAssoEntity);
  54. }
  55. }
  56. class HelloAssoServiceTest extends TestCase
  57. {
  58. private HttpClientInterface|MockObject $httpClient;
  59. private OrganizationRepository|MockObject $organizationRepository;
  60. private EventRepository|MockObject $eventRepository;
  61. private EntityManagerInterface|MockObject $entityManager;
  62. private LoggerInterface|MockObject $logger;
  63. private string $baseUrl = 'https://test-base.com';
  64. private string $publicAppBaseUrl = 'https://test-public.com';
  65. private string $helloAssoApiBaseUrl = 'https://api.helloasso.com/v5';
  66. private string $helloAssoAuthBaseUrl = 'https://auth.helloasso.com';
  67. private string $helloAssoClientId = 'test-client-id';
  68. private string $helloAssoClientSecret = 'test-client-secret';
  69. public function setUp(): void
  70. {
  71. $this->httpClient = $this->getMockBuilder(HttpClientInterface::class)
  72. ->disableOriginalConstructor()
  73. ->getMock();
  74. $this->organizationRepository = $this->getMockBuilder(OrganizationRepository::class)
  75. ->disableOriginalConstructor()
  76. ->getMock();
  77. $this->eventRepository = $this->getMockBuilder(EventRepository::class)
  78. ->disableOriginalConstructor()
  79. ->getMock();
  80. $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)
  81. ->disableOriginalConstructor()
  82. ->getMock();
  83. $this->logger = $this->getMockBuilder(LoggerInterface::class)
  84. ->disableOriginalConstructor()
  85. ->getMock();
  86. }
  87. private function getHelloAssoServiceMockFor(string $methodName): TestableHelloAssoService|MockObject
  88. {
  89. return $this->getMockBuilder(TestableHelloAssoService::class)
  90. ->setConstructorArgs([
  91. $this->httpClient,
  92. $this->organizationRepository,
  93. $this->eventRepository,
  94. $this->baseUrl,
  95. $this->publicAppBaseUrl,
  96. $this->helloAssoApiBaseUrl,
  97. $this->helloAssoAuthBaseUrl,
  98. $this->helloAssoClientId,
  99. $this->helloAssoClientSecret,
  100. $this->entityManager,
  101. $this->logger,
  102. ])
  103. ->setMethodsExcept([$methodName])
  104. ->getMock();
  105. }
  106. /**
  107. * @see HelloAssoService::setupOpentalentDomain()
  108. */
  109. public function testSetupOpentalentDomain(): void
  110. {
  111. $service = $this->getHelloAssoServiceMockFor('setupOpentalentDomain');
  112. $tokensData = ['access_token' => 'test-token'];
  113. $service->expects(self::once())
  114. ->method('fetchAccessToken')
  115. ->with(null)
  116. ->willReturn($tokensData);
  117. $service->expects(self::once())
  118. ->method('updateDomain')
  119. ->with($tokensData, 'https://*.opentalent.fr');
  120. $service->setupOpentalentDomain();
  121. }
  122. /**
  123. * @see HelloAssoService::getAuthUrl()
  124. */
  125. public function testGetAuthUrl(): void
  126. {
  127. $service = $this->getHelloAssoServiceMockFor('getAuthUrl');
  128. $organizationId = 1;
  129. $organization = $this->getMockBuilder(Organization::class)
  130. ->disableOriginalConstructor()
  131. ->getMock();
  132. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  133. ->disableOriginalConstructor()
  134. ->getMock();
  135. $this->organizationRepository->expects(self::once())
  136. ->method('find')
  137. ->with($organizationId)
  138. ->willReturn($organization);
  139. $organization->expects(self::once())
  140. ->method('getHelloAsso')
  141. ->willReturn($helloAssoEntity);
  142. $helloAssoEntity->expects(self::once())
  143. ->method('setChallengeVerifier')
  144. ->with(self::isType('string'));
  145. $this->entityManager->expects(self::once())
  146. ->method('persist')
  147. ->with($helloAssoEntity);
  148. $this->entityManager->expects(self::once())
  149. ->method('flush');
  150. $result = $service->getAuthUrl($organizationId);
  151. $this->assertInstanceOf(AuthUrl::class, $result);
  152. }
  153. /**
  154. * @see HelloAssoService::getAuthUrl()
  155. */
  156. public function testGetAuthUrlWhenOrganizationNotFound(): void
  157. {
  158. $service = $this->getHelloAssoServiceMockFor('getAuthUrl');
  159. $organizationId = 1;
  160. $this->organizationRepository->expects(self::once())
  161. ->method('find')
  162. ->with($organizationId)
  163. ->willReturn(null);
  164. $this->expectException(\RuntimeException::class);
  165. $this->expectExceptionMessage('Organization not found');
  166. $service->getAuthUrl($organizationId);
  167. }
  168. /**
  169. * @see HelloAssoService::getAuthUrl()
  170. */
  171. public function testGetAuthUrlWhenHelloAssoEntityNotExists(): void
  172. {
  173. $service = $this->getHelloAssoServiceMockFor('getAuthUrl');
  174. $organizationId = 1;
  175. $organization = $this->getMockBuilder(Organization::class)
  176. ->disableOriginalConstructor()
  177. ->getMock();
  178. $this->organizationRepository->expects(self::once())
  179. ->method('find')
  180. ->with($organizationId)
  181. ->willReturn($organization);
  182. $organization->expects(self::once())
  183. ->method('getHelloAsso')
  184. ->willReturn(null);
  185. $this->entityManager->expects(self::once())
  186. ->method('persist')
  187. ->with(self::isInstanceOf(HelloAsso::class));
  188. $this->entityManager->expects(self::once())
  189. ->method('flush');
  190. $result = $service->getAuthUrl($organizationId);
  191. $this->assertInstanceOf(AuthUrl::class, $result);
  192. }
  193. /**
  194. * @see HelloAssoService::connect()
  195. */
  196. public function testConnect(): void
  197. {
  198. $service = $this->getHelloAssoServiceMockFor('connect');
  199. $organizationId = 1;
  200. $authorizationCode = 'test-auth-code';
  201. $organization = $this->getMockBuilder(Organization::class)
  202. ->disableOriginalConstructor()
  203. ->getMock();
  204. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  205. ->disableOriginalConstructor()
  206. ->getMock();
  207. $challengeVerifier = 'test-verifier';
  208. $tokensData = [
  209. 'access_token' => 'test-access-token',
  210. 'refresh_token' => 'test-refresh-token',
  211. 'expires_in' => 3600,
  212. 'token_type' => 'bearer',
  213. ];
  214. $this->organizationRepository->expects(self::once())
  215. ->method('find')
  216. ->with($organizationId)
  217. ->willReturn($organization);
  218. $organization->expects(self::once())
  219. ->method('getHelloAsso')
  220. ->willReturn($helloAssoEntity);
  221. $helloAssoEntity->expects(self::once())
  222. ->method('getChallengeVerifier')
  223. ->willReturn($challengeVerifier);
  224. $service->expects(self::once())
  225. ->method('fetchAccessToken')
  226. ->with($authorizationCode, $challengeVerifier)
  227. ->willReturn($tokensData);
  228. $helloAssoEntity->expects(self::once())
  229. ->method('setToken')
  230. ->with('test-access-token');
  231. $helloAssoEntity->expects(self::once())
  232. ->method('setTokenCreatedAt')
  233. ->with(self::isInstanceOf(\DateTime::class));
  234. $helloAssoEntity->expects(self::once())
  235. ->method('setRefreshToken')
  236. ->with('test-refresh-token');
  237. $helloAssoEntity->expects(self::once())
  238. ->method('setRefreshTokenCreatedAt')
  239. ->with(self::isInstanceOf(\DateTime::class));
  240. $helloAssoEntity->expects(self::once())
  241. ->method('setOrganizationSlug')
  242. ->with(null);
  243. $helloAssoEntity->expects(self::once())
  244. ->method('setChallengeVerifier')
  245. ->with(null);
  246. $this->entityManager->expects(self::once())
  247. ->method('persist')
  248. ->with($helloAssoEntity);
  249. $this->entityManager->expects(self::once())
  250. ->method('flush');
  251. $result = $service->connect($organizationId, $authorizationCode);
  252. $this->assertSame($helloAssoEntity, $result);
  253. }
  254. /**
  255. * @see HelloAssoService::connect()
  256. */
  257. public function testConnectWhenOrganizationNotFound(): void
  258. {
  259. $service = $this->getHelloAssoServiceMockFor('connect');
  260. $organizationId = 1;
  261. $authorizationCode = 'test-auth-code';
  262. $this->organizationRepository->expects(self::once())
  263. ->method('find')
  264. ->with($organizationId)
  265. ->willReturn(null);
  266. $this->expectException(\RuntimeException::class);
  267. $this->expectExceptionMessage('Organization not found');
  268. $service->connect($organizationId, $authorizationCode);
  269. }
  270. /**
  271. * @see HelloAssoService::connect()
  272. */
  273. public function testConnectWhenHelloAssoEntityNotFound(): void
  274. {
  275. $service = $this->getHelloAssoServiceMockFor('connect');
  276. $organizationId = 1;
  277. $authorizationCode = 'test-auth-code';
  278. $organization = $this->getMockBuilder(Organization::class)
  279. ->disableOriginalConstructor()
  280. ->getMock();
  281. $this->organizationRepository->expects(self::once())
  282. ->method('find')
  283. ->with($organizationId)
  284. ->willReturn($organization);
  285. $organization->expects(self::once())
  286. ->method('getHelloAsso')
  287. ->willReturn(null);
  288. $this->expectException(\RuntimeException::class);
  289. $this->expectExceptionMessage('HelloAsso entity not found');
  290. $service->connect($organizationId, $authorizationCode);
  291. }
  292. /**
  293. * @see HelloAssoService::connect()
  294. */
  295. public function testConnectWhenInvalidTokenType(): void
  296. {
  297. $service = $this->getHelloAssoServiceMockFor('connect');
  298. $organizationId = 1;
  299. $authorizationCode = 'test-auth-code';
  300. $organization = $this->getMockBuilder(Organization::class)
  301. ->disableOriginalConstructor()
  302. ->getMock();
  303. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  304. ->disableOriginalConstructor()
  305. ->getMock();
  306. $challengeVerifier = 'test-verifier';
  307. $tokensData = [
  308. 'access_token' => 'test-access-token',
  309. 'refresh_token' => 'test-refresh-token',
  310. 'expires_in' => 3600,
  311. 'token_type' => 'invalid',
  312. ];
  313. $this->organizationRepository->expects(self::once())
  314. ->method('find')
  315. ->with($organizationId)
  316. ->willReturn($organization);
  317. $organization->expects(self::once())
  318. ->method('getHelloAsso')
  319. ->willReturn($helloAssoEntity);
  320. $helloAssoEntity->expects(self::once())
  321. ->method('getChallengeVerifier')
  322. ->willReturn($challengeVerifier);
  323. $service->expects(self::once())
  324. ->method('fetchAccessToken')
  325. ->with($authorizationCode, $challengeVerifier)
  326. ->willReturn($tokensData);
  327. $this->expectException(\RuntimeException::class);
  328. $this->expectExceptionMessage('Invalid token type received');
  329. $service->connect($organizationId, $authorizationCode);
  330. }
  331. /**
  332. * @see HelloAssoService::makeHelloAssoProfile()
  333. */
  334. public function testMakeHelloAssoProfile(): void
  335. {
  336. $service = $this->getHelloAssoServiceMockFor('makeHelloAssoProfile');
  337. $organizationId = 1;
  338. $organization = $this->getMockBuilder(Organization::class)
  339. ->disableOriginalConstructor()
  340. ->getMock();
  341. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  342. ->disableOriginalConstructor()
  343. ->getMock();
  344. $this->organizationRepository->expects(self::once())
  345. ->method('find')
  346. ->with($organizationId)
  347. ->willReturn($organization);
  348. $organization->expects(self::once())
  349. ->method('getHelloAsso')
  350. ->willReturn($helloAssoEntity);
  351. $helloAssoEntity->expects(self::once())
  352. ->method('getToken')
  353. ->willReturn('test-token');
  354. $helloAssoEntity->expects(self::once())
  355. ->method('getOrganizationSlug')
  356. ->willReturn('test-org-slug');
  357. $result = $service->makeHelloAssoProfile($organizationId);
  358. $this->assertInstanceOf(HelloAssoProfile::class, $result);
  359. }
  360. /**
  361. * @see HelloAssoService::makeHelloAssoProfile()
  362. */
  363. public function testMakeHelloAssoProfileWhenOrganizationNotFound(): void
  364. {
  365. $service = $this->getHelloAssoServiceMockFor('makeHelloAssoProfile');
  366. $organizationId = 1;
  367. $this->organizationRepository->expects(self::once())
  368. ->method('find')
  369. ->with($organizationId)
  370. ->willReturn(null);
  371. $this->expectException(\RuntimeException::class);
  372. $this->expectExceptionMessage('Organization not found');
  373. $service->makeHelloAssoProfile($organizationId);
  374. }
  375. /**
  376. * @see HelloAssoService::makeHelloAssoProfile()
  377. */
  378. public function testMakeHelloAssoProfileWhenHelloAssoEntityNotFound(): void
  379. {
  380. $service = $this->getHelloAssoServiceMockFor('makeHelloAssoProfile');
  381. $organizationId = 1;
  382. $organization = $this->getMockBuilder(Organization::class)
  383. ->disableOriginalConstructor()
  384. ->getMock();
  385. $this->organizationRepository->expects(self::once())
  386. ->method('find')
  387. ->with($organizationId)
  388. ->willReturn($organization);
  389. $organization->expects(self::once())
  390. ->method('getHelloAsso')
  391. ->willReturn(null);
  392. $result = $service->makeHelloAssoProfile($organizationId);
  393. $this->assertInstanceOf(HelloAssoProfile::class, $result);
  394. $this->assertFalse($result->isExisting());
  395. }
  396. /**
  397. * @see HelloAssoService::unlinkHelloAssoAccount()
  398. */
  399. public function testUnlinkHelloAssoAccount(): void
  400. {
  401. $service = $this->getHelloAssoServiceMockFor('unlinkHelloAssoAccount');
  402. $organizationId = 1;
  403. $organization = $this->getMockBuilder(Organization::class)
  404. ->disableOriginalConstructor()
  405. ->getMock();
  406. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  407. ->disableOriginalConstructor()
  408. ->getMock();
  409. $this->organizationRepository->expects(self::once())
  410. ->method('find')
  411. ->with($organizationId)
  412. ->willReturn($organization);
  413. $organization->expects(self::once())
  414. ->method('getHelloAsso')
  415. ->willReturn($helloAssoEntity);
  416. $helloAssoEntity->expects(self::once())
  417. ->method('setToken')
  418. ->with(null);
  419. $helloAssoEntity->expects(self::once())
  420. ->method('setTokenCreatedAt')
  421. ->with(null);
  422. $helloAssoEntity->expects(self::once())
  423. ->method('setRefreshToken')
  424. ->with(null);
  425. $helloAssoEntity->expects(self::once())
  426. ->method('setRefreshTokenCreatedAt')
  427. ->with(null);
  428. $helloAssoEntity->expects(self::once())
  429. ->method('setOrganizationSlug')
  430. ->with(null);
  431. $this->entityManager->expects(self::once())
  432. ->method('persist')
  433. ->with($helloAssoEntity);
  434. $this->entityManager->expects(self::once())
  435. ->method('flush');
  436. $service->unlinkHelloAssoAccount($organizationId);
  437. }
  438. /**
  439. * @see HelloAssoService::unlinkHelloAssoAccount()
  440. */
  441. public function testUnlinkHelloAssoAccountWhenOrganizationNotFound(): void
  442. {
  443. $service = $this->getHelloAssoServiceMockFor('unlinkHelloAssoAccount');
  444. $organizationId = 1;
  445. $this->organizationRepository->expects(self::once())
  446. ->method('find')
  447. ->with($organizationId)
  448. ->willReturn(null);
  449. $this->expectException(\RuntimeException::class);
  450. $this->expectExceptionMessage('Organization not found');
  451. $service->unlinkHelloAssoAccount($organizationId);
  452. }
  453. /**
  454. * @see HelloAssoService::unlinkHelloAssoAccount()
  455. */
  456. public function testUnlinkHelloAssoAccountWhenHelloAssoEntityNotFound(): void
  457. {
  458. $service = $this->getHelloAssoServiceMockFor('unlinkHelloAssoAccount');
  459. $organizationId = 1;
  460. $organization = $this->getMockBuilder(Organization::class)
  461. ->disableOriginalConstructor()
  462. ->getMock();
  463. $this->organizationRepository->expects(self::once())
  464. ->method('find')
  465. ->with($organizationId)
  466. ->willReturn($organization);
  467. $organization->expects(self::once())
  468. ->method('getHelloAsso')
  469. ->willReturn(null);
  470. $this->entityManager->expects(self::never())
  471. ->method('persist');
  472. $this->entityManager->expects(self::never())
  473. ->method('flush');
  474. $service->unlinkHelloAssoAccount($organizationId);
  475. }
  476. /**
  477. * @see HelloAssoService::getResource()
  478. */
  479. public function testGetResource(): void
  480. {
  481. $service = $this->getHelloAssoServiceMockFor('getResource');
  482. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  483. ->disableOriginalConstructor()
  484. ->getMock();
  485. $response = $this->getMockBuilder(ResponseInterface::class)
  486. ->disableOriginalConstructor()
  487. ->getMock();
  488. $routeParts = ['organizations', 'test-slug', 'forms'];
  489. $expectedData = ['test' => 'data'];
  490. $helloAssoEntity->expects(self::once())
  491. ->method('getOrganizationSlug')
  492. ->willReturn('test-org-slug');
  493. $helloAssoEntity->expects(self::atLeastOnce())
  494. ->method('getToken')
  495. ->willReturn('test-token');
  496. $service->expects(self::once())
  497. ->method('refreshTokenIfNeeded')
  498. ->with($helloAssoEntity)
  499. ->willReturn($helloAssoEntity);
  500. $service->expects(self::once())
  501. ->method('get')
  502. ->with('https://api.helloasso.com/v5/v5/organizations/test-slug/forms', [], [
  503. 'headers' => [
  504. 'accept' => 'application/json',
  505. 'authorization' => 'Bearer test-token',
  506. ],
  507. ])
  508. ->willReturn($response);
  509. $response->expects(self::once())
  510. ->method('getStatusCode')
  511. ->willReturn(200);
  512. $response->expects(self::once())
  513. ->method('getContent')
  514. ->willReturn(json_encode($expectedData));
  515. $result = $service->getResource($helloAssoEntity, $routeParts);
  516. $this->assertSame($expectedData, $result);
  517. }
  518. /**
  519. * @see HelloAssoService::getResource()
  520. */
  521. public function testGetResourceWhenIncompleteEntity(): void
  522. {
  523. $service = $this->getHelloAssoServiceMockFor('getResource');
  524. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  525. ->disableOriginalConstructor()
  526. ->getMock();
  527. $routeParts = ['organizations', 'test-slug', 'forms'];
  528. $helloAssoEntity->expects(self::once())
  529. ->method('getOrganizationSlug')
  530. ->willReturn(null);
  531. // getToken() won't be called because getOrganizationSlug() returns null first
  532. $this->expectException(\RuntimeException::class);
  533. $this->expectExceptionMessage('HelloAsso entity incomplete');
  534. $service->getResource($helloAssoEntity, $routeParts);
  535. }
  536. /**
  537. * @see HelloAssoService::getResource()
  538. */
  539. public function testGetResourceWhenHttpError(): void
  540. {
  541. $service = $this->getHelloAssoServiceMockFor('getResource');
  542. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  543. ->disableOriginalConstructor()
  544. ->getMock();
  545. $response = $this->getMockBuilder(ResponseInterface::class)
  546. ->disableOriginalConstructor()
  547. ->getMock();
  548. $routeParts = ['organizations', 'test-slug', 'forms'];
  549. $helloAssoEntity->expects(self::once())
  550. ->method('getOrganizationSlug')
  551. ->willReturn('test-org-slug');
  552. $helloAssoEntity->expects(self::atLeastOnce())
  553. ->method('getToken')
  554. ->willReturn('test-token');
  555. $service->expects(self::once())
  556. ->method('refreshTokenIfNeeded')
  557. ->with($helloAssoEntity)
  558. ->willReturn($helloAssoEntity);
  559. $service->expects(self::once())
  560. ->method('get')
  561. ->willReturn($response);
  562. $response->expects(self::atLeastOnce())
  563. ->method('getStatusCode')
  564. ->willReturn(404);
  565. $response->expects(self::once())
  566. ->method('getContent')
  567. ->with(false)
  568. ->willReturn('Not Found');
  569. $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
  570. $this->expectExceptionMessage('Failed to fetch resource: [404] Not Found');
  571. $service->getResource($helloAssoEntity, $routeParts);
  572. }
  573. /**
  574. * @see HelloAssoService::getHelloAssoEventForms()
  575. */
  576. public function testGetHelloAssoEventForms(): void
  577. {
  578. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEventForms');
  579. $organizationId = 1;
  580. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  581. ->disableOriginalConstructor()
  582. ->getMock();
  583. $formsData = [
  584. 'data' => [
  585. ['formSlug' => 'form1', 'title' => 'Event 1'],
  586. ['formSlug' => 'form2', 'title' => 'Event 2'],
  587. ]
  588. ];
  589. $service->expects(self::once())
  590. ->method('getHelloAssoEntityFor')
  591. ->with($organizationId, true)
  592. ->willReturn($helloAssoEntity);
  593. $helloAssoEntity
  594. ->method('getOrganizationSlug')
  595. ->willReturn('test-org-slug');
  596. $helloAssoEntity
  597. ->method('getToken')
  598. ->willReturn('abcd123');
  599. $service->expects(self::once())
  600. ->method('getResource')
  601. ->with($helloAssoEntity, ['organizations', 'test-org-slug', 'forms'])
  602. ->willReturn($formsData);
  603. $result = $service->getHelloAssoEventForms($organizationId);
  604. $this->assertIsArray($result);
  605. $this->assertCount(2, $result);
  606. $this->assertInstanceOf(EventForm::class, $result[0]);
  607. $this->assertInstanceOf(EventForm::class, $result[1]);
  608. }
  609. /**
  610. * @see HelloAssoService::getHelloAssoEventForms()
  611. */
  612. public function testGetHelloAssoEventFormsWhenIncomplete(): void
  613. {
  614. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEventForms');
  615. $organizationId = 1;
  616. $service->expects(self::once())
  617. ->method('getHelloAssoEntityFor')
  618. ->with($organizationId, true)
  619. ->willThrowException(new \RuntimeException('HelloAsso entity incomplete'));
  620. $this->expectException(\RuntimeException::class);
  621. $this->expectExceptionMessage('HelloAsso entity incomplete');
  622. $service->getHelloAssoEventForms($organizationId);
  623. }
  624. /**
  625. * @see HelloAssoService::getHelloAssoEventForm()
  626. */
  627. public function testGetHelloAssoEventForm(): void
  628. {
  629. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEventForm');
  630. $organizationId = 1;
  631. $formSlug = 'test-form-slug';
  632. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  633. ->disableOriginalConstructor()
  634. ->getMock();
  635. $formData = [
  636. 'formSlug' => 'test-form-slug',
  637. 'title' => 'Test Event',
  638. 'widgetFullUrl' => 'https://widget.url'
  639. ];
  640. $service->expects(self::once())
  641. ->method('getHelloAssoEntityFor')
  642. ->with($organizationId, true)
  643. ->willReturn($helloAssoEntity);
  644. $helloAssoEntity->expects(self::atLeastOnce())
  645. ->method('getOrganizationSlug')
  646. ->willReturn('test-org-slug');
  647. $service->expects(self::once())
  648. ->method('getResource')
  649. ->with($helloAssoEntity, ['organizations', 'test-org-slug', 'forms', 'Event', 'test-form-slug', 'public'])
  650. ->willReturn($formData);
  651. $service->expects(self::once())
  652. ->method('makeHelloAssoEventForm')
  653. ->with($formData)
  654. ->willReturn(new EventForm());
  655. $result = $service->getHelloAssoEventForm($organizationId, $formSlug);
  656. $this->assertInstanceOf(EventForm::class, $result);
  657. }
  658. /**
  659. * @see HelloAssoService::getHelloAssoEventFormByEventId()
  660. */
  661. public function testGetHelloAssoEventFormByEventId(): void
  662. {
  663. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEventFormByEventId');
  664. $eventId = 1;
  665. $organization = $this->getMockBuilder(Organization::class)
  666. ->disableOriginalConstructor()
  667. ->getMock();
  668. $event = $this->getMockBuilder(Event::class)
  669. ->disableOriginalConstructor()
  670. ->getMock();
  671. $eventForm = new EventForm();
  672. $this->eventRepository->expects(self::once())
  673. ->method('find')
  674. ->with($eventId)
  675. ->willReturn($event);
  676. $event->expects(self::once())
  677. ->method('getOrganization')
  678. ->willReturn($organization);
  679. $organization->expects(self::once())
  680. ->method('getId')
  681. ->willReturn(1);
  682. $event->expects(self::once())
  683. ->method('getHelloAssoSlug')
  684. ->willReturn('test-hello-asso-slug');
  685. $service->expects(self::once())
  686. ->method('getHelloAssoEventForm')
  687. ->with(1, 'test-hello-asso-slug')
  688. ->willReturn($eventForm);
  689. $result = $service->getHelloAssoEventFormByEventId($eventId);
  690. $this->assertSame($eventForm, $result);
  691. }
  692. /**
  693. * @see HelloAssoService::getHelloAssoEventFormByEventId()
  694. */
  695. public function testGetHelloAssoEventFormByEventIdWhenEventNotFound(): void
  696. {
  697. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEventFormByEventId');
  698. $eventId = 1;
  699. $this->eventRepository->expects(self::once())
  700. ->method('find')
  701. ->with($eventId)
  702. ->willReturn(null);
  703. $this->expectException(\RuntimeException::class);
  704. $this->expectExceptionMessage('Event not found');
  705. $service->getHelloAssoEventFormByEventId($eventId);
  706. }
  707. /**
  708. * @see HelloAssoService::getHelloAssoEventFormByEventId()
  709. */
  710. public function testGetHelloAssoEventFormByEventIdWhenNoHelloAssoSlug(): void
  711. {
  712. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEventFormByEventId');
  713. $eventId = 1;
  714. $organization = $this->getMockBuilder(Organization::class)
  715. ->disableOriginalConstructor()
  716. ->getMock();
  717. $event = $this->getMockBuilder(Event::class)
  718. ->disableOriginalConstructor()
  719. ->getMock();
  720. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  721. ->disableOriginalConstructor()
  722. ->getMock();
  723. $this->eventRepository->expects(self::once())
  724. ->method('find')
  725. ->with($eventId)
  726. ->willReturn($event);
  727. $event->expects(self::once())
  728. ->method('getHelloAssoSlug')
  729. ->willReturn(null);
  730. $this->expectException(\RuntimeException::class);
  731. $this->expectExceptionMessage('HelloAsso form slug not found');
  732. $service->getHelloAssoEventFormByEventId($eventId);
  733. }
  734. /**
  735. * @see HelloAssoService::getHelloAssoEntityFor()
  736. */
  737. public function testGetHelloAssoEntityFor(): void
  738. {
  739. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEntityFor');
  740. $organizationId = 1;
  741. $organization = $this->getMockBuilder(Organization::class)
  742. ->disableOriginalConstructor()
  743. ->getMock();
  744. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  745. ->disableOriginalConstructor()
  746. ->getMock();
  747. $this->organizationRepository->expects(self::once())
  748. ->method('find')
  749. ->with($organizationId)
  750. ->willReturn($organization);
  751. $organization->expects(self::once())
  752. ->method('getHelloAsso')
  753. ->willReturn($helloAssoEntity);
  754. $helloAssoEntity->expects(self::once())
  755. ->method('getOrganizationSlug')
  756. ->willReturn('test-org-slug');
  757. $helloAssoEntity->expects(self::once())
  758. ->method('getToken')
  759. ->willReturn('test-token');
  760. $result = $service->getHelloAssoEntityFor($organizationId);
  761. $this->assertSame($helloAssoEntity, $result);
  762. }
  763. /**
  764. * @see HelloAssoService::getHelloAssoEntityFor()
  765. */
  766. public function testGetHelloAssoEntityForWhenIncomplete(): void
  767. {
  768. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEntityFor');
  769. $organizationId = 1;
  770. $organization = $this->getMockBuilder(Organization::class)
  771. ->disableOriginalConstructor()
  772. ->getMock();
  773. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  774. ->disableOriginalConstructor()
  775. ->getMock();
  776. $this->organizationRepository->expects(self::once())
  777. ->method('find')
  778. ->with($organizationId)
  779. ->willReturn($organization);
  780. $organization->expects(self::once())
  781. ->method('getHelloAsso')
  782. ->willReturn($helloAssoEntity);
  783. $helloAssoEntity->expects(self::once())
  784. ->method('getOrganizationSlug')
  785. ->willReturn('test-org-slug');
  786. $helloAssoEntity->expects(self::once())
  787. ->method('getToken')
  788. ->willReturn(null);
  789. $this->expectException(\RuntimeException::class);
  790. $this->expectExceptionMessage('HelloAsso entity incomplete');
  791. $service->getHelloAssoEntityFor($organizationId);
  792. }
  793. /**
  794. * @see HelloAssoService::getHelloAssoEntityFor()
  795. */
  796. public function testGetHelloAssoEntityForWhenOrganizationNotFound(): void
  797. {
  798. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEntityFor');
  799. $organizationId = 1;
  800. $this->organizationRepository->expects(self::once())
  801. ->method('find')
  802. ->with($organizationId)
  803. ->willReturn(null);
  804. $this->expectException(\RuntimeException::class);
  805. $this->expectExceptionMessage('Organization not found');
  806. $service->getHelloAssoEntityFor($organizationId);
  807. }
  808. /**
  809. * @see HelloAssoService::getHelloAssoEntityFor()
  810. */
  811. public function testGetHelloAssoEntityForWhenHelloAssoNotFound(): void
  812. {
  813. $service = $this->getHelloAssoServiceMockFor('getHelloAssoEntityFor');
  814. $organizationId = 1;
  815. $organization = $this->getMockBuilder(Organization::class)
  816. ->disableOriginalConstructor()
  817. ->getMock();
  818. $this->organizationRepository->expects(self::once())
  819. ->method('find')
  820. ->with($organizationId)
  821. ->willReturn($organization);
  822. $organization->expects(self::once())
  823. ->method('getHelloAsso')
  824. ->willReturn(null);
  825. $this->expectException(\RuntimeException::class);
  826. $this->expectExceptionMessage('HelloAsso entity not found');
  827. $service->getHelloAssoEntityFor($organizationId);
  828. }
  829. /**
  830. * @see HelloAssoService::makeHelloAssoEventForm()
  831. */
  832. public function testMakeHelloAssoEventForm(): void
  833. {
  834. $service = $this->getHelloAssoServiceMockFor('makeHelloAssoEventForm');
  835. $formData = [
  836. 'formSlug' => 'test-form-slug',
  837. 'title' => 'Test Event Title',
  838. 'widgetFullUrl' => 'https://widget.full.url'
  839. ];
  840. $result = $service->makeHelloAssoEventForm($formData);
  841. $this->assertInstanceOf(EventForm::class, $result);
  842. }
  843. /**
  844. * @see HelloAssoService::getCallbackUrl()
  845. */
  846. public function testGetCallbackUrl(): void
  847. {
  848. $service = $this->getHelloAssoServiceMockFor('getCallbackUrl');
  849. $result = $service->getCallbackUrl();
  850. $this->assertEquals('https://test-public.com/helloasso/callback', $result);
  851. }
  852. /**
  853. * @see HelloAssoService::fetchAccessToken()
  854. */
  855. public function testFetchAccessToken(): void
  856. {
  857. $service = $this->getHelloAssoServiceMockFor('fetchAccessToken');
  858. $response = $this->getMockBuilder(ResponseInterface::class)
  859. ->disableOriginalConstructor()
  860. ->getMock();
  861. $expectedTokenData = [
  862. 'access_token' => 'test-access-token',
  863. 'refresh_token' => 'test-refresh-token',
  864. 'token_type' => 'Bearer',
  865. 'expires_in' => 3600,
  866. 'organization_slug' => null
  867. ];
  868. $this->httpClient->expects(self::once())
  869. ->method('request')
  870. ->with('POST', 'https://api.helloasso.com/v5/oauth2/token', [
  871. 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
  872. 'body' => [
  873. 'grant_type' => 'client_credentials',
  874. 'client_id' => 'test-client-id',
  875. 'client_secret' => 'test-client-secret',
  876. ]
  877. ])
  878. ->willReturn($response);
  879. $response->expects(self::once())
  880. ->method('getStatusCode')
  881. ->willReturn(200);
  882. $response->expects(self::once())
  883. ->method('getContent')
  884. ->willReturn(json_encode($expectedTokenData));
  885. $result = $service->fetchAccessToken();
  886. $this->assertSame($expectedTokenData, $result);
  887. }
  888. /**
  889. * @see HelloAssoService::fetchAccessToken()
  890. */
  891. public function testFetchAccessTokenWithAuthCode(): void
  892. {
  893. $service = $this->getHelloAssoServiceMockFor('fetchAccessToken');
  894. $response = $this->getMockBuilder(ResponseInterface::class)
  895. ->disableOriginalConstructor()
  896. ->getMock();
  897. $expectedTokenData = [
  898. 'access_token' => 'test-access-token',
  899. 'refresh_token' => 'test-refresh-token',
  900. 'token_type' => 'Bearer',
  901. 'expires_in' => 3600,
  902. 'organization_slug' => null
  903. ];
  904. $service->expects(self::once())
  905. ->method('getCallbackUrl')
  906. ->willReturn('https://test-public.com/helloasso/callback');
  907. $this->httpClient->expects(self::once())
  908. ->method('request')
  909. ->with('POST', 'https://api.helloasso.com/v5/oauth2/token', [
  910. 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
  911. 'body' => [
  912. 'grant_type' => 'authorization_code',
  913. 'client_id' => 'test-client-id',
  914. 'client_secret' => 'test-client-secret',
  915. 'code' => 'test-auth-code',
  916. 'redirect_uri' => 'https://test-public.com/helloasso/callback',
  917. 'code_verifier' => 'test-verifier'
  918. ]
  919. ])
  920. ->willReturn($response);
  921. $response->expects(self::once())
  922. ->method('getStatusCode')
  923. ->willReturn(200);
  924. $response->expects(self::once())
  925. ->method('getContent')
  926. ->willReturn(json_encode($expectedTokenData));
  927. $result = $service->fetchAccessToken('test-auth-code', 'test-verifier');
  928. $this->assertSame($expectedTokenData, $result);
  929. }
  930. /**
  931. * @see HelloAssoService::fetchAccessToken()
  932. */
  933. public function testFetchAccessTokenWhenHttpError(): void
  934. {
  935. $service = $this->getHelloAssoServiceMockFor('fetchAccessToken');
  936. $response = $this->getMockBuilder(ResponseInterface::class)
  937. ->disableOriginalConstructor()
  938. ->getMock();
  939. $this->httpClient->expects(self::once())
  940. ->method('request')
  941. ->willReturn($response);
  942. $response->expects(self::once())
  943. ->method('getStatusCode')
  944. ->willReturn(400);
  945. $response->expects(self::once())
  946. ->method('getContent')
  947. ->with(false)
  948. ->willReturn('Bad Request');
  949. $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
  950. $this->expectExceptionMessage('Failed to fetch access token: Bad Request');
  951. $service->fetchAccessToken();
  952. }
  953. /**
  954. * @see HelloAssoService::fetchAccessToken()
  955. */
  956. public function testFetchAccessTokenWhenJsonError(): void
  957. {
  958. $service = $this->getHelloAssoServiceMockFor('fetchAccessToken');
  959. $response = $this->getMockBuilder(ResponseInterface::class)
  960. ->disableOriginalConstructor()
  961. ->getMock();
  962. $this->httpClient->expects(self::once())
  963. ->method('request')
  964. ->willReturn($response);
  965. $response->expects(self::once())
  966. ->method('getStatusCode')
  967. ->willReturn(200);
  968. $response->expects(self::once())
  969. ->method('getContent')
  970. ->willReturn('invalid-json');
  971. $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
  972. $this->expectExceptionMessage('Failed to parse authentication response:');
  973. $service->fetchAccessToken();
  974. }
  975. /**
  976. * @see HelloAssoService::updateDomain()
  977. */
  978. public function testUpdateDomain(): void
  979. {
  980. $service = $this->getHelloAssoServiceMockFor('updateDomain');
  981. $response = $this->getMockBuilder(ResponseInterface::class)
  982. ->disableOriginalConstructor()
  983. ->getMock();
  984. $service->expects(self::once())
  985. ->method('put')
  986. ->with('https://api.helloasso.com/v5/v5/partners/me/api-clients', ['domain' => 'https://test-domain.com'], [], [
  987. 'headers' => [
  988. 'Authorization' => 'Bearer test-access-token',
  989. 'Content-Type' => 'application/json',
  990. ],
  991. ])
  992. ->willReturn($response);
  993. $response->expects(self::once())
  994. ->method('getStatusCode')
  995. ->willReturn(200);
  996. $service->updateDomain('test-access-token', 'https://test-domain.com');
  997. }
  998. /**
  999. * @see HelloAssoService::updateDomain()
  1000. */
  1001. public function testUpdateDomainWhenHttpError(): void
  1002. {
  1003. $service = $this->getHelloAssoServiceMockFor('updateDomain');
  1004. $response = $this->getMockBuilder(ResponseInterface::class)
  1005. ->disableOriginalConstructor()
  1006. ->getMock();
  1007. $service->expects(self::once())
  1008. ->method('put')
  1009. ->willReturn($response);
  1010. $response->expects(self::once())
  1011. ->method('getStatusCode')
  1012. ->willReturn(403);
  1013. $response->expects(self::once())
  1014. ->method('getContent')
  1015. ->willReturn('Forbidden');
  1016. $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
  1017. $this->expectExceptionMessage('Failed to update domain: Forbidden');
  1018. $service->updateDomain('test-access-token', 'https://test-domain.com');
  1019. }
  1020. /**
  1021. * @see HelloAssoService::refreshTokenIfNeeded()
  1022. */
  1023. public function testRefreshTokenIfNeeded(): void
  1024. {
  1025. $service = $this->getHelloAssoServiceMockFor('refreshTokenIfNeeded');
  1026. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  1027. ->disableOriginalConstructor()
  1028. ->getMock();
  1029. $futureDate = new \DateTime('+1 hour');
  1030. $helloAssoEntity->expects(self::once())
  1031. ->method('getRefreshToken')
  1032. ->willReturn('test-refresh-token');
  1033. $helloAssoEntity->expects(self::atLeastOnce())
  1034. ->method('getRefreshTokenCreatedAt')
  1035. ->willReturn($futureDate);
  1036. $result = $service->refreshTokenIfNeeded($helloAssoEntity);
  1037. $this->assertSame($helloAssoEntity, $result);
  1038. }
  1039. /**
  1040. * @see HelloAssoService::refreshTokenIfNeeded()
  1041. */
  1042. public function testRefreshTokenIfNeededWhenTokenExpired(): void
  1043. {
  1044. // Set fake current time to control the test scenario
  1045. DatesUtils::setFakeDatetime('2024-01-01 12:00:00');
  1046. $service = $this->getHelloAssoServiceMockFor('refreshTokenIfNeeded');
  1047. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  1048. ->disableOriginalConstructor()
  1049. ->getMock();
  1050. $refreshedEntity = $this->getMockBuilder(HelloAsso::class)
  1051. ->disableOriginalConstructor()
  1052. ->getMock();
  1053. // Create a date that when 25 minutes are added, will be less than current fake time
  1054. $pastDate = DatesUtils::new('2024-01-01 11:30:00');
  1055. $helloAssoEntity->expects(self::once())
  1056. ->method('getRefreshToken')
  1057. ->willReturn('test-refresh-token');
  1058. $helloAssoEntity->expects(self::atLeastOnce())
  1059. ->method('getRefreshTokenCreatedAt')
  1060. ->willReturn($pastDate);
  1061. $service->expects(self::once())
  1062. ->method('refreshTokens')
  1063. ->with($helloAssoEntity)
  1064. ->willReturn($helloAssoEntity);
  1065. $result = $service->refreshTokenIfNeeded($helloAssoEntity);
  1066. $this->assertSame($helloAssoEntity, $result);
  1067. // Clean up fake datetime
  1068. DatesUtils::clearFakeDatetime();
  1069. }
  1070. /**
  1071. * @see HelloAssoService::refreshTokens()
  1072. */
  1073. public function testRefreshTokens(): void
  1074. {
  1075. $service = $this->getHelloAssoServiceMockFor('refreshTokens');
  1076. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  1077. ->disableOriginalConstructor()
  1078. ->getMock();
  1079. $response = $this->getMockBuilder(ResponseInterface::class)
  1080. ->disableOriginalConstructor()
  1081. ->getMock();
  1082. $tokensData = [
  1083. 'access_token' => 'new-access-token',
  1084. 'refresh_token' => 'new-refresh-token',
  1085. 'expires_in' => 3600
  1086. ];
  1087. $helloAssoEntity->expects(self::atLeastOnce())
  1088. ->method('getRefreshToken')
  1089. ->willReturn('current-refresh-token');
  1090. $helloAssoEntity->expects(self::once())
  1091. ->method('getRefreshTokenCreatedAt')
  1092. ->willReturn(new \DateTime());
  1093. $this->httpClient->expects(self::once())
  1094. ->method('request')
  1095. ->with('POST', 'https://api.helloasso.com/v5/oauth2/token', [
  1096. 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
  1097. 'body' => [
  1098. 'grant_type' => 'refresh_token',
  1099. 'refresh_token' => 'current-refresh-token'
  1100. ]
  1101. ])
  1102. ->willReturn($response);
  1103. $response->expects(self::once())
  1104. ->method('getStatusCode')
  1105. ->willReturn(200);
  1106. $response->expects(self::once())
  1107. ->method('getContent')
  1108. ->willReturn(json_encode($tokensData));
  1109. $helloAssoEntity->expects(self::once())
  1110. ->method('setToken')
  1111. ->with('new-access-token');
  1112. $helloAssoEntity->expects(self::once())
  1113. ->method('setRefreshToken')
  1114. ->with('new-refresh-token');
  1115. $helloAssoEntity->expects(self::once())
  1116. ->method('setTokenCreatedAt')
  1117. ->with(self::isInstanceOf(\DateTime::class));
  1118. $helloAssoEntity->expects(self::once())
  1119. ->method('setRefreshTokenCreatedAt')
  1120. ->with(self::isInstanceOf(\DateTime::class));
  1121. $this->entityManager->expects(self::once())
  1122. ->method('persist')
  1123. ->with($helloAssoEntity);
  1124. $this->entityManager->expects(self::once())
  1125. ->method('flush');
  1126. $result = $service->refreshTokens($helloAssoEntity);
  1127. $this->assertSame($helloAssoEntity, $result);
  1128. }
  1129. /**
  1130. * @see HelloAssoService::refreshTokens()
  1131. */
  1132. public function testRefreshTokensWhenIncompleteEntity(): void
  1133. {
  1134. $service = $this->getHelloAssoServiceMockFor('refreshTokens');
  1135. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  1136. ->disableOriginalConstructor()
  1137. ->getMock();
  1138. $helloAssoEntity->expects(self::once())
  1139. ->method('getRefreshToken')
  1140. ->willReturn(null);
  1141. // getRefreshTokenCreatedAt() won't be called because getRefreshToken() returns null first
  1142. $this->expectException(\RuntimeException::class);
  1143. $this->expectExceptionMessage('HelloAsso entity incomplete');
  1144. $service->refreshTokens($helloAssoEntity);
  1145. }
  1146. /**
  1147. * @see HelloAssoService::refreshTokens()
  1148. */
  1149. public function testRefreshTokensWhenHttpError(): void
  1150. {
  1151. $service = $this->getHelloAssoServiceMockFor('refreshTokens');
  1152. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  1153. ->disableOriginalConstructor()
  1154. ->getMock();
  1155. $response = $this->getMockBuilder(ResponseInterface::class)
  1156. ->disableOriginalConstructor()
  1157. ->getMock();
  1158. $helloAssoEntity->expects(self::atLeastOnce())
  1159. ->method('getRefreshToken')
  1160. ->willReturn('current-refresh-token');
  1161. $helloAssoEntity->expects(self::once())
  1162. ->method('getRefreshTokenCreatedAt')
  1163. ->willReturn(new \DateTime());
  1164. $this->httpClient->expects(self::once())
  1165. ->method('request')
  1166. ->willReturn($response);
  1167. $response->expects(self::once())
  1168. ->method('getStatusCode')
  1169. ->willReturn(401);
  1170. $response->expects(self::once())
  1171. ->method('getContent')
  1172. ->with(false)
  1173. ->willReturn('Unauthorized');
  1174. $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
  1175. $this->expectExceptionMessage('Failed to refresh access token: Unauthorized');
  1176. $service->refreshTokens($helloAssoEntity);
  1177. }
  1178. /**
  1179. * @see HelloAssoService::refreshTokenIfNeeded()
  1180. */
  1181. public function testRefreshTokenIfNeededWhenIncompleteEntity(): void
  1182. {
  1183. $service = $this->getHelloAssoServiceMockFor('refreshTokenIfNeeded');
  1184. $helloAssoEntity = $this->getMockBuilder(HelloAsso::class)
  1185. ->disableOriginalConstructor()
  1186. ->getMock();
  1187. $helloAssoEntity->expects(self::once())
  1188. ->method('getRefreshToken')
  1189. ->willReturn('test-refresh-token');
  1190. $helloAssoEntity->expects(self::once())
  1191. ->method('getRefreshTokenCreatedAt')
  1192. ->willReturn(null);
  1193. $this->expectException(\RuntimeException::class);
  1194. $this->expectExceptionMessage('HelloAsso entity incomplete');
  1195. $service->refreshTokenIfNeeded($helloAssoEntity);
  1196. }
  1197. }