|
|
@@ -0,0 +1,430 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Tests\Unit\Service\Cron\Job;
|
|
|
+
|
|
|
+use App\Entity\Booking\Work;
|
|
|
+use App\Entity\Booking\WorkByUser;
|
|
|
+use App\Entity\Booking\Course;
|
|
|
+use App\Entity\Core\File;
|
|
|
+use App\Enum\Core\FileStatusEnum;
|
|
|
+use App\Service\Cron\Job\CleanWorkToDo;
|
|
|
+use App\Service\Cron\UI\CronUIInterface;
|
|
|
+use App\Service\File\Storage\LocalStorage;
|
|
|
+use App\Service\Utils\DatesUtils;
|
|
|
+use Doctrine\DBAL\Connection;
|
|
|
+use Doctrine\Common\Collections\ArrayCollection;
|
|
|
+use Doctrine\ORM\EntityManagerInterface;
|
|
|
+use Doctrine\ORM\Query;
|
|
|
+use Doctrine\ORM\QueryBuilder;
|
|
|
+use PHPUnit\Framework\MockObject\MockObject;
|
|
|
+use PHPUnit\Framework\TestCase;
|
|
|
+use Psr\Log\LoggerInterface;
|
|
|
+
|
|
|
+class TestableCleanWorkToDo extends CleanWorkToDo
|
|
|
+{
|
|
|
+ public function deleteWorks(bool $preview = false): void
|
|
|
+ {
|
|
|
+ parent::deleteWorks($preview);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function listWorksToDelete(\DateTime $maxDate): array
|
|
|
+ {
|
|
|
+ return parent::listWorksToDelete($maxDate);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function previewWorks(array $works): void
|
|
|
+ {
|
|
|
+ parent::previewWorks($works);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class CleanWorkToDoTest extends TestCase
|
|
|
+{
|
|
|
+ private CronUIInterface|MockObject $ui;
|
|
|
+ private MockObject|LoggerInterface $logger;
|
|
|
+ private Connection|MockObject $connection;
|
|
|
+ private EntityManagerInterface|MockObject $entityManager;
|
|
|
+ private LocalStorage|MockObject $storage;
|
|
|
+
|
|
|
+ public function setUp(): void
|
|
|
+ {
|
|
|
+ $this->ui = $this->getMockBuilder(CronUIInterface::class)->disableOriginalConstructor()->getMock();
|
|
|
+ $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
|
|
|
+ $this->connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
|
|
|
+ $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
|
|
|
+ $this->storage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getCleanWorkToDoMockFor(string $methodName): TestableCleanWorkToDo|MockObject
|
|
|
+ {
|
|
|
+ $cleanWorkToDo = $this->getMockBuilder(TestableCleanWorkToDo::class)
|
|
|
+ ->setConstructorArgs([$this->connection, $this->entityManager, $this->storage])
|
|
|
+ ->setMethodsExcept([$methodName, 'setUI', 'setLoggerInterface'])
|
|
|
+ ->getMock();
|
|
|
+ $cleanWorkToDo->setUI($this->ui);
|
|
|
+ $cleanWorkToDo->setLoggerInterface($this->logger);
|
|
|
+
|
|
|
+ return $cleanWorkToDo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::preview()
|
|
|
+ */
|
|
|
+ public function testPreview(): void
|
|
|
+ {
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('preview');
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('deleteWorks')
|
|
|
+ ->with(true);
|
|
|
+
|
|
|
+ $cleanWorkToDo->preview();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::execute()
|
|
|
+ */
|
|
|
+ public function testExecute(): void
|
|
|
+ {
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('execute');
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('deleteWorks')
|
|
|
+ ->with();
|
|
|
+
|
|
|
+ $cleanWorkToDo->execute();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::deleteWorks()
|
|
|
+ */
|
|
|
+ public function testDeleteWorks(): void
|
|
|
+ {
|
|
|
+ DatesUtils::setFakeDatetime('2023-12-08 00:00:00');
|
|
|
+ $expectedMaxDate = DatesUtils::new();
|
|
|
+ $expectedMaxDate->sub(new \DateInterval('P12M'));
|
|
|
+
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks');
|
|
|
+
|
|
|
+ $work1 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work1->method('getId')->willReturn(1);
|
|
|
+ $work2 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work2->method('getId')->willReturn(2);
|
|
|
+
|
|
|
+ $works = [$work1, $work2];
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('listWorksToDelete')
|
|
|
+ ->with($this->callback(function ($date) use ($expectedMaxDate) {
|
|
|
+ return $date->format('Y-m-d') === $expectedMaxDate->format('Y-m-d');
|
|
|
+ }))
|
|
|
+ ->willReturn($works);
|
|
|
+
|
|
|
+ $this->ui->expects(self::atLeastOnce())
|
|
|
+ ->method('print')
|
|
|
+ ->with('2 works to be removed');
|
|
|
+
|
|
|
+ $this->ui->expects(self::exactly(3))
|
|
|
+ ->method('progress');
|
|
|
+
|
|
|
+ $this->connection->expects(self::exactly(2))
|
|
|
+ ->method('beginTransaction');
|
|
|
+
|
|
|
+ $this->connection->expects(self::exactly(2))
|
|
|
+ ->method('commit');
|
|
|
+
|
|
|
+ $this->connection->expects(self::exactly(6))
|
|
|
+ ->method('executeStatement');
|
|
|
+
|
|
|
+ $this->logger->expects(self::exactly(2))
|
|
|
+ ->method('info')
|
|
|
+ ->withConsecutive(
|
|
|
+ [$this->stringContains('Finding works with courses created before')],
|
|
|
+ [$this->stringContains('Works cleanup completed')]
|
|
|
+ );
|
|
|
+
|
|
|
+ $cleanWorkToDo->deleteWorks();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::deleteWorks()
|
|
|
+ */
|
|
|
+ public function testDeleteWorksWhenPreview(): void
|
|
|
+ {
|
|
|
+ DatesUtils::setFakeDatetime('2023-12-08 00:00:00');
|
|
|
+
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks');
|
|
|
+
|
|
|
+ $work1 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work2 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+
|
|
|
+ $works = [$work1, $work2];
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('listWorksToDelete')
|
|
|
+ ->willReturn($works);
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('previewWorks')
|
|
|
+ ->with($works);
|
|
|
+
|
|
|
+ $this->ui->expects(self::exactly(2))
|
|
|
+ ->method('print')
|
|
|
+ ->withConsecutive(
|
|
|
+ ['2 works to be removed'],
|
|
|
+ ['> Printing the first and last 10 :']
|
|
|
+ );
|
|
|
+
|
|
|
+ $cleanWorkToDo->deleteWorks(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::deleteWorks()
|
|
|
+ */
|
|
|
+ public function testDeleteWorksWhenNoWorks(): void
|
|
|
+ {
|
|
|
+ DatesUtils::setFakeDatetime('2023-12-08 00:00:00');
|
|
|
+
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks');
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('listWorksToDelete')
|
|
|
+ ->willReturn([]);
|
|
|
+
|
|
|
+ $this->ui->expects(self::once())
|
|
|
+ ->method('print')
|
|
|
+ ->with('0 works to be removed');
|
|
|
+
|
|
|
+ $this->logger->expects(self::exactly(2))
|
|
|
+ ->method('info')
|
|
|
+ ->withConsecutive(
|
|
|
+ [$this->stringContains('Finding works with courses created before')],
|
|
|
+ ['No works found to delete']
|
|
|
+ );
|
|
|
+
|
|
|
+ $cleanWorkToDo->deleteWorks();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::deleteWorks()
|
|
|
+ */
|
|
|
+ public function testDeleteWorksWithNonBlockingError(): void
|
|
|
+ {
|
|
|
+ DatesUtils::setFakeDatetime('2023-12-08 00:00:00');
|
|
|
+
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks');
|
|
|
+
|
|
|
+ $work = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work->method('getId')->willReturn(1);
|
|
|
+
|
|
|
+ $works = [$work];
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('listWorksToDelete')
|
|
|
+ ->willReturn($works);
|
|
|
+
|
|
|
+ $this->connection->expects(self::once())
|
|
|
+ ->method('beginTransaction');
|
|
|
+
|
|
|
+ $this->connection->expects(self::once())
|
|
|
+ ->method('executeStatement')
|
|
|
+ ->willThrowException(new \RuntimeException('Database error'));
|
|
|
+
|
|
|
+ $this->connection->expects(self::once())
|
|
|
+ ->method('rollback');
|
|
|
+
|
|
|
+ $this->logger->expects(self::once())
|
|
|
+ ->method('error')
|
|
|
+ ->with('ERROR deleting work 1: Database error');
|
|
|
+
|
|
|
+ $cleanWorkToDo->deleteWorks();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::deleteWorks()
|
|
|
+ */
|
|
|
+ public function testDeleteWorksWithBlockingError(): void
|
|
|
+ {
|
|
|
+ DatesUtils::setFakeDatetime('2023-12-08 00:00:00');
|
|
|
+
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('deleteWorks');
|
|
|
+
|
|
|
+ $work = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work->method('getId')->willReturn(1);
|
|
|
+
|
|
|
+ $works = [$work];
|
|
|
+
|
|
|
+ $cleanWorkToDo->expects(self::once())
|
|
|
+ ->method('listWorksToDelete')
|
|
|
+ ->willReturn($works);
|
|
|
+
|
|
|
+ $this->connection->expects(self::once())
|
|
|
+ ->method('beginTransaction');
|
|
|
+
|
|
|
+ $this->connection->expects(self::once())
|
|
|
+ ->method('executeStatement')
|
|
|
+ ->willThrowException(new \Exception('Serious database error'));
|
|
|
+
|
|
|
+ $this->connection->expects(self::once())
|
|
|
+ ->method('rollback');
|
|
|
+
|
|
|
+ $this->expectException(\Exception::class);
|
|
|
+ $this->expectExceptionMessage('Serious database error');
|
|
|
+
|
|
|
+ $cleanWorkToDo->deleteWorks();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::listWorksToDelete()
|
|
|
+ */
|
|
|
+ public function testListWorksToDelete(): void
|
|
|
+ {
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('listWorksToDelete');
|
|
|
+
|
|
|
+ $maxDate = new \DateTime('2022-01-01');
|
|
|
+
|
|
|
+ $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
|
|
|
+ ->disableOriginalConstructor()
|
|
|
+ ->getMock();
|
|
|
+
|
|
|
+ $query = $this->getMockBuilder(Query::class)
|
|
|
+ ->disableOriginalConstructor()
|
|
|
+ ->getMock();
|
|
|
+
|
|
|
+ $work1 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work2 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+
|
|
|
+ $expectedResult = [$work1, $work2];
|
|
|
+
|
|
|
+ $this->entityManager->expects(self::once())
|
|
|
+ ->method('createQueryBuilder')
|
|
|
+ ->willReturn($queryBuilder);
|
|
|
+
|
|
|
+ $queryBuilder->expects(self::once())
|
|
|
+ ->method('select')
|
|
|
+ ->with('w')
|
|
|
+ ->willReturnSelf();
|
|
|
+
|
|
|
+ $queryBuilder->expects(self::once())
|
|
|
+ ->method('from')
|
|
|
+ ->with(Work::class, 'w')
|
|
|
+ ->willReturnSelf();
|
|
|
+
|
|
|
+ $queryBuilder->expects(self::once())
|
|
|
+ ->method('leftJoin')
|
|
|
+ ->with('w.course', 'c')
|
|
|
+ ->willReturnSelf();
|
|
|
+
|
|
|
+ $queryBuilder->expects(self::once())
|
|
|
+ ->method('where')
|
|
|
+ ->with('c.datetimeStart <= :maxDate OR c.datetimeStart IS NULL')
|
|
|
+ ->willReturnSelf();
|
|
|
+
|
|
|
+ $queryBuilder->expects(self::once())
|
|
|
+ ->method('setParameter')
|
|
|
+ ->with('maxDate', $maxDate)
|
|
|
+ ->willReturnSelf();
|
|
|
+
|
|
|
+ $queryBuilder->expects(self::once())
|
|
|
+ ->method('getQuery')
|
|
|
+ ->willReturn($query);
|
|
|
+
|
|
|
+ $query->expects(self::once())
|
|
|
+ ->method('getResult')
|
|
|
+ ->willReturn($expectedResult);
|
|
|
+
|
|
|
+ $result = $cleanWorkToDo->listWorksToDelete($maxDate);
|
|
|
+
|
|
|
+ $this->assertEquals($expectedResult, $result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::previewWorks()
|
|
|
+ */
|
|
|
+ public function testPreviewWorks(): void
|
|
|
+ {
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('previewWorks');
|
|
|
+
|
|
|
+ $course1 = $this->getMockBuilder(Course::class)->getMock();
|
|
|
+ $course1->method('getName')->willReturn('Test Course 1');
|
|
|
+
|
|
|
+ $course2 = $this->getMockBuilder(Course::class)->getMock();
|
|
|
+ $course2->method('getName')->willReturn('Test Course 2');
|
|
|
+
|
|
|
+ $work1 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work1->method('getId')->willReturn(1);
|
|
|
+ $work1->method('getCourse')->willReturn($course1);
|
|
|
+ $work1->method('getFiles')->willReturn(new ArrayCollection([]));
|
|
|
+ $work1->method('getWorkByUsers')->willReturn(new ArrayCollection([]));
|
|
|
+
|
|
|
+ $work2 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work2->method('getId')->willReturn(2);
|
|
|
+ $work2->method('getCourse')->willReturn($course2);
|
|
|
+ $work2->method('getFiles')->willReturn(new ArrayCollection([new File(), new File()]));
|
|
|
+ $work2->method('getWorkByUsers')->willReturn(new ArrayCollection([new WorkByUser()]));
|
|
|
+
|
|
|
+ $work3 = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work3->method('getId')->willReturn(3);
|
|
|
+ $work3->method('getCourse')->willReturn(null);
|
|
|
+ $work3->method('getFiles')->willReturn(new ArrayCollection([]));
|
|
|
+ $work3->method('getWorkByUsers')->willReturn(new ArrayCollection([]));
|
|
|
+
|
|
|
+ $works = [$work1, $work2, $work3];
|
|
|
+
|
|
|
+ $this->ui->expects(self::exactly(3))
|
|
|
+ ->method('print')
|
|
|
+ ->withConsecutive(
|
|
|
+ [' * Work ID 1 - Course: Test Course 1 - Files: 0 - Users: 0'],
|
|
|
+ [' * Work ID 2 - Course: Test Course 2 - Files: 2 - Users: 1'],
|
|
|
+ [' * Work ID 3 - Course: No Course - Files: 0 - Users: 0']
|
|
|
+ );
|
|
|
+
|
|
|
+ $cleanWorkToDo->previewWorks($works);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @see CleanWorkToDo::previewWorks()
|
|
|
+ */
|
|
|
+ public function testPreviewWorksWithManyWorks(): void
|
|
|
+ {
|
|
|
+ $cleanWorkToDo = $this->getCleanWorkToDoMockFor('previewWorks');
|
|
|
+
|
|
|
+ $works = [];
|
|
|
+
|
|
|
+ // Create 25 mock works to test the preview logic
|
|
|
+ for ($i = 1; $i <= 25; $i++) {
|
|
|
+ $course = $this->getMockBuilder(Course::class)->getMock();
|
|
|
+ $course->method('getName')->willReturn("Course $i");
|
|
|
+
|
|
|
+ $work = $this->getMockBuilder(Work::class)->getMock();
|
|
|
+ $work->method('getId')->willReturn($i);
|
|
|
+ $work->method('getCourse')->willReturn($course);
|
|
|
+ $work->method('getFiles')->willReturn(new ArrayCollection([]));
|
|
|
+ $work->method('getWorkByUsers')->willReturn(new ArrayCollection([]));
|
|
|
+
|
|
|
+ $works[] = $work;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Expect calls for first 10 works, then "(...)", then last 10 works
|
|
|
+ $expectedCalls = [];
|
|
|
+
|
|
|
+ // First 10
|
|
|
+ for ($i = 1; $i <= 10; $i++) {
|
|
|
+ $expectedCalls[] = [" * Work ID $i - Course: Course $i - Files: 0 - Users: 0"];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ellipsis
|
|
|
+ $expectedCalls[] = [' (...)'];
|
|
|
+
|
|
|
+ // Last 10
|
|
|
+ for ($i = 16; $i <= 25; $i++) {
|
|
|
+ $expectedCalls[] = [" * Work ID $i - Course: Course $i - Files: 0 - Users: 0"];
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->ui->expects(self::exactly(21))
|
|
|
+ ->method('print')
|
|
|
+ ->withConsecutive(...$expectedCalls);
|
|
|
+
|
|
|
+ $cleanWorkToDo->previewWorks($works);
|
|
|
+ }
|
|
|
+}
|