CleanTempFilesTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <?php
  2. namespace App\Tests\Unit\Service\Cron\Job;
  3. use App\Entity\Core\File;
  4. use App\Enum\Core\FileStatusEnum;
  5. use App\Repository\Core\FileRepository;
  6. use App\Service\Cron\Job\CleanTempFiles;
  7. use App\Service\Cron\UI\CronUIInterface;
  8. use App\Service\Utils\DatesUtils;
  9. use Doctrine\DBAL\Connection;
  10. use Doctrine\ORM\EntityManagerInterface;
  11. use Doctrine\ORM\Query;
  12. use Doctrine\ORM\Query\Expr;
  13. use Doctrine\ORM\Query\Expr\Comparison;
  14. use Doctrine\ORM\QueryBuilder;
  15. use PHPUnit\Framework\MockObject\MockObject;
  16. use PHPUnit\Framework\TestCase;
  17. use Psr\Log\LoggerInterface;
  18. class TestableCleanTempFile extends CleanTempFiles
  19. {
  20. public function listFilesToDelete(\DateTime $maxDate): array
  21. {
  22. return parent::listFilesToDelete($maxDate);
  23. }
  24. public function deleteFiles(array $files): void
  25. {
  26. parent::deleteFiles($files);
  27. }
  28. public function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void
  29. {
  30. parent::getQueryConditions($queryBuilder, $maxDate);
  31. }
  32. }
  33. class CleanTempFilesTest extends TestCase
  34. {
  35. private CronUIInterface|MockObject $ui;
  36. private MockObject|LoggerInterface $logger;
  37. private FileRepository|MockObject $fileRepository;
  38. private Connection|MockObject $connection;
  39. private EntityManagerInterface|MockObject $em;
  40. public function setUp(): void
  41. {
  42. $this->ui = $this->getMockBuilder(CronUIInterface::class)->disableOriginalConstructor()->getMock();
  43. $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
  44. $this->fileRepository = $this->getMockBuilder(FileRepository::class)->disableOriginalConstructor()->getMock();
  45. $this->connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
  46. $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
  47. }
  48. private function getMockFor(string $method): MockObject|TestableCleanTempFile
  49. {
  50. $cleanTempFiles = $this->getMockBuilder(TestableCleanTempFile::class)
  51. ->setConstructorArgs([$this->connection, $this->fileRepository, $this->em])
  52. ->setMethodsExcept([$method, 'setUI', 'setLoggerInterface'])
  53. ->getMock();
  54. $cleanTempFiles->setUI($this->ui);
  55. $cleanTempFiles->setLoggerInterface($this->logger);
  56. return $cleanTempFiles;
  57. }
  58. public function testPreview(): void
  59. {
  60. DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
  61. $maxDate = DatesUtils::new();
  62. $maxDate->sub(new \DateInterval('P60D'));
  63. $cleanTempFiles = $this->getMockFor('preview');
  64. $file1 = $this->getMockBuilder(File::class)->getMock();
  65. $file1->method('getSlug')->willReturn('/foo');
  66. $file2 = $this->getMockBuilder(File::class)->getMock();
  67. $file2->method('getSlug')->willReturn('/bar');
  68. $file3 = $this->getMockBuilder(File::class)->getMock();
  69. $file3->method('getSlug')->willReturn('/foo/bar');
  70. $cleanTempFiles->method('listFilesToDelete')->with($maxDate)->willReturn([$file1, $file2, $file3]);
  71. $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
  72. ['3 temporary files to be removed'],
  73. ['> Printing the first and last 50 :'],
  74. [' * /foo'],
  75. [' * /bar'],
  76. [' * /foo/bar']
  77. );
  78. $cleanTempFiles->preview();
  79. }
  80. public function testExecute(): void
  81. {
  82. DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
  83. $maxDate = DatesUtils::new();
  84. $maxDate->sub(new \DateInterval('P60D'));
  85. $cleanTempFiles = $this->getMockFor('execute');
  86. $files = [
  87. $this->getMockBuilder(File::class)->getMock(),
  88. $this->getMockBuilder(File::class)->getMock(),
  89. $this->getMockBuilder(File::class)->getMock(),
  90. ];
  91. $cleanTempFiles->method('listFilesToDelete')->with($maxDate)->willReturn($files);
  92. $cleanTempFiles->expects(self::once())->method('deleteFiles')->with($files);
  93. $cleanTempFiles->execute();
  94. }
  95. public function testListFilesToDelete(): void
  96. {
  97. $cleanTempFiles = $this->getMockFor('listFilesToDelete');
  98. DatesUtils::setFakeDatetime('2023-05-01 00:00:00');
  99. $maxDate = DatesUtils::new();
  100. // Mock la méthode getQueryBuilder()
  101. $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
  102. ->disableOriginalConstructor()
  103. ->getMock();
  104. $this->fileRepository->method('createQueryBuilder')->with('f')->willReturn($queryBuilder);
  105. // S'attend à ce que la méthode select() soit appelée
  106. $queryBuilder->expects($this->once())
  107. ->method('select');
  108. // S'attend à ce que la méthode getQueryConditions() soit appelée avec $maxDate
  109. $cleanTempFiles->expects($this->once())
  110. ->method('getQueryConditions')
  111. ->with($queryBuilder, $maxDate);
  112. // Mock la méthode getQuery() et getResult() pour renvoyer un tableau vide
  113. $query = $this->getMockBuilder(Query::class)
  114. ->disableOriginalConstructor()
  115. ->getMock();
  116. $query->expects($this->once())
  117. ->method('getResult')
  118. ->willReturn([]);
  119. $queryBuilder->expects($this->once())
  120. ->method('getQuery')
  121. ->willReturn($query);
  122. // Appeler la méthode listFilesToDelete()
  123. $result = $cleanTempFiles->listFilesToDelete($maxDate);
  124. // Vérifier que la méthode getResult() a été appelée sur la requête
  125. $this->assertEquals([], $result);
  126. }
  127. public function testDeleteFiles(): void
  128. {
  129. $cleanTempFiles = $this->getMockFor('deleteFiles');
  130. $file1 = $this->getMockBuilder(File::class)->getMock();
  131. $file2 = $this->getMockBuilder(File::class)->getMock();
  132. $file3 = $this->getMockBuilder(File::class)->getMock();
  133. $files = [$file1, $file2, $file3];
  134. // Set expectations for connection
  135. $this->connection->expects($this->once())->method('setAutoCommit')->with(false);
  136. $this->connection->expects($this->exactly(3))->method('beginTransaction');
  137. $this->connection->expects($this->exactly(3))->method('commit');
  138. $this->connection->expects($this->never())->method('rollback');
  139. // Set expectations for file status changes
  140. $file1->expects($this->once())->method('setStatus')->with(FileStatusEnum::DELETION_REQUESTED);
  141. $file2->expects($this->once())->method('setStatus')->with(FileStatusEnum::DELETION_REQUESTED);
  142. $file3->expects($this->once())->method('setStatus')->with(FileStatusEnum::DELETION_REQUESTED);
  143. // Set expectations for entity manager operations
  144. $this->em->expects($this->exactly(3))->method('persist')->withConsecutive([$file1], [$file2], [$file3]);
  145. $this->em->expects($this->exactly(3))->method('flush');
  146. // Set expectations for UI progress calls
  147. $this->ui->expects($this->exactly(4))->method('progress');
  148. // Set expectations for logger calls
  149. $this->logger->expects($this->exactly(3))->method('info')->withConsecutive(
  150. ['3 temporary files to be marked for deletion'],
  151. ['Marking files for deletion...'],
  152. ['3 files marked for deletion']
  153. );
  154. $cleanTempFiles->deleteFiles($files);
  155. }
  156. public function testDeleteFilesWithNonBlockingErrors(): void
  157. {
  158. $cleanTempFiles = $this->getMockFor('deleteFiles');
  159. $file1 = $this->getMockBuilder(File::class)->getMock();
  160. $file2 = $this->getMockBuilder(File::class)->getMock();
  161. $files = [$file1, $file2];
  162. // Set expectations for connection
  163. $this->connection->expects($this->once())->method('setAutoCommit')->with(false);
  164. $this->connection->expects($this->exactly(2))->method('beginTransaction');
  165. $this->connection->expects($this->never())->method('commit');
  166. $this->connection->expects($this->exactly(2))->method('rollback');
  167. // Set expectations for file status changes
  168. $file1->expects($this->once())->method('setStatus')->with(FileStatusEnum::DELETION_REQUESTED);
  169. $file2->expects($this->once())->method('setStatus')->with(FileStatusEnum::DELETION_REQUESTED);
  170. // Set expectations for entity manager operations that throw errors
  171. $this->em->expects($this->exactly(2))->method('persist')->withConsecutive([$file1], [$file2]);
  172. $this->em->expects($this->exactly(2))->method('flush')
  173. ->willReturnOnConsecutiveCalls(
  174. $this->throwException(new \RuntimeException('Some error')),
  175. $this->throwException(new \InvalidArgumentException('Some other error'))
  176. );
  177. // Set expectations for UI progress calls
  178. $this->ui->expects($this->exactly(3))->method('progress');
  179. // Set expectations for logger calls
  180. $this->logger->expects($this->exactly(3))->method('info')->withConsecutive(
  181. ['2 temporary files to be marked for deletion'],
  182. ['Marking files for deletion...'],
  183. ['0 files marked for deletion']
  184. );
  185. $this->logger->expects($this->exactly(2))->method('error')
  186. ->withConsecutive(
  187. ['ERROR : Some error'],
  188. ['ERROR : Some other error']
  189. );
  190. $cleanTempFiles->deleteFiles($files);
  191. }
  192. public function testDeleteFilesWithBlockingError(): void
  193. {
  194. $cleanTempFiles = $this->getMockFor('deleteFiles');
  195. $file1 = $this->getMockBuilder(File::class)->getMock();
  196. $files = [$file1];
  197. // Set expectations for connection
  198. $this->connection->expects($this->once())->method('setAutoCommit')->with(false);
  199. $this->connection->expects($this->once())->method('beginTransaction');
  200. $this->connection->expects($this->never())->method('commit');
  201. $this->connection->expects($this->once())->method('rollback');
  202. // Set expectations for file status changes
  203. $file1->expects($this->once())->method('setStatus')->with(FileStatusEnum::DELETION_REQUESTED);
  204. // Set expectations for entity manager operations that throw blocking error
  205. $this->em->expects($this->once())->method('persist')->with($file1);
  206. $this->em->expects($this->once())->method('flush')
  207. ->willThrowException(new \Exception('Some unknown error'));
  208. // Set expectations for UI progress calls
  209. $this->ui->expects($this->exactly(2))->method('progress');
  210. // Set expectations for logger calls
  211. $this->logger->expects($this->exactly(2))->method('info')->withConsecutive(
  212. ['1 temporary files to be marked for deletion'],
  213. ['Marking files for deletion...']
  214. );
  215. $this->expectException(\Exception::class);
  216. $this->expectExceptionMessage('Some unknown error');
  217. $cleanTempFiles->deleteFiles($files);
  218. }
  219. public function testGetQueryConditions(): void
  220. {
  221. DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
  222. $cleanTempFiles = $this->getMockFor('getQueryConditions');
  223. $maxDate = DatesUtils::new();
  224. $maxDate->sub(new \DateInterval('P60D'));
  225. $queryBuilder = $this->getMockBuilder(QueryBuilder::class)->disableOriginalConstructor()->getMock();
  226. $expr = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
  227. $queryBuilder->method('expr')->willReturn($expr);
  228. $cmp1 = $this->getMockBuilder(Comparison::class)->disableOriginalConstructor()->getMock();
  229. $cmp2 = $this->getMockBuilder(Comparison::class)->disableOriginalConstructor()->getMock();
  230. $cmp3 = $this->getMockBuilder(Comparison::class)->disableOriginalConstructor()->getMock();
  231. $expr->expects(self::exactly(2))->method('eq')->willReturnMap(
  232. [
  233. ['f.isTemporaryFile', ':temporaryTrue', $cmp1],
  234. ['f.status', ':status', $cmp2],
  235. ]
  236. );
  237. $expr->expects(self::once())->method('lt')->with('f.createDate', ':maxDate')->willReturn($cmp3);
  238. $expr->expects(self::once())->method('isNull')->with('f.createDate')->willReturn('f.createDate is null');
  239. $orX1 = $this->getMockBuilder(Expr\Orx::class)->disableOriginalConstructor()->getMock();
  240. $orX2 = $this->getMockBuilder(Expr\Orx::class)->disableOriginalConstructor()->getMock();
  241. $expr->expects(self::exactly(2))->method('orX')->willReturnMap(
  242. [
  243. [$cmp1, $cmp2, $orX1],
  244. [$cmp3, 'f.createDate is null', $orX2],
  245. ]
  246. );
  247. $queryBuilder
  248. ->expects(self::exactly(2))
  249. ->method('andWhere')
  250. ->withConsecutive(
  251. [$orX1],
  252. [$orX2],
  253. )
  254. ->willReturnSelf();
  255. $queryBuilder
  256. ->expects(self::exactly(3))
  257. ->method('setParameter')
  258. ->withConsecutive(
  259. ['temporaryTrue', true],
  260. ['status', FileStatusEnum::DELETED],
  261. ['maxDate', '2021-11-09'],
  262. )
  263. ->willReturnSelf();
  264. $cleanTempFiles->getQueryConditions($queryBuilder, $maxDate);
  265. }
  266. }