浏览代码

refactor Path and FileUtils class, complete unit tests

Olivier Massot 9 月之前
父节点
当前提交
8d6b0b67bb

+ 1 - 0
composer.json

@@ -35,6 +35,7 @@
     "myclabs/php-enum": "^1.7",
     "myclabs/php-enum": "^1.7",
     "nelmio/cors-bundle": "^2.1",
     "nelmio/cors-bundle": "^2.1",
     "odolbeau/phone-number-bundle": "^3.1",
     "odolbeau/phone-number-bundle": "^3.1",
+    "olinox14/path-php": "^0.1.8",
     "opentalent/phpdocx": "dev-master",
     "opentalent/phpdocx": "dev-master",
     "phpdocumentor/reflection-docblock": "^5.2",
     "phpdocumentor/reflection-docblock": "^5.2",
     "phpstan/phpdoc-parser": "^1.0",
     "phpstan/phpdoc-parser": "^1.0",

+ 11 - 4
src/Commands/Doctrine/SchemaUpdateCommand.php

@@ -4,7 +4,8 @@
 
 
 namespace App\Commands\Doctrine;
 namespace App\Commands\Doctrine;
 
 
-use App\Service\Utils\Path;
+use App\Service\Utils\FileUtils;
+use App\Service\Utils\PathUtils;
 use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
 use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
 use Doctrine\ORM\Tools\SchemaTool;
 use Doctrine\ORM\Tools\SchemaTool;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputInterface;
@@ -16,13 +17,19 @@ use Symfony\Component\Console\Style\SymfonyStyle;
  */
  */
 class SchemaUpdateCommand extends UpdateSchemaDoctrineCommand
 class SchemaUpdateCommand extends UpdateSchemaDoctrineCommand
 {
 {
+    public function __construct(
+        private readonly FileUtils $fileUtils,
+    ) {
+        parent::__construct();
+    }
+
     protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): ?int
     protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): ?int
     {
     {
         $output->writeln('-- Executing pre-update scripts');
         $output->writeln('-- Executing pre-update scripts');
 
 
         // Lists schema extensions scripts in the '/sql/schema-extensions' dir
         // Lists schema extensions scripts in the '/sql/schema-extensions' dir
-        $schemaExtensionsDir = Path::join(Path::getProjectDir(), 'sql', 'schema-extensions');
-        $scripts = Path::list($schemaExtensionsDir, '*.sql');
+        $schemaExtensionsDir = PathUtils::join(PathUtils::getProjectDir(), 'sql', 'schema-extensions');
+        $scripts = $this->fileUtils->list($schemaExtensionsDir, '*.sql');
         sort($scripts);
         sort($scripts);
 
 
         // Execute those scripts in alphabetical order
         // Execute those scripts in alphabetical order
@@ -30,7 +37,7 @@ class SchemaUpdateCommand extends UpdateSchemaDoctrineCommand
         $conn = $em->getConnection();
         $conn = $em->getConnection();
 
 
         foreach ($scripts as $script) {
         foreach ($scripts as $script) {
-            $sql = Path::read($script);
+            $sql = $this->fileUtils->getFileContent($script);
             $conn->executeQuery($sql);
             $conn->executeQuery($sql);
         }
         }
 
 

+ 3 - 3
src/Commands/SetupEnvCommand.php

@@ -2,7 +2,7 @@
 
 
 namespace App\Commands;
 namespace App\Commands;
 
 
-use App\Service\Utils\Path;
+use App\Service\Utils\PathUtils;
 use Symfony\Component\Console\Attribute\AsCommand;
 use Symfony\Component\Console\Attribute\AsCommand;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputInterface;
@@ -73,8 +73,8 @@ class SetupEnvCommand extends Command
             throw new \RuntimeException('Critical : unknown environment ['.$hostname.']');
             throw new \RuntimeException('Critical : unknown environment ['.$hostname.']');
         }
         }
 
 
-        $envFile = Path::join($this->projectDir, 'env', self::ENVIRONMENTS_FILES[$hostname]);
-        $symlinkLocation = Path::join($this->projectDir, '.env.local');
+        $envFile = PathUtils::join($this->projectDir, 'env', self::ENVIRONMENTS_FILES[$hostname]);
+        $symlinkLocation = PathUtils::join($this->projectDir, '.env.local');
 
 
         if (file_exists($symlinkLocation)) {
         if (file_exists($symlinkLocation)) {
             unlink($symlinkLocation);
             unlink($symlinkLocation);

+ 1 - 1
src/Service/Export/Encoder/DocXEncoder.php

@@ -46,7 +46,7 @@ class DocXEncoder implements EncoderInterface
 
 
             return $this->fileUtils->getFileContent($tempFilename);
             return $this->fileUtils->getFileContent($tempFilename);
         } finally {
         } finally {
-            $this->fileUtils->unlinkIfExist($tempFilename);
+            $this->fileUtils->rmIfExist($tempFilename);
         }
         }
     }
     }
 }
 }

+ 2 - 2
src/Service/Export/Encoder/PdfEncoder.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 namespace App\Service\Export\Encoder;
 namespace App\Service\Export\Encoder;
 
 
 use App\Enum\Export\ExportFormatEnum;
 use App\Enum\Export\ExportFormatEnum;
-use App\Service\Utils\Path;
+use App\Service\Utils\PathUtils;
 use Dompdf\Dompdf;
 use Dompdf\Dompdf;
 use Dompdf\Options;
 use Dompdf\Options;
 
 
@@ -41,7 +41,7 @@ class PdfEncoder implements EncoderInterface
     public function encode(string $html, array $options = []): string
     public function encode(string $html, array $options = []): string
     {
     {
         $this->domPdfOptions->setIsRemoteEnabled(true);
         $this->domPdfOptions->setIsRemoteEnabled(true);
-        $this->domPdfOptions->setChroot(Path::getProjectDir().'/public');
+        $this->domPdfOptions->setChroot(PathUtils::getProjectDir().'/public');
         $this->domPdfOptions->setDefaultPaperOrientation('portrait');
         $this->domPdfOptions->setDefaultPaperOrientation('portrait');
         $this->domPdfOptions->setDefaultPaperSize('A4');
         $this->domPdfOptions->setDefaultPaperSize('A4');
         $this->domPdfOptions->set($options);
         $this->domPdfOptions->set($options);

+ 9 - 9
src/Service/File/Storage/LocalStorage.php

@@ -16,7 +16,7 @@ use App\Enum\Core\FileVisibilityEnum;
 use App\Repository\Access\AccessRepository;
 use App\Repository\Access\AccessRepository;
 use App\Service\File\Factory\ImageFactory;
 use App\Service\File\Factory\ImageFactory;
 use App\Service\Utils\FileUtils;
 use App\Service\Utils\FileUtils;
-use App\Service\Utils\Path;
+use App\Service\Utils\PathUtils;
 use App\Service\Utils\UrlBuilder;
 use App\Service\Utils\UrlBuilder;
 use App\Service\Utils\Uuid;
 use App\Service\Utils\Uuid;
 use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\ORM\EntityManagerInterface;
@@ -210,7 +210,7 @@ class LocalStorage implements FileStorageInterface
 
 
             $uid = date('Ymd_His').'_'.Uuid::uuid(5);
             $uid = date('Ymd_His').'_'.Uuid::uuid(5);
 
 
-            $key = Path::join($prefix, $uid, $file->getName());
+            $key = PathUtils::join($prefix, $uid, $file->getName());
         } else {
         } else {
             $key = $file->getSlug();
             $key = $file->getSlug();
         }
         }
@@ -323,8 +323,8 @@ class LocalStorage implements FileStorageInterface
         if (!$this->filesystem->isDirectory($dirKey)) {
         if (!$this->filesystem->isDirectory($dirKey)) {
             throw new \RuntimeException('Directory `'.$dirKey.'` does not exist');
             throw new \RuntimeException('Directory `'.$dirKey.'` does not exist');
         }
         }
-        $dir = Path::join($this->fileStorageDir, $dirKey);
-        Path::rmtree($dir);
+        $dir = PathUtils::join($this->fileStorageDir, $dirKey);
+        $this->fileUtils->rmTree($dir);
     }
     }
 
 
     /**
     /**
@@ -343,19 +343,19 @@ class LocalStorage implements FileStorageInterface
         ?FileTypeEnum $type = null,
         ?FileTypeEnum $type = null,
     ): string {
     ): string {
         if ($owner instanceof Access) {
         if ($owner instanceof Access) {
-            $prefix = Path::join('organization', $owner->getOrganization()?->getId(), $owner->getId());
+            $prefix = PathUtils::join('organization', $owner->getOrganization()?->getId(), $owner->getId());
         } elseif ($owner instanceof Organization) {
         } elseif ($owner instanceof Organization) {
-            $prefix = Path::join('organization', $owner->getId());
+            $prefix = PathUtils::join('organization', $owner->getId());
         } else {
         } else {
-            $prefix = Path::join('person', $owner->getId());
+            $prefix = PathUtils::join('person', $owner->getId());
         }
         }
 
 
         if ($isTemporary) {
         if ($isTemporary) {
-            $prefix = Path::join('temp', $prefix);
+            $prefix = PathUtils::join('temp', $prefix);
         }
         }
 
 
         if ($type !== null && $type !== FileTypeEnum::NONE) {
         if ($type !== null && $type !== FileTypeEnum::NONE) {
-            $prefix = Path::join($prefix, strtolower($type->value));
+            $prefix = PathUtils::join($prefix, strtolower($type->value));
         }
         }
 
 
         return $prefix;
         return $prefix;

+ 2 - 2
src/Service/Twig/AssetsExtension.php

@@ -7,7 +7,7 @@ namespace App\Service\Twig;
 use App\Entity\Core\File;
 use App\Entity\Core\File;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\FileManager;
 use App\Service\File\FileManager;
-use App\Service\Utils\Path;
+use App\Service\Utils\PathUtils;
 use Twig\Extension\AbstractExtension;
 use Twig\Extension\AbstractExtension;
 use Twig\TwigFunction;
 use Twig\TwigFunction;
 
 
@@ -41,7 +41,7 @@ class AssetsExtension extends AbstractExtension
      */
      */
     public function absPath(string $partialPath): string
     public function absPath(string $partialPath): string
     {
     {
-        return Path::join(Path::getProjectDir(), 'public', $partialPath);
+        return PathUtils::join(PathUtils::getProjectDir(), 'public', $partialPath);
     }
     }
 
 
     /**
     /**

+ 90 - 14
src/Service/Utils/FileUtils.php

@@ -6,6 +6,10 @@ namespace App\Service\Utils;
 
 
 use App\Entity\Core\File;
 use App\Entity\Core\File;
 use Mimey\MimeTypes;
 use Mimey\MimeTypes;
+use Path\Exception\FileNotFoundException;
+use Path\Exception\IOException;
+use Path\Path;
+use Symfony\Component\Config\FileLocator;
 
 
 class FileUtils
 class FileUtils
 {
 {
@@ -14,7 +18,33 @@ class FileUtils
     }
     }
 
 
     /**
     /**
-     * Return the mimetype corresponding to the givent file extension.
+     * Return a Path object
+     * > Method is for testing purposes
+     *
+     * @see https://path-php.net/api
+     *
+     * @param string $path
+     * @return Path
+     */
+    protected function makePath(string $path): Path
+    {
+        return new Path($path);
+    }
+
+    /**
+     * Return a FileLocator object
+     * > Method is for testing purposes
+     *
+     * @param array<string> $directories
+     * @return FileLocator
+     */
+    protected function makeFileLocator(array $directories): FileLocator
+    {
+        return new FileLocator($directories);
+    }
+
+    /**
+     * Return the mimetype corresponding to the given file extension.
      */
      */
     public function getMimeTypeFromExt(string $ext): ?string
     public function getMimeTypeFromExt(string $ext): ?string
     {
     {
@@ -33,7 +63,7 @@ class FileUtils
             return null;
             return null;
         }
         }
 
 
-        return self::getMimeTypeFromExt($ext);
+        return $this->getMimeTypeFromExt($ext);
     }
     }
 
 
     /**
     /**
@@ -41,9 +71,9 @@ class FileUtils
      */
      */
     public function isImage(File $file): bool
     public function isImage(File $file): bool
     {
     {
-        $mimetype = $file->getMimeType() ?: $this->guessMimeTypeFromFilename($file->getName());
+        $mimetype = $file->getMimeType() ?? $this->guessMimeTypeFromFilename($file->getName());
 
 
-        return boolval(preg_match('#^image#', $mimetype));
+        return boolval(preg_match('#^image#', $mimetype ?? ''));
     }
     }
 
 
     /**
     /**
@@ -57,23 +87,69 @@ class FileUtils
         if (empty($ext)) {
         if (empty($ext)) {
             throw new \RuntimeException('Extension can not be empty');
             throw new \RuntimeException('Extension can not be empty');
         }
         }
-        $tempDir = Path::getProjectDir().'/var/tmp';
-        if (!is_dir($tempDir)) {
-            mkdir($tempDir);
-        }
+        $tempDir = PathUtils::getProjectDir().'/var/tmp';
+        $this->makePath($tempDir)->mkdir(0777, true);
 
 
-        return $tempDir.'/'.$prefix.uniqid().'.'.$ext;
+        return PathUtils::join($tempDir, uniqid($prefix).'.'.$ext);
     }
     }
 
 
-    public function unlinkIfExist(string $path): void
+    public function rmIfExist(string $path): void
     {
     {
-        if (file_exists($path)) {
-            unlink($path);
-        }
+        $this->makePath($path)->remove_p();
     }
     }
 
 
+    /**
+     * Reads the content of a file.
+     *
+     * @param string $path the path to the file to be read
+     * @return string the content of the file
+     * @throws \Path\Exception\FileNotFoundException
+     * @throws \Path\Exception\IOException
+     */
     public function getFileContent(string $path): string
     public function getFileContent(string $path): string
     {
     {
-        return file_get_contents($path);
+        return $this->makePath($path)->getContent();
+    }
+
+    /**
+     * Locate a file in the given directories and return its absolute path, or
+     * null if no file were found.
+     *
+     * @param array<string> $directories
+     */
+    public function locate(array $directories, string $filename): ?string
+    {
+        $fileLocator = $this->makeFileLocator($directories);
+
+        return $fileLocator->locate($filename, null, false)[0] ?? null;
+    }
+
+    /**
+     * Recursively removes a directory and all its contents.
+     *
+     * @param string $path the path to the directory to be removed
+     *
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public function rmTree(string $path): void
+    {
+        $this->makePath($path)->rmdir(true);
+    }
+
+    /**
+     * List the files located in the given directory.
+     *
+     * @param string $path
+     * @param string $glob
+     * @return list<string>
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public function list(string $path, string $glob = '*'): array
+    {
+        $paths = $this->makePath($path)->glob($glob);
+
+        return array_map('strval', $paths);
     }
     }
 }
 }

+ 0 - 121
src/Service/Utils/Path.php

@@ -1,121 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Service\Utils;
-
-use Symfony\Component\Config\FileLocator;
-
-/**
- * Various methods to manipulate file paths.
- */
-class Path
-{
-    /**
-     * Returns the application directory.
-     */
-    public static function getProjectDir(): string
-    {
-        return dirname(__FILE__, 4);
-    }
-
-    /**
-     * Properly join the $path and $tail as a new valid file's path.
-     *
-     * @see https://stackoverflow.com/a/15575293/4279120
-     *
-     * Ex:
-     *   Path.join('/var/www/', '/html/') > '/var/www/html'
-     *
-     * Input                   Result
-     * ['','']              >  ''
-     * ['','/']             >  '/'
-     * ['/','a']            >  '/a'
-     * ['/','/a']           >  '/a'
-     * ['abc','def']        >  'abc/def'
-     * ['abc','/def']       >  'abc/def'
-     * ['/abc','def']       >  '/abc/def'
-     * ['','foo.jpg']       >  'foo.jpg'
-     * ['dir','0','a.jpg']  >  'dir/0/a.jpg'
-     *
-     * @param string ...$paths
-     */
-    public static function join(string|int ...$paths): string
-    {
-        $paths = array_map(function ($x) { return ''.$x; }, $paths);
-        $paths = array_filter($paths, static function ($s) { return $s !== ''; });
-
-        return preg_replace('#/+#', '/', implode('/', $paths));
-    }
-
-    /**
-     * List the files located in the given directory.
-     *
-     * @return list<string>
-     */
-    public static function list(string $path, string $glob = '*'): array
-    {
-        return glob(self::join($path, $glob));
-    }
-
-    /**
-     * Reads the content of a file.
-     *
-     * @param string $path the path to the file to be read
-     *
-     * @return string the content of the file
-     *
-     * @throws \RuntimeException if the file could not be read
-     */
-    public static function read(string $path): string
-    {
-        $content = file_get_contents($path);
-        if ($content === false) {
-            throw new \RuntimeException('File could not be read');
-        }
-
-        return $content;
-    }
-
-    /**
-     * Locate a file in the given directories and return its absolute path.
-     *
-     * @param array<string> $directories
-     */
-    public static function locate(array $directories, string $filename): string
-    {
-        $fileLocator = new FileLocator($directories);
-
-        return $fileLocator->locate($filename, null, false)[0];
-    }
-
-    /**
-     * Recursively removes a directory and all its contents.
-     *
-     * @param string $path the path to the directory to be removed
-     *
-     * @return bool returns true if the directory was successfully removed, false otherwise
-     */
-    public static function rmtree(string $path): bool
-    {
-        if (!file_exists($path)) {
-            return true;
-        }
-
-        if (!is_dir($path)) {
-            return unlink($path);
-        }
-
-        foreach (scandir($path) as $item) {
-            if ($item == '.' || $item == '..') {
-                continue;
-            }
-
-            if (!self::rmtree($path.DIRECTORY_SEPARATOR.$item)) {
-                return false;
-            }
-        }
-
-        return rmdir($path);
-    }
-}

+ 54 - 0
src/Service/Utils/PathUtils.php

@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Utils;
+
+use Path\Exception\FileNotFoundException;
+use Path\Exception\IOException;
+use Path\Path;
+
+/**
+ * Various methods to manipulate file paths.
+ */
+class PathUtils
+{
+    /**
+     * Returns the application directory.
+     */
+    public static function getProjectDir(): string
+    {
+        return (string)(new Path(__FILE__))->parent(4);
+    }
+
+    /**
+     * Properly join the $path and $tail as a new valid file's path.
+     *
+     * @see https://stackoverflow.com/a/15575293/4279120
+     *
+     * Ex:
+     *   Path.join('/var/www/', '/html/') > '/var/www/html'
+     *
+     * Input                   Result
+     * ['','']              >  ''
+     * ['','/']             >  '/'
+     * ['/','a']            >  '/a'
+     * ['/','/a']           >  '/a'
+     * ['abc','def']        >  'abc/def'
+     * ['abc','/def']       >  '/def'
+     * ['/abc','def']       >  '/abc/def'
+     * ['','foo.jpg']       >  'foo.jpg'
+     * ['dir','0','a.jpg']  >  'dir/0/a.jpg'
+     *
+     * @param string ...$paths
+     */
+    public static function join(string|int ...$paths): string
+    {
+        // Cast args to string
+        $paths = array_map('strval', $paths);
+
+        $basePath = array_shift($paths);
+
+        return (string)(new Path($basePath))->append(...$paths);
+    }
+}

+ 2 - 2
tests/Unit/Service/Export/Encoder/DocXEncoderTest.php

@@ -63,7 +63,7 @@ class DocXEncoderTest extends TestCase
 
 
         $this->fileUtils
         $this->fileUtils
             ->expects(self::once())
             ->expects(self::once())
-            ->method('unlinkIfExist')
+            ->method('rmIfExist')
             ->with('tmp/temp.docx');
             ->with('tmp/temp.docx');
 
 
         $this->assertEquals(
         $this->assertEquals(
@@ -98,7 +98,7 @@ class DocXEncoderTest extends TestCase
 
 
         $this->fileUtils
         $this->fileUtils
             ->expects(self::once())
             ->expects(self::once())
-            ->method('unlinkIfExist')
+            ->method('rmIfExist')
             ->with('tmp/temp.docx');
             ->with('tmp/temp.docx');
 
 
         $this->expectException(\Exception::class);
         $this->expectException(\Exception::class);

+ 2 - 2
tests/Unit/Service/Export/Encoder/PdfEncoderTest.php

@@ -3,7 +3,7 @@
 namespace App\Tests\Unit\Service\Export\Encoder;
 namespace App\Tests\Unit\Service\Export\Encoder;
 
 
 use App\Service\Export\Encoder\PdfEncoder;
 use App\Service\Export\Encoder\PdfEncoder;
-use App\Service\Utils\Path;
+use App\Service\Utils\PathUtils;
 use Dompdf\Dompdf;
 use Dompdf\Dompdf;
 use Dompdf\Options;
 use Dompdf\Options;
 use PHPUnit\Framework\TestCase;
 use PHPUnit\Framework\TestCase;
@@ -53,7 +53,7 @@ class PdfEncoderTest extends TestCase
         $encoder->setDomPdf($domPdf);
         $encoder->setDomPdf($domPdf);
 
 
         $domPdfOptions->expects(self::once())->method('setIsRemoteEnabled')->with(true);
         $domPdfOptions->expects(self::once())->method('setIsRemoteEnabled')->with(true);
-        $domPdfOptions->expects(self::once())->method('setChroot')->with(Path::getProjectDir().'/public');
+        $domPdfOptions->expects(self::once())->method('setChroot')->with(PathUtils::getProjectDir().'/public');
         $domPdfOptions->expects(self::once())->method('setDefaultPaperOrientation')->with('portrait');
         $domPdfOptions->expects(self::once())->method('setDefaultPaperOrientation')->with('portrait');
         $domPdfOptions->expects(self::once())->method('setDefaultPaperSize')->with('A4');
         $domPdfOptions->expects(self::once())->method('setDefaultPaperSize')->with('A4');
         $domPdfOptions->expects(self::once())->method('set')->with(['additionalOption' => 2]);
         $domPdfOptions->expects(self::once())->method('set')->with(['additionalOption' => 2]);

+ 5 - 5
tests/Unit/Service/Twig/AssetsExtensionTest.php

@@ -4,7 +4,7 @@ namespace App\Tests\Unit\Service\Twig;
 
 
 use App\Service\File\FileManager;
 use App\Service\File\FileManager;
 use App\Service\Twig\AssetsExtension;
 use App\Service\Twig\AssetsExtension;
-use App\Service\Utils\Path;
+use App\Service\Utils\PathUtils;
 use PHPUnit\Framework\TestCase;
 use PHPUnit\Framework\TestCase;
 
 
 class AssetsExtensionTest extends TestCase
 class AssetsExtensionTest extends TestCase
@@ -40,20 +40,20 @@ class AssetsExtensionTest extends TestCase
             ->setMethodsExcept(['absPath'])
             ->setMethodsExcept(['absPath'])
             ->getMock();
             ->getMock();
 
 
-        $publicDir = Path::getProjectDir();
+        $publicDir = PathUtils::getProjectDir().'/public';
 
 
         $this->assertEquals(
         $this->assertEquals(
-            $publicDir.'/public/foo',
+            $publicDir.'/foo',
             $assetsExtension->absPath('foo')
             $assetsExtension->absPath('foo')
         );
         );
 
 
         $this->assertEquals(
         $this->assertEquals(
-            $publicDir.'/public/foo/bar',
+            $publicDir.'/foo/bar',
             $assetsExtension->absPath('foo/bar')
             $assetsExtension->absPath('foo/bar')
         );
         );
 
 
         $this->assertEquals(
         $this->assertEquals(
-            $publicDir.'/public',
+            $publicDir.'/',
             $assetsExtension->absPath('')
             $assetsExtension->absPath('')
         );
         );
     }
     }

+ 235 - 0
tests/Unit/Service/Utils/FileUtilsTest.php

@@ -0,0 +1,235 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Tests\Unit\Service\Utils;
+
+use App\Entity\Core\File;
+use App\Service\Utils\FileUtils;
+use Path\Path;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\FileLocator;
+
+class TestableFileUtils extends FileUtils {
+    public function makePath(string $path): Path
+    {
+        return parent::makePath($path);
+    }
+
+    public function makeFileLocator(array $directories): FileLocator
+    {
+        return parent::makeFileLocator($directories);
+    }
+}
+
+class FileUtilsTest extends TestCase
+{
+    private function getMockFor(string $methodName): TestableFileUtils|MockObject
+    {
+        return $this
+            ->getMockBuilder(TestableFileUtils::class)
+            ->setMethodsExcept([$methodName])
+            ->getMock();
+    }
+
+    public function testGetMimeTypeFromExt() {
+        $fileUtils = $this->getMockFor('getMimeTypeFromExt');
+
+        $this->assertEquals(
+            'image/jpeg',
+            $fileUtils->getMimeTypeFromExt('jpg')
+        );
+
+        $this->assertEquals(
+            'image/png',
+            $fileUtils->getMimeTypeFromExt('.png')
+        );
+
+        $this->assertEquals(
+            null,
+            $fileUtils->getMimeTypeFromExt('.unknown')
+        );
+    }
+
+    public function testGetMimeTypeFromFilename() {
+        $fileUtils = $this->getMockFor('guessMimeTypeFromFilename');
+
+        $fileUtils->method('getMimeTypeFromExt')->with('png')->willReturn('image/png');
+
+        $this->assertEquals(
+            'image/png',
+            $fileUtils->guessMimeTypeFromFilename('my_file.png')
+        );
+    }
+
+    public function testIsImage() {
+        $fileUtils = $this->getMockFor('isImage');
+
+        // has a registered mimetype
+        $file1 = $this->getMockBuilder(File::class)->getMock();
+        $file1->method('getMimeType')->willReturn('image/png');
+
+        // has no registered mimetype
+        $file2 = $this->getMockBuilder(File::class)->getMock();
+        $file2->method('getMimeType')->willReturn(null);
+        $file2->method('getName')->willReturn('my_file.png');
+
+        // has no registered mimetype and is not an image
+        $file3 = $this->getMockBuilder(File::class)->getMock();
+        $file3->method('getMimeType')->willReturn(null);
+        $file3->method('getName')->willReturn('my_file.pdf');
+
+        $fileUtils->method('guessMimeTypeFromFilename')->willReturnMap([
+            ['my_file.png', 'image/png'],
+            ['my_file.pdf', 'document/pdf'],
+        ]);
+
+        $this->assertTrue($fileUtils->isImage($file1));
+        $this->assertTrue($fileUtils->isImage($file2));
+        $this->assertFalse($fileUtils->isImage($file3));
+    }
+
+    public function testGetTempFilename() {
+        $fileUtils = $this->getMockFor('getTempFilename');
+
+        $tempDirPath = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $tempDirPath->expects($this->once())->method('mkdir')->with(511, true);
+
+        $fileUtils->method('makePath')->willReturn($tempDirPath);
+
+        $this->assertMatchesRegularExpression(
+            '/^.*\/var\/tmp\/\w{13}\.jpg$/',
+            $fileUtils->getTempFilename('jpg')
+        );
+    }
+
+    public function testGetTempFilenameWithPrefix() {
+        $fileUtils = $this->getMockFor('getTempFilename');
+
+        $tempDirPath = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $tempDirPath->expects($this->once())->method('mkdir')->with(511, true);
+
+        $fileUtils->method('makePath')->willReturn($tempDirPath);
+
+        $this->assertMatchesRegularExpression(
+            '/^.*\/var\/tmp\/foo\w{13}\.jpg$/',
+            $fileUtils->getTempFilename('jpg', 'foo')
+        );
+    }
+
+    public function testRmIfExist() {
+        $fileUtils = $this->getMockFor('rmIfExist');
+
+        $myPath = '/path/to/something';
+
+        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $fileUtils->method('makePath')->with($myPath)->willReturn($path);
+
+        $path->expects($this->once())->method('remove_p');
+
+        $fileUtils->rmIfExist($myPath);
+    }
+
+    public function testGetFileContent() {
+        $fileUtils = $this->getMockFor('getFileContent');
+
+        $myPath = '/path/to/something';
+
+        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $fileUtils->method('makePath')->with($myPath)->willReturn($path);
+
+        $path->expects($this->once())->method('getContent')->willReturn('my content');
+
+        $this->assertEquals('my content', $fileUtils->getFileContent($myPath));
+    }
+
+    public function testLocate() {
+        $fileUtils = $this->getMockFor('locate');
+
+        $fileLocator = $this->getMockBuilder(FileLocator::class)->disableOriginalConstructor()->getMock();
+
+        $fileLocator
+            ->expects($this->once())
+            ->method('locate')
+            ->with('my_file.pdf', null, false)
+            ->willReturn(['/my/dir/my_file.pdf']);
+
+        $fileUtils->method('makeFileLocator')->with(['/my/dir'])->willReturn($fileLocator);
+
+        $this->assertEquals(
+            '/my/dir/my_file.pdf',
+            $fileUtils->locate(['/my/dir'], 'my_file.pdf')
+        );
+    }
+
+    public function testLocateNotFound() {
+        $fileUtils = $this->getMockFor('locate');
+
+        $fileLocator = $this->getMockBuilder(FileLocator::class)->disableOriginalConstructor()->getMock();
+
+        $fileLocator
+            ->expects($this->once())
+            ->method('locate')
+            ->with('my_file.pdf', null, false)
+            ->willReturn([]);
+
+        $fileUtils->method('makeFileLocator')->with(['/my/dir'])->willReturn($fileLocator);
+
+        $this->assertEquals(
+            null,
+            $fileUtils->locate(['/my/dir'], 'my_file.pdf')
+        );
+    }
+
+    public function testList() {
+        $fileUtils = $this->getMockFor('list');
+
+        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+
+        $fileUtils->method('makePath')->with('/home')->willReturn($path);
+
+        $child1 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $child1->method('__toString')->willReturn('/home/my_file.pdf');
+
+        $child2 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $child2->method('__toString')->willReturn('/home/my_other_file.pdf');
+
+        $path
+            ->expects($this->once())
+            ->method('glob')
+            ->with('*')
+            ->willReturn([$child1, $child2]);
+
+        $this->assertEquals(
+            ['/home/my_file.pdf', '/home/my_other_file.pdf'],
+            $fileUtils->list('/home')
+        );
+    }
+
+    public function testListWithGlob() {
+        $fileUtils = $this->getMockFor('list');
+
+        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+
+        $fileUtils->method('makePath')->with('/home')->willReturn($path);
+
+        $child1 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $child1->method('__toString')->willReturn('/home/my_file.pdf');
+
+        $child2 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
+        $child2->method('__toString')->willReturn('/home/my_other_file.pdf');
+
+        $path
+            ->expects($this->once())
+            ->method('glob')
+            ->with('*.pdf')
+            ->willReturn([$child1, $child2]);
+
+        $this->assertEquals(
+            ['/home/my_file.pdf', '/home/my_other_file.pdf'],
+            $fileUtils->list('/home', '*.pdf')
+        );
+    }
+
+
+}

+ 0 - 33
tests/Unit/Service/Utils/PathTest.php

@@ -1,33 +0,0 @@
-<?php
-
-namespace App\Tests\Unit\Service\Utils;
-
-use App\Service\Utils\Path;
-use PHPUnit\Framework\TestCase;
-
-class PathTest extends TestCase
-{
-    /**
-     * @see Path::getProjectDir()
-     */
-    public function testGetProjectDir(): void
-    {
-        $this->assertFileExists(Path::getProjectDir().'/phpunit.xml.dist');
-    }
-
-    /**
-     * @see Path::join()
-     */
-    public function testJoin(): void
-    {
-        $this->assertEquals('', Path::join('', ''));
-        $this->assertEquals('/', Path::join('', '/'));
-        $this->assertEquals('/a', Path::join('/', 'a'));
-        $this->assertEquals('/a', Path::join('/', '/a'));
-        $this->assertEquals('abc/def', Path::join('abc', 'def'));
-        $this->assertEquals('abc/def', Path::join('abc', '/def'));
-        $this->assertEquals('/abc/def', Path::join('/abc', 'def'));
-        $this->assertEquals('foo.jpg', Path::join('', 'foo.jpg'));
-        $this->assertEquals('dir/0/a.jpg', Path::join('dir', '0', 'a.jpg'));
-    }
-}

+ 33 - 0
tests/Unit/Service/Utils/PathUtilsTest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Tests\Unit\Service\Utils;
+
+use App\Service\Utils\PathUtils;
+use PHPUnit\Framework\TestCase;
+
+class PathUtilsTest extends TestCase
+{
+    /**
+     * @see PathUtils::getProjectDir()
+     */
+    public function testGetProjectDir(): void
+    {
+        $this->assertFileExists(PathUtils::getProjectDir().'/phpunit.xml.dist');
+    }
+
+    /**
+     * @see PathUtils::join()
+     */
+    public function testJoin(): void
+    {
+        $this->assertEquals('', PathUtils::join('', ''));
+        $this->assertEquals('/', PathUtils::join('', '/'));
+        $this->assertEquals('/a', PathUtils::join('/', 'a'));
+        $this->assertEquals('/a', PathUtils::join('/', '/a'));
+        $this->assertEquals('abc/def', PathUtils::join('abc', 'def'));
+        $this->assertEquals('/def', PathUtils::join('abc', '/def'));
+        $this->assertEquals('/abc/def', PathUtils::join('/abc', 'def'));
+        $this->assertEquals('foo.jpg', PathUtils::join('', 'foo.jpg'));
+        $this->assertEquals('dir/0/a.jpg', PathUtils::join('dir', '0', 'a.jpg'));
+    }
+}