Vincent 2 years ago
parent
commit
4f9aec7f31

+ 20 - 0
docker-compose.override.yml

@@ -0,0 +1,20 @@
+version: '3'
+
+services:
+###> doctrine/doctrine-bundle ###
+  database:
+    ports:
+      - "5432"
+###< doctrine/doctrine-bundle ###
+
+###> symfony/mailer ###
+  mailer:
+    image: schickling/mailcatcher
+    ports: ["1025", "1080"]
+###< symfony/mailer ###
+
+###> symfony/mercure-bundle ###
+  mercure:
+    ports:
+      - "80"
+###< symfony/mercure-bundle ###

+ 44 - 0
docker-compose.yml

@@ -0,0 +1,44 @@
+version: '3'
+
+services:
+###> doctrine/doctrine-bundle ###
+  database:
+    image: postgres:${POSTGRES_VERSION:-15}-alpine
+    environment:
+      POSTGRES_DB: ${POSTGRES_DB:-app}
+      # You should definitely change the password in production
+      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
+      POSTGRES_USER: ${POSTGRES_USER:-app}
+    volumes:
+      - database_data:/var/lib/postgresql/data:rw
+      # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
+      # - ./docker/db/data:/var/lib/postgresql/data:rw
+###< doctrine/doctrine-bundle ###
+
+###> symfony/mercure-bundle ###
+  mercure:
+    image: dunglas/mercure
+    restart: unless-stopped
+    environment:
+      SERVER_NAME: ':80'
+      MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
+      MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
+      # Set the URL of your Symfony project (without trailing slash!) as value of the cors_origins directive
+      MERCURE_EXTRA_DIRECTIVES: |
+        cors_origins http://127.0.0.1:8000
+    # Comment the following line to disable the development mode
+    command: /usr/bin/caddy run --config /etc/caddy/Caddyfile.dev
+    volumes:
+      - mercure_data:/data
+      - mercure_config:/config
+###< symfony/mercure-bundle ###
+
+volumes:
+###> doctrine/doctrine-bundle ###
+  database_data:
+###< doctrine/doctrine-bundle ###
+
+###> symfony/mercure-bundle ###
+  mercure_data:
+  mercure_config:
+###< symfony/mercure-bundle ###

+ 24 - 14
src/Service/Cron/Job/CleanTempFiles.php

@@ -10,7 +10,9 @@ use App\Service\Cron\BaseCronJob;
 use App\Service\Cron\CronjobInterface;
 use App\Service\File\Storage\LocalStorage;
 use App\Service\Utils\DatesUtils;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\ConnectionException;
 use Doctrine\ORM\QueryBuilder;
 use Exception;
 use JetBrains\PhpStorm\Pure;
@@ -48,6 +50,8 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
     public function preview(): void
     {
         $maxDate = DatesUtils::new();
+        $maxDate->sub(new \DateInterval('P' . self::DELETE_OLDER_THAN . 'D'));
+
         $files = $this->listFilesToDelete($maxDate);
         $total = count($files);
         $this->ui->print($total . " temporary files to be removed");
@@ -73,6 +77,8 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
     public function execute(): void
     {
         $maxDate = DatesUtils::new();
+        $maxDate->sub(new \DateInterval('P' . self::DELETE_OLDER_THAN . 'D'));
+
         $files = $this->listFilesToDelete($maxDate);
         $this->deleteFiles($files);
         $this->purgeDb($maxDate);
@@ -81,10 +87,10 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
     /**
      * List the files to delete in the DB
      * @param \DateTime $maxDate
-     * @return array<File>
+     * @return Collection<File>
      * @throws Exception
      */
-    protected function listFilesToDelete(\DateTime $maxDate): array
+    protected function listFilesToDelete(\DateTime $maxDate): Collection
     {
         $this->ui->print('List temporary files created before ' . $maxDate->format('c'));
 
@@ -95,22 +101,20 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
     }
 
     /**
-     * Purge File table and returns the number of deleted records
+     * Purge the DB from temporary file records older than N days
      *
      * @param \DateTime $maxDate
      * @param bool $commit
-     * @return int
+     * @throws ConnectionException
      */
     protected function purgeDb(\DateTime $maxDate, bool $commit = true, ): void {
-        $this->ui->print('Purge DB from records of temporary files create before ' . $maxDate->format('c'));
-
         $this->connection->beginTransaction();
         $this->connection->setAutoCommit(false);
 
         $purged = 0;
 
         try{
-            $purged += $this->purgeFile($maxDate);
+            $purged += $this->purgeFiles($maxDate);
 
             if ($commit) {
                 $this->connection->commit();
@@ -119,12 +123,19 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
                 $this->connection->rollback();
                 $this->ui->print('DB purged - ' . $purged . ' records would be permanently deleted');
             }
-        }catch (\Throwable $exception){
+        }catch (\Exception $exception){
             $this->connection->rollback();
+            throw $exception;
         }
     }
 
-    private function purgeFile($maxDate){
+    /**
+     * Purge File tables and returns the number of deleted records
+     *
+     * @param $maxDate
+     * @return int
+     */
+    protected function purgeFiles(\DateTime $maxDate): int{
         $queryBuilder = $this->fileRepository->createQueryBuilder('f');
         $queryBuilder->delete();
         $this->getQueryConditions($queryBuilder, $maxDate);
@@ -134,10 +145,10 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
     /**
      * Delete the files
      *
-     * @param array $files
+     * @param Collection<File> $files
      * @return void
      */
-    protected function deleteFiles(array $files): void {
+    protected function deleteFiles(Collection $files): void {
         $total = count($files);
         $this->ui->print($total . " temporary files to be removed");
 
@@ -149,7 +160,7 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
             try {
                 $i++;
                 $this->ui->progress($i, $total);
-                $this->storage->forceDelete($file);
+                $this->storage->hardDelete($file);
                 $deleted++;
             } catch (\RuntimeException $e) {
                 $this->ui->print('ERROR : ' . $e->getMessage());
@@ -162,9 +173,8 @@ class CleanTempFiles extends BaseCronJob implements CronjobInterface
     /**
      * @param QueryBuilder $queryBuilder
      * @param \DateTime $maxDate
-     * @return mixed
      */
-    private function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void{
+    protected function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void{
         $queryBuilder
             ->andWhere(
                 $queryBuilder->expr()->orX(

+ 5 - 3
src/Service/File/Storage/LocalStorage.php

@@ -227,13 +227,13 @@ class LocalStorage implements FileStorageInterface
     }
 
     /**
-     * Delete the given file from the filesystem and update the status of the File
+     * Uupdate the status of the File
      *
      * @param File $file
      * @param Access $author
      * @return File
      */
-    public function delete(File $file, Access $author): File
+    public function softDelete(File $file, Access $author): File
     {
         $file->setStatus(FileStatusEnum::DELETED()->getValue())
              ->setSize(0)
@@ -243,9 +243,11 @@ class LocalStorage implements FileStorageInterface
     }
 
     /**
+     * Delete the given file from the filesystem
+     *
      * @param File $file
      */
-    public function forceDelete(File $file): void
+    public function hardDelete(File $file): void
     {
         $deleted = $this->filesystem->delete($file->getSlug());
 

+ 123 - 175
tests/Unit/Service/Cron/Job/CleanTempFilesTest.php

@@ -1,9 +1,10 @@
 <?php
+
 namespace App\Tests\Unit\Service\Cron\Job;
 
-use App\Entity\Access\Access;
 use App\Entity\Core\File;
-use App\Repository\Access\AccessRepository;
+use App\Enum\Core\FileHostEnum;
+use App\Enum\Core\FileStatusEnum;
 use App\Repository\Core\FileRepository;
 use App\Service\Cron\Job\CleanTempFiles;
 use App\Service\Cron\UI\CronUIInterface;
@@ -11,7 +12,7 @@ use App\Service\File\Storage\LocalStorage;
 use App\Service\Utils\DatesUtils;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
-use Doctrine\Common\Collections\Criteria;
+use Doctrine\DBAL\Connection;
 use Doctrine\ORM\AbstractQuery;
 use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\ORM\Query\Expr;
@@ -19,11 +20,14 @@ use Doctrine\ORM\QueryBuilder;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
+use Symfony\Component\Config\Definition\Builder\ExprBuilder;
 
 class TestableCleanTempFile extends CleanTempFiles {
-    public function listFilesToDelete(): Collection { return parent::listFilesToDelete(); }
+    public function listFilesToDelete(\DateTime $maxDate): Collection { return parent::listFilesToDelete($maxDate); }
     public function deleteFiles(Collection $files): void { parent::deleteFiles($files); }
-    public function purgeDb(bool $commit = true): void { parent::purgeDb($commit); }
+    public function purgeDb(\DateTime $maxDate, bool $commit = true): void { parent::purgeDb($maxDate, $commit); }
+    public function purgeFiles(\DateTime $maxDate): int { return parent::purgeFiles($maxDate); }
+    public function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void { parent::getQueryConditions($queryBuilder, $maxDate); }
 }
 
 class CleanTempFilesTest extends TestCase
@@ -31,8 +35,7 @@ class CleanTempFilesTest extends TestCase
     private CronUIInterface|MockObject $ui;
     private MockObject|LoggerInterface $logger;
     private FileRepository|MockObject $fileRepository;
-    private EntityManagerInterface|MockObject $em;
-    private AccessRepository|MockObject $accessRepository;
+    private Connection|MockObject $connection;
     private LocalStorage|MockObject $storage;
 
     public function setUp(): void
@@ -41,14 +44,13 @@ class CleanTempFilesTest extends TestCase
         $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
 
         $this->fileRepository = $this->getMockBuilder(FileRepository::class)->disableOriginalConstructor()->getMock();
-        $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
-        $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
+        $this->connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
         $this->storage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock();
     }
 
     private function getMockFor(string $method) {
         $cleanTempFiles = $this->getMockBuilder(TestableCleanTempFile::class)
-            ->setConstructorArgs([$this->fileRepository, $this->em, $this->accessRepository, $this->storage])
+            ->setConstructorArgs([$this->connection, $this->fileRepository, $this->storage])
             ->setMethodsExcept([$method, 'setUI', 'setLoggerInterface'])
             ->getMock();
         $cleanTempFiles->setUI($this->ui);
@@ -59,6 +61,10 @@ class CleanTempFilesTest extends TestCase
 
     public function testPreview(): void
     {
+        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
+        $maxDate = DatesUtils::new();
+        $maxDate->sub(new \DateInterval('P60D'));
+
         $cleanTempFiles = $this->getMockFor('preview');
 
         $file1 = $this->getMockBuilder(File::class)->getMock();
@@ -70,7 +76,7 @@ class CleanTempFilesTest extends TestCase
         $file3 = $this->getMockBuilder(File::class)->getMock();
         $file3->method('getPath')->willReturn('/foo/bar');
 
-        $cleanTempFiles->method('listFilesToDelete')->willReturn(new ArrayCollection([$file1, $file2, $file3]));
+        $cleanTempFiles->method('listFilesToDelete')->with($maxDate)->willReturn(new ArrayCollection([$file1, $file2, $file3]));
 
         $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
             ["3 temporary files to be removed"],
@@ -80,12 +86,16 @@ class CleanTempFilesTest extends TestCase
             ["  * /foo/bar"]
         );
 
-        $cleanTempFiles->expects(self::once())->method('purgeDb')->with(false);
+        $cleanTempFiles->expects(self::once())->method('purgeDb')->with($maxDate, false);
 
         $cleanTempFiles->preview();
     }
 
     public function testExecute(): void {
+        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
+        $maxDate = DatesUtils::new();
+        $maxDate->sub(new \DateInterval('P60D'));
+
         $cleanTempFiles = $this->getMockFor('execute');
 
         $files = new ArrayCollection([
@@ -94,216 +104,154 @@ class CleanTempFilesTest extends TestCase
             $this->getMockBuilder(File::class)->getMock()
         ]);
 
-        $cleanTempFiles->method('listFilesToDelete')->willReturn($files);
+        $cleanTempFiles->method('listFilesToDelete')->willReturn($files)->with($maxDate);
 
         $cleanTempFiles->expects(self::once())->method('deleteFiles')->with($files);
 
-        $cleanTempFiles->expects(self::once())->method('purgeDb')->with();
+        $cleanTempFiles->expects(self::once())->method('purgeDb')->with($maxDate);
 
         $cleanTempFiles->execute();
     }
 
-    public function testListFilesToDelete(): void {
+    public function testListFilesToDelete()
+    {
         $cleanTempFiles = $this->getMockFor('listFilesToDelete');
 
-        DatesUtils::setFakeDatetime('2022-01-08 12:00:00');
-
-        $this->ui->expects(self::once())->method('print')->with(
-            'List temporary files created before 2022-01-01T12:00:00+00:00'
-        );
+        DatesUtils::setFakeDatetime('2023-05-01 00:00:00');
+        $maxDate = DatesUtils::new();
 
-        $file1 = $this->getMockBuilder(File::class)->getMock(); // Temporary, but recent : do not remove
-        $file1->method('getId')->willReturn(1);
-        $file1->method('getCreateDate')->willReturn(new \DateTime('2022-01-02'));
-        $file1->method('getIsTemporaryFile')->willReturn(true);
-
-        $file2 = $this->getMockBuilder(File::class)->getMock(); // Temporary and 1-year-old : remove
-        $file1->method('getId')->willReturn(2);
-        $file2->method('getCreateDate')->willReturn(new \DateTime('2021-01-01'));
-        $file2->method('getIsTemporaryFile')->willReturn(true);
-
-        $file3 = $this->getMockBuilder(File::class)->getMock(); // 2 years old but not temporary : do not remove
-        $file1->method('getId')->willReturn(3);
-        $file3->method('getCreateDate')->willReturn(new \DateTime('2020-01-01'));
-        $file3->method('getIsTemporaryFile')->willReturn(false);
-
-        $files = new ArrayCollection([$file1, $file2, $file3]);
-
-        $this->fileRepository
-            ->expects(self::once())
-            ->method('matching')
-            ->with(
-                self::callback(static function (Criteria $c) use ($files) {
-                    $matching = $files->matching($c)->toArray();
-                    return count($matching) === 1 && array_values($matching)[0] === $files[1];
-                })
-            )->willReturn($files);
-
-        $this->assertEquals(
-            $files,
-            $cleanTempFiles->listFilesToDelete()
-        );
-    }
+        // Mock la méthode getQueryBuilder()
+        $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
+            ->disableOriginalConstructor()
+            ->getMock();
 
-    public function testDeleteFiles(): void
-    {
-        $cleanTempFiles = $this->getMockFor('deleteFiles');
+        $this->fileRepository->method('createQueryBuilder')->with('f')->willReturn($queryBuilder);
 
-        $file1 = $this->getMockBuilder(File::class)->getMock();
-        $file2 = $this->getMockBuilder(File::class)->getMock();
-        $file3 = $this->getMockBuilder(File::class)->getMock();
-        $files = new ArrayCollection([$file1, $file2, $file3]);
+        // S'attend à ce que la méthode select() soit appelée
+        $queryBuilder->expects($this->once())
+            ->method('select');
 
-        $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
-            ['3 temporary files to be removed'],
-            ['Deleting files...'],
-            ['3 files deleted'],
-        );
+        // S'attend à ce que la méthode getQueryConditions() soit appelée avec $maxDate
+        $cleanTempFiles->expects($this->once())
+            ->method('getQueryConditions')
+            ->with($queryBuilder, $maxDate);
 
-        $author = $this->getMockBuilder(Access::class)->getMock();
-        $this->accessRepository->method('find')->with(10984)->willReturn($author);
+        // Mock la méthode getQuery() et getResult() pour renvoyer un tableau vide
+        $query = $this->getMockBuilder(AbstractQuery::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $query->expects($this->once())
+            ->method('getResult')
+            ->willReturn(new ArrayCollection());
 
-        $this->ui->expects(self::exactly(4))->method('progress')->withConsecutive([0, 3], [1, 3], [2, 3], [3, 3]);
+        $queryBuilder->expects($this->once())
+            ->method('getQuery')
+            ->willReturn($query);
 
-        $this->storage
-            ->expects(self::exactly(3))
-            ->method('delete')
-            ->withConsecutive(
-                [$file1, $author],
-                [$file2, $author],
-                [$file3, $author]
-            );
+        // Appeler la méthode listFilesToDelete()
+        $result = $cleanTempFiles->listFilesToDelete($maxDate);
 
-        $cleanTempFiles->deleteFiles($files);
+        // Vérifier que la méthode getResult() a été appelée sur la requête
+        $this->assertInstanceOf(Collection::class, $result);
+        $this->assertEquals(0, $result->count());
     }
 
-    public function testDeleteFilesUnknownAuthor(): void
+    public function testPurgeDbCommitsTransactionIfCommitIsTrue(): void
     {
-        $cleanTempFiles = $this->getMockFor('deleteFiles');
+        $cleanTempFiles = $this->getMockFor('purgeDb');
 
-        $file1 = $this->getMockBuilder(File::class)->getMock();
-        $file2 = $this->getMockBuilder(File::class)->getMock();
-        $file3 = $this->getMockBuilder(File::class)->getMock();
-        $files = new ArrayCollection([$file1, $file2, $file3]);
+        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
+        $maxDate = new \DateTime('now');
 
-        $this->accessRepository->method('find')->with(10984)->willReturn(null);
+        $this->connection->expects($this->once())
+            ->method('beginTransaction');
+        $this->connection->expects($this->once())
+            ->method('setAutoCommit')
+            ->with(false);
 
-        $this->ui->expects(self::never())->method('progress');
-        $this->storage->expects(self::never())->method('delete');
+        $cleanTempFiles->method('purgeFiles')->willReturn(5)->with($maxDate);
 
-        $this->expectException(\RuntimeException::class);
-        $this->expectExceptionMessage('Access 10984 could not be found');
+        $this->connection->expects($this->once())
+            ->method('commit');
 
-        $cleanTempFiles->deleteFiles($files);
+        $this->ui->expects($this->once())
+            ->method('print')
+            ->with('DB purged - 5 records permanently deleted');
+
+        $cleanTempFiles->purgeDb($maxDate);
     }
 
-    public function testDeleteFilesDeletionError(): void
+    public function testPurgeDbRollsbackTransactionIfCommitIsFalse(): void
     {
-        $cleanTempFiles = $this->getMockFor('deleteFiles');
-
-        $file1 = $this->getMockBuilder(File::class)->getMock();
-        $file1->method('getId')->willReturn(1);
-
-        $file2 = $this->getMockBuilder(File::class)->getMock();
-        $file2->method('getId')->willReturn(2);
+        $cleanTempFiles = $this->getMockFor('purgeDb');
 
-        $files = new ArrayCollection([$file1, $file2]);
+        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
+        $maxDate = DatesUtils::new();
 
-        $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
-            ['2 temporary files to be removed'],
-            ['Deleting files...'],
-            ['ERROR : foo'],
-            ['1 files deleted'],
-        );
+        $this->connection->expects($this->once())
+            ->method('beginTransaction');
+        $this->connection->expects($this->once())
+            ->method('setAutoCommit')
+            ->with(false);
 
-        $author = $this->getMockBuilder(Access::class)->getMock();
-        $this->accessRepository->method('find')->with(10984)->willReturn($author);
+        $cleanTempFiles->method('purgeFiles')->willReturn(5)->with($maxDate);
 
-        $this->ui->expects(self::exactly(3))->method('progress')->withConsecutive([0, 2], [1, 2], [2, 2]);
+        $this->connection->expects($this->once())
+            ->method('rollback');
+        $this->ui->expects($this->once())
+            ->method('print')
+            ->with('DB purged - 5 records would be permanently deleted');
 
-        $this->storage
-            ->expects(self::exactly(2))
-            ->method('delete')
-            ->willReturnCallback(static function ($file, $author): File {
-                if ($file->getId() === 1) {
-                    throw new \RuntimeException('foo');
-                }
-                return $file;
-            });
-
-        $cleanTempFiles->deleteFiles($files);
+        $cleanTempFiles->purgeDb($maxDate, false);
     }
 
-    public function testPurgeDb(): void {
+    public function testPurgeDbRollsbackTransactionOnException(): void
+    {
         $cleanTempFiles = $this->getMockFor('purgeDb');
 
-        DatesUtils::setFakeDatetime('2022-01-01 12:00:00');
-        $this->ui->expects(self::exactly(2))->method('print')->withConsecutive(
-            ['Purge DB from records of files deleted before 2021-01-01T12:00:00+00:00'],
-            ['DB purged - 3 records permanently deleted'],
-        );
-
-        $qb = $this->getMockBuilder(QueryBuilder::class)->disableOriginalConstructor()->getMock();
-        $this->em->expects(self::once())->method('createQueryBuilder')->willReturn($qb);
-
-        $exprBuilder = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
-        $qb->method('expr')->willReturn($exprBuilder);
-
-        $cmp1 = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
-        $exprBuilder->expects(self::once())->method('eq')->with('f.isTemporaryFile', 1)->willReturn($cmp1);
-
-        $cmp2 = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
-        $exprBuilder->expects(self::once())->method('lt')->with('f.updateDate', ':maxDate')->willReturn($cmp2);
+        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
+        $maxDate = DatesUtils::new();
 
-        $qb->expects(self::once())->method('delete')->with('File', 'f')->willReturnSelf();
-        $qb->method('where')->with($cmp1)->willReturnSelf();
-        $qb->method('andWhere')->with($cmp2)->willReturnSelf();
-        $qb->method('setParameter')->with('maxDate', DatesUtils::new('2021-01-01T12:00:00+00:00'))->willReturnSelf();
+        $cleanTempFiles->method('purgeFiles')->willThrowException(new \Exception('error'))->with($maxDate);
 
-        $q = $this->getMockBuilder(AbstractQuery::class)->disableOriginalConstructor()->getMock();
-        $qb->method('getQuery')->willReturn($q);
+        $this->connection->expects($this->once())
+            ->method('beginTransaction');
+        $this->connection->expects($this->once())
+            ->method('rollback');
+        $this->ui->expects($this->never())
+            ->method('print');
 
-        $q->method('getResult')->willReturn(3);
-
-        $this->em->expects(self::once())->method('commit');
-
-        $cleanTempFiles->purgeDb();
+        $this->expectException(\Exception::class);
+        $cleanTempFiles->purgeDb($maxDate, true);
     }
 
-    public function testPurgeDbNoCommit(): void {
-        $cleanTempFiles = $this->getMockFor('purgeDb');
-
-        DatesUtils::setFakeDatetime('2022-01-01 12:00:00');
-        $this->ui->expects(self::exactly(2))->method('print')->withConsecutive(
-            ['Purge DB from records of files deleted before 2021-01-01T12:00:00+00:00'],
-            ['DB purged - 3 records would be permanently deleted'],
-        );
-
-        $qb = $this->getMockBuilder(QueryBuilder::class)->disableOriginalConstructor()->getMock();
-        $this->em->expects(self::once())->method('createQueryBuilder')->willReturn($qb);
 
-        $exprBuilder = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
-        $qb->method('expr')->willReturn($exprBuilder);
-
-        $cmp1 = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
-        $exprBuilder->expects(self::once())->method('eq')->with('f.isTemporaryFile', 1)->willReturn($cmp1);
-
-        $cmp2 = $this->getMockBuilder(Expr::class)->disableOriginalConstructor()->getMock();
-        $exprBuilder->expects(self::once())->method('lt')->with('f.updateDate', ':maxDate')->willReturn($cmp2);
+    public function testPurgeFilesDeletes()
+    {
+        $cleanTempFiles = $this->getMockFor('purgeFiles');
 
-        $qb->expects(self::once())->method('delete')->with('File', 'f')->willReturnSelf();
-        $qb->method('where')->willReturnSelf();
-        $qb->method('andWhere')->willReturnSelf();
-        $qb->method('setParameter')->willReturnSelf();
+        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
+        $maxDate = DatesUtils::new();
 
-        $q = $this->getMockBuilder(AbstractQuery::class)->disableOriginalConstructor()->getMock();
-        $qb->method('getQuery')->willReturn($q);
+        $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $queryBuilder->expects($this->once())
+            ->method('delete')
+            ->willReturnSelf();
 
-        $q->method('getResult')->willReturn(3);
+        $query = $this->getMockBuilder(AbstractQuery::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $queryBuilder->expects($this->once())
+            ->method('getQuery')
+            ->willReturn($query);
+        $query->expects($this->once())
+            ->method('execute')
+            ->willReturn(3);
 
-        $this->em->expects(self::never())->method('commit');
-        $this->em->expects(self::once())->method('rollback');
+        $this->fileRepository->method('createQueryBuilder')->with('f')->willReturn($queryBuilder);
 
-        $cleanTempFiles->purgeDb(false);
+        $this->assertEquals(3, $cleanTempFiles->purgeFiles($maxDate));
     }
 }

+ 24 - 16
tests/Unit/Service/File/Storage/LocalStorageTest.php

@@ -468,12 +468,12 @@ class LocalStorageTest extends TestCase
     }
 
     /**
-     * @see LocalStorage::delete()
+     * @see LocalStorage::softDelete()
      */
-    public function testDelete(): void {
+    public function testSoftdelete(): void {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['delete'])
+            ->setMethodsExcept(['softDelete'])
             ->getMock();
 
         $author = $this->getMockBuilder(Access::class)->getMock();
@@ -482,42 +482,50 @@ class LocalStorageTest extends TestCase
         $file = $this->getMockBuilder(File::class)->getMock();
         $file->method('getSlug')->willReturn('key');
 
-        $this->filesystem->expects(self::once())->method('delete')->with('key')->willReturn(true);
-
         $file->expects(self::once())->method('setStatus')->with(FileStatusEnum::DELETED()->getValue())->willReturnSelf();
         $file->expects(self::once())->method('setSize')->with(0)->willReturnSelf();
         $file->expects(self::once())->method('setUpdatedBy')->with(123)->willReturnSelf();
 
-        $returned = $fileStorage->delete($file, $author);
+        $returned = $fileStorage->softDelete($file, $author);
 
         $this->assertEquals($file, $returned);
     }
 
     /**
-     * @see LocalStorage::delete()
+     * @see LocalStorage::hardDelete()
      */
-    public function testDeleteFailed(): void {
+    public function testHardDelete(): void {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['delete'])
+            ->setMethodsExcept(['hardDelete'])
             ->getMock();
 
-        $author = $this->getMockBuilder(Access::class)->getMock();
-        $author->method('getId')->willReturn(123);
+        $file = $this->getMockBuilder(File::class)->getMock();
+        $file->method('getSlug')->willReturn('key');
+
+        $this->filesystem->expects(self::once())->method('delete')->with('key')->willReturn(true);
+
+        $fileStorage->hardDelete($file);
+    }
+
+    /**
+     * @see LocalStorage::hardDelete()
+     */
+    public function testHardDeleteFailed(): void {
+        $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
+            ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
+            ->setMethodsExcept(['hardDelete'])
+            ->getMock();
 
         $file = $this->getMockBuilder(File::class)->getMock();
         $file->method('getSlug')->willReturn('key');
 
         $this->filesystem->expects(self::once())->method('delete')->with('key')->willReturn(false);
 
-        $file->expects(self::never())->method('setStatus');
-        $file->expects(self::never())->method('setSize');
-        $file->expects(self::never())->method('setUpdatedBy');
-
         $this->expectException(RuntimeException::class);
         $this->expectExceptionMessage('File `' . $file->getSlug() . '` could\'nt be deleted');
 
-        $fileStorage->delete($file, $author);
+        $fileStorage->hardDelete($file);
     }
 
     /**