浏览代码

Merge branch 'v8-5397_upload_requests' into develop

Olivier Massot 1 年之前
父节点
当前提交
d455371670
共有 30 个文件被更改,包括 570 次插入84 次删除
  1. 1 0
      config/opentalent/products.yaml
  2. 0 0
      icon.svg
  3. 229 0
      src/ApiResources/Core/File/UploadRequest.php
  4. 2 0
      src/Entity/Core/File.php
  5. 2 0
      src/Enum/Core/FileTypeEnum.php
  6. 1 1
      src/Service/Access/AdminAccessUtils.php
  7. 2 8
      src/Service/Export/BaseExporter.php
  8. 78 0
      src/Service/File/FileManager.php
  9. 5 0
      src/Service/File/Storage/ApiLegacyStorage.php
  10. 3 2
      src/Service/File/Storage/FileStorageInterface.php
  11. 10 3
      src/Service/File/Storage/LocalStorage.php
  12. 3 2
      src/State/Processor/Access/AdminAccessProcessor.php
  13. 77 0
      src/State/Processor/Core/UploadRequestProcessor.php
  14. 2 1
      src/State/Processor/EntityProcessor.php
  15. 2 1
      src/State/Processor/Export/LicenceCmf/ExportRequestProcessor.php
  16. 5 3
      src/State/Processor/Organization/SubdomainProcessor.php
  17. 3 2
      src/State/Provider/Access/AccessProfileProvider.php
  18. 4 3
      src/State/Provider/Access/AdminAccessProvider.php
  19. 2 2
      src/State/Provider/Core/DownloadRequestProvider.php
  20. 2 1
      src/State/Provider/Cotisation/CotisationProvider.php
  21. 2 1
      src/State/Provider/Dolibarr/DolibarrAccountProvider.php
  22. 2 1
      src/State/Provider/Enum/EnumProvider.php
  23. 2 1
      src/State/Provider/Mobyt/MobytUserStatusProvider.php
  24. 3 3
      src/State/Provider/Organization/Subdomain/SubdomainAvailabilityProvider.php
  25. 2 1
      src/State/Provider/Utils/SiretProvider.php
  26. 16 16
      tests/Unit/Service/Export/BaseExporterTest.php
  27. 1 5
      tests/Unit/Service/Export/LicenceCmfExporterTest.php
  28. 65 3
      tests/Unit/Service/File/FileManagerTest.php
  29. 22 2
      tests/Unit/Service/File/Storage/ApiLegacyStorageTest.php
  30. 22 22
      tests/Unit/Service/File/Storage/LocalStorageTest.php

+ 1 - 0
config/opentalent/products.yaml

@@ -15,6 +15,7 @@ parameters:
           - Enum
           - LicenceCmfOrganizationER
           - DownloadRequest
+          - UploadRequest
           - SubdomainAvailability
         roles:
           - ROLE_IMPORT

文件差异内容过多而无法显示
+ 0 - 0
icon.svg


+ 229 - 0
src/ApiResources/Core/File/UploadRequest.php

@@ -0,0 +1,229 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\ApiResources\Core\File;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use App\Enum\Core\FileTypeEnum;
+use App\State\Processor\Core\UploadRequestProcessor;
+use App\State\Provider\Core\DownloadRequestProvider;
+use Ramsey\Uuid\Uuid;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * A request to upload the given content to a File
+ */
+#[ApiResource(
+    operations: [
+        new Post(
+            uriTemplate: '/upload',
+            requirements: ['id' => '\\d+'],
+            security: 'is_granted("ROLE_FILE")',
+            processor: UploadRequestProcessor::class
+        )
+    ]
+)]
+class UploadRequest
+{
+    /**
+     * @var int | null
+     */
+    #[ApiProperty(identifier: true)]
+    private ?int $fileId = null;
+
+    /**
+     * Le nom du fichier
+     * @var string
+     */
+    private string $filename;
+
+    /**
+     * Le contenu du fichier uploadé encodé au format Base64
+     * @var string
+     */
+    private string $content;
+
+    /**
+     * Si vrai, le propriétaire du fichier ne sera pas l'utilisateur en cours, mais l'organisation à laquelle il
+     * appartient.
+     * @var bool
+     */
+    private bool $organizationOwned = false;
+
+    /**
+     * Le type de fichier uploadé, si connu
+     * @var string
+     */
+    #[Assert\Choice(callback: [FileTypeEnum::class, 'toArray'])]
+    private string $type = "NONE";
+
+    /**
+     * Visibilité du fichier
+     * @var string
+     */
+    private string $visibility = 'NOBODY';
+
+    /**
+     * Type mime (il sera déduit automatiquement du nom du fichier s'il n'est pas fourni ici)
+     * @var string|null
+     */
+    private ?string $mimeType = null;
+
+    /**
+     * Configuration du fichier (par exemple le cropping d'une image)
+     * @var string
+     */
+    private ?string $config = null;
+
+    public function __construct()
+    {
+        $this->type = FileTypeEnum::NONE()->getValue();
+    }
+
+    /**
+     * @return int
+     */
+    public function getFileId() : int
+    {
+        return $this->fileId;
+    }
+
+    /**
+     * @param int $id
+     * @return self
+     */
+    public function setFileId(int $id) : self
+    {
+        $this->fileId = $id;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getContent(): string
+    {
+        return $this->content;
+    }
+
+    /**
+     * @param string $content
+     * @return void
+     */
+    public function setContent(string $content): void
+    {
+        $this->content = $content;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isOrganizationOwned(): bool
+    {
+        return $this->organizationOwned;
+    }
+
+    /**
+     * @param bool $organizationOwned
+     * @return self
+     */
+    public function setOrganizationOwned(bool $organizationOwned): self
+    {
+        $this->organizationOwned = $organizationOwned;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFilename(): string
+    {
+        return $this->filename;
+    }
+
+    /**
+     * @param string $filename
+     * @return self
+     */
+    public function setFilename(string $filename): self
+    {
+        $this->filename = $filename;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type;
+    }
+
+    /**
+     * @param string $type
+     * @return self
+     */
+    public function setType(string $type): self
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getVisibility(): string
+    {
+        return $this->visibility;
+    }
+
+    /**
+     * @param string $visibility
+     * @return self
+     */
+    public function setVisibility(string $visibility): self
+    {
+        $this->visibility = $visibility;
+        return $this;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getMimeType(): ?string
+    {
+        return $this->mimeType;
+    }
+
+    /**
+     * @param string|null $mimeType
+     * @return self
+     */
+    public function setMimeType(?string $mimeType): self
+    {
+        $this->mimeType = $mimeType;
+        return $this;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getConfig(): ?string
+    {
+        return $this->config;
+    }
+
+    /**
+     * @param string|null $config
+     * @return self
+     */
+    public function setConfig(?string $config): self
+    {
+        $this->config = $config;
+        return $this;
+    }
+}

+ 2 - 0
src/Entity/Core/File.php

@@ -18,6 +18,7 @@ use App\Entity\Organization\OnlineRegistrationSettings;
 use App\Entity\Organization\Organization;
 use App\Entity\Person\Person;
 use App\Enum\Core\FileHostEnum;
+use App\Enum\Core\FileTypeEnum;
 use App\Repository\Core\FileRepository;
 use App\Entity\Organization\Parameters;
 use DateTime;
@@ -117,6 +118,7 @@ class File
      * @var string
      */
     #[ORM\Column(length: 50, options: ['default' => 'NONE'])]
+    #[Assert\Choice(callback: [FileTypeEnum::class, 'toArray'])]
     private string $type = "NONE";
 
     /**

+ 2 - 0
src/Enum/Core/FileTypeEnum.php

@@ -9,6 +9,7 @@ use MyCLabs\Enum\Enum;
  * @method static NONE()
  * @method static LICENCE_CMF()
  * @method static BILL()
+ * @method static UPLOADED()
  */
 class FileTypeEnum extends Enum
 {
@@ -16,4 +17,5 @@ class FileTypeEnum extends Enum
     private const NONE = 'NONE';
     private const LICENCE_CMF ='LICENCE_CMF';
     private const BILL ='BILL';
+    private const UPLOADED ='UPLOADED';
 }

+ 1 - 1
src/Service/Access/AdminAccessUtils.php

@@ -40,9 +40,9 @@ class AdminAccessUtils
         $adminAccess
             ->setOrganizationId($organization->getId())
             ->setId($administrator->getId())
+            ->setOrganizationId($organization->getId())
             ->setUsername($administrator->getPerson()->getUserIdentifier())
             ->setEmail($contactPoint->getEmail())
-            ->setOrganizationId($organization->getId())
         ;
         return $adminAccess;
     }

+ 2 - 8
src/Service/Export/BaseExporter.php

@@ -10,7 +10,6 @@ use App\Repository\Access\AccessRepository;
 use App\Repository\Core\FileRepository;
 use App\Service\Export\Model\ExportModelInterface;
 use App\Service\File\FileManager;
-use App\Service\File\Storage\LocalStorage;
 use App\Service\ServiceIterator\EncoderIterator;
 use App\Service\Utils\StringsUtils;
 use Doctrine\ORM\EntityManagerInterface;
@@ -30,7 +29,6 @@ abstract class BaseExporter
     protected EncoderIterator $encoderIterator;
     protected EntityManagerInterface $entityManager;
     protected LoggerInterface $logger;
-    private LocalStorage $localStorage;
     protected FileManager $fileManager;
 
     #[Required]
@@ -49,9 +47,6 @@ abstract class BaseExporter
     public function setEntityManager(EntityManagerInterface $entityManager): void
     { $this->entityManager = $entityManager; }
     #[Required]
-    public function setLocalStorage(LocalStorage $localStorage): void
-    { $this->localStorage = $localStorage; }
-    #[Required]
     public function setFileManager(FileManager $fileManager): void
     { $this->fileManager = $fileManager; }
     #[Required]
@@ -101,8 +96,7 @@ abstract class BaseExporter
             $file = $this->prepareFile($exportRequest, false);
         }
 
-        // TODO: passer par le FileManager plutôt?
-        return $this->localStorage->writeFile($file, $content, $requester);
+        return $this->fileManager->write($file, $content, $requester);
     }
 
     /**
@@ -126,7 +120,7 @@ abstract class BaseExporter
             $filename .= '.' . $exportRequest->getFormat();
         }
 
-        return $this->localStorage->prepareFile(
+        return $this->fileManager->prepareFile(
             $requester,
             $filename,
             $this->getFileType(),

+ 78 - 0
src/Service/File/FileManager.php

@@ -7,8 +7,12 @@ use ApiPlatform\Api\IriConverterInterface;
 use ApiPlatform\Api\UrlGeneratorInterface;
 use ApiPlatform\Metadata\Get;
 use App\ApiResources\Core\File\DownloadRequest;
+use App\Entity\Access\Access;
 use App\Entity\Core\File;
+use App\Entity\Organization\Organization;
+use App\Entity\Person\Person;
 use App\Enum\Core\FileHostEnum;
+use App\Enum\Core\FileTypeEnum;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\Storage\ApiLegacyStorage;
 use App\Service\File\Storage\FileStorageInterface;
@@ -60,6 +64,80 @@ class FileManager
         return $storage->read($file);
     }
 
+    /**
+     * Prepare a File record with a PENDING status.
+     * This record will hold all the data needed to create the file, except its content.
+     *
+     * @param Organization|Access|Person $owner Owner of the file, either an organization, a person or both (access)
+     * @param string $filename The file's name (mandatory)
+     * @param FileTypeEnum $type The type of the new file
+     * @param Access $createdBy Id of the access responsible for this creation
+     * @param bool $isTemporary Is it a temporary file that can be deleted after some time
+     * @param string|null $mimeType Mimetype of the file, if not provided, the method will try to guess it from its file name's extension
+     * @param string $visibility
+     * @param bool $flushFile Should the newly created file be flushed after having been persisted?
+     * @return File
+     */
+    public function prepareFile(
+        Organization | Access | Person $owner,
+        string $filename,
+        FileTypeEnum $type,
+        Access $createdBy,
+        bool $isTemporary = false,
+        string $visibility = 'NOBODY',
+        string $mimeType = null,
+        bool $flushFile = true
+    ): File
+    {
+        return $this
+            ->localStorage
+            ->prepareFile($owner, $filename, $type, $createdBy, $isTemporary, $visibility, $mimeType, $flushFile);
+    }
+
+    /**
+     * Write the $content into the file storage and update the given File object's size, slug, status (READY)...
+     *
+     * @param File $file
+     * @param string $content
+     * @param Access $author
+     * @return File
+     * @throws FileNotFoundException
+     */
+    public function write(File $file, string $content, Access $author): File {
+        return $this
+            ->localStorage
+            ->write($file, $content, $author);
+    }
+
+    /**
+     * Convenient method to successively prepare and write a file
+     *
+     * @param Organization|Access|Person $owner
+     * @param string $filename
+     * @param FileTypeEnum $type
+     * @param string $content
+     * @param Access $author
+     * @param bool $isTemporary
+     * @param string|null $mimeType
+     * @param string $visibility
+     * @param string|null $config
+     * @return File
+     */
+    public function makeFile (
+        Organization | Access | Person $owner,
+        string                         $filename,
+        FileTypeEnum                   $type,
+        string                         $content,
+        Access                         $author,
+        bool                           $isTemporary = false,
+        string                         $visibility = 'NOBODY',
+        string                         $mimeType = null,
+        string                         $config = null
+    ): File {
+        return $this
+            ->localStorage
+            ->makeFile($owner, $filename, $type, $content, $author, $isTemporary, $visibility, $mimeType);
+    }
 
     /**
      * Get the IRI to download this file (eg: /api/download/1)

+ 5 - 0
src/Service/File/Storage/ApiLegacyStorage.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 namespace App\Service\File\Storage;
 
+use App\Entity\Access\Access;
 use App\Entity\Core\File;
 use App\Service\ApiLegacy\ApiLegacyRequestService;
 use App\Service\Utils\UrlBuilder;
@@ -20,6 +21,10 @@ class ApiLegacyStorage implements FileStorageInterface
     public function __construct(private ApiLegacyRequestService $apiLegacyRequestService)
     {}
 
+    public function exists(File $file): bool {
+        throw new \RuntimeException('not implemented error');
+    }
+
     /**
      * Reads the given file and returns its content as a string
      *

+ 3 - 2
src/Service/File/Storage/FileStorageInterface.php

@@ -3,11 +3,12 @@ declare(strict_types=1);
 
 namespace App\Service\File\Storage;
 
+use App\Entity\Access\Access;
 use App\Entity\Core\File;
 
 interface FileStorageInterface
 {
-    public function read(File $file): string;
+    public function exists(File $file): bool;
 
-    // TODO : complete with 'exists', 'write', and 'delete'
+    public function read(File $file): string;
 }

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

@@ -136,7 +136,7 @@ class LocalStorage implements FileStorageInterface
      * @param Access $author The access responsible for the creation / update of the file
      * @return File
      */
-    public function writeFile(File $file, string $content, Access $author): File
+    public function write(File $file, string $content, Access $author): File
     {
         if (empty($file->getName())) {
             throw new RuntimeException('File has no filename');
@@ -199,6 +199,7 @@ class LocalStorage implements FileStorageInterface
      * @param bool $isTemporary
      * @param string|null $mimeType
      * @param string $visibility
+     * @param string|null $config
      * @return File
      */
     public function makeFile (
@@ -209,7 +210,8 @@ class LocalStorage implements FileStorageInterface
         Access                         $author,
         bool                           $isTemporary = false,
         string                         $visibility = 'NOBODY',
-        string                         $mimeType = null
+        string                         $mimeType = null,
+        string                         $config = null
     ): File
     {
         $file = $this->prepareFile(
@@ -223,7 +225,12 @@ class LocalStorage implements FileStorageInterface
             false
         );
 
-        return $this->writeFile($file, $content, $author);
+        if (!empty($config)) {
+            // TODO: Déplacer dans le prepareFile?
+            $file->setConfig($config);
+        }
+
+        return $this->write($file, $content, $author);
     }
 
     /**

+ 3 - 2
src/State/Processor/Access/AdminAccessProcessor.php

@@ -6,6 +6,7 @@ namespace App\State\Processor\Access;
 use ApiPlatform\Metadata\Delete;
 use ApiPlatform\State\ProcessorInterface;
 use App\Entity\Access\Access;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\Access\Utils as AccessUtils;
 use App\Service\Core\ContactPointUtils;
 use Doctrine\ORM\EntityManagerInterface;
@@ -36,7 +37,7 @@ class AdminAccessProcessor implements ProcessorInterface
     public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
     {
         if($operation instanceof Delete){
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         /** @var Access $access */
@@ -56,4 +57,4 @@ class AdminAccessProcessor implements ProcessorInterface
         $this->entityManager->persist($contactPoint);
         $this->entityManager->flush();
     }
-}
+}

+ 77 - 0
src/State/Processor/Core/UploadRequestProcessor.php

@@ -0,0 +1,77 @@
+<?php
+declare(strict_types=1);
+
+namespace App\State\Processor\Core;
+
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Operation;
+use ApiPlatform\State\ProcessorInterface;
+use ApiPlatform\State\ProviderInterface;
+use App\ApiResources\Core\File\DownloadRequest;
+use App\ApiResources\Core\File\UploadRequest;
+use App\Entity\Access\Access;
+use App\Enum\Core\FileStatusEnum;
+use App\Enum\Core\FileTypeEnum;
+use Symfony\Component\HttpFoundation\Response;
+use App\Repository\Core\FileRepository;
+use App\Service\File\Exception\FileNotFoundException;
+use App\Service\File\FileManager;
+use RuntimeException;
+use Symfony\Bundle\SecurityBundle\Security;
+use Symfony\Component\HttpFoundation\HeaderUtils;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Custom provider pour le téléchargement des fichiers du LocalStorage
+ */
+final class UploadRequestProcessor implements ProcessorInterface
+{
+    public function __construct(
+        private Security                $security,
+        private readonly FileManager    $fileManager,
+    ) {}
+
+    /**
+     * @param Operation $operation
+     * @param array<mixed> $uriVariables
+     * @param array<mixed> $context
+     * @return UploadRequest
+     * @throws FileNotFoundException
+     */
+    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): UploadRequest
+    {
+        if ($operation instanceof Delete){
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
+        }
+
+        /** @var Access $author */
+        $author = $this->security->getUser();
+
+        /** @var UploadRequest $uploadRequest */
+        $uploadRequest = $data;
+
+        if (empty($uploadRequest->getFilename())) {
+            throw new \RuntimeException("Missing filename", Response::HTTP_BAD_REQUEST);
+        }
+        if (empty($uploadRequest->getContent())) {
+            throw new \RuntimeException("Provided content is empty", Response::HTTP_BAD_REQUEST);
+        }
+
+        $file = $this->fileManager->makeFile(
+            $uploadRequest->isOrganizationOwned() ? $author->getOrganization() : $author,
+            $uploadRequest->getFilename(),
+            FileTypeEnum::from($uploadRequest->getType()),
+            base64_decode($uploadRequest->getContent()),
+            $author,
+            false,
+            $uploadRequest->getVisibility(),
+            $uploadRequest->getMimeType(),
+            $uploadRequest->getConfig()
+        );
+
+        $uploadRequest->setFileId($file->getId());
+
+        return $uploadRequest;
+    }
+}

+ 2 - 1
src/State/Processor/EntityProcessor.php

@@ -5,6 +5,7 @@ namespace App\State\Processor;
 use ApiPlatform\Metadata\Delete;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProcessorInterface;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\OnChange\OnChangeContext;
 use App\Service\OnChange\OnChangeDefault;
 use App\Service\OnChange\OnChangeInterface;
@@ -42,7 +43,7 @@ class EntityProcessor implements ProcessorInterface
     public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): object
     {
         if($operation instanceof Delete){
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         $onChangeContext = new OnChangeContext($context);

+ 2 - 1
src/State/Processor/Export/LicenceCmf/ExportRequestProcessor.php

@@ -9,6 +9,7 @@ use ApiPlatform\State\ProcessorInterface;
 use App\ApiResources\Export\ExportRequest;
 use App\Entity\Access\Access;
 use App\Entity\Core\File;
+use Symfony\Component\HttpFoundation\Response;
 use App\Message\Command\Export;
 use App\Service\ServiceIterator\ExporterIterator;
 use Exception;
@@ -35,7 +36,7 @@ class ExportRequestProcessor implements ProcessorInterface
     public function process(mixed $exportRequest, Operation $operation, array $uriVariables = [], array $context = []): ExportRequest
     {
         if($operation instanceof Delete){
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         /** @var Access $access */

+ 5 - 3
src/State/Processor/Organization/SubdomainProcessor.php

@@ -10,6 +10,7 @@ use ApiPlatform\Metadata\Put;
 use ApiPlatform\State\ProcessorInterface;
 use App\Entity\Access\Access;
 use App\Entity\Organization\Subdomain;
+use Symfony\Component\HttpFoundation\Response;
 use App\Repository\Organization\SubdomainRepository;
 use App\Service\Typo3\SubdomainService;
 use Doctrine\ORM\EntityManagerInterface;
@@ -36,14 +37,15 @@ class SubdomainProcessor implements ProcessorInterface
      */
     public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) {
         if($operation instanceof Delete){
-            throw new \RuntimeException('not supported', 500);
+            throw new \RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         /** @var Access $access */
         $access = $this->security->getUser();
+
         if ($data->getOrganization()->getId() !== $access->getOrganization()->getId()) {
             // TODO: voir à déplacer dans un voter?
-            throw new \RuntimeException('forbidden', 500);
+            throw new \RuntimeException('forbidden', Response::HTTP_FORBIDDEN);
         }
 
         if ($operation instanceof Post) {
@@ -58,7 +60,7 @@ class SubdomainProcessor implements ProcessorInterface
             $data->setActive(false); // On triche : c'est le service qui va activer ce sous-domaine, pas le processor
             $subdomain = $this->subdomainService->activateSubdomain($data);
         } else {
-            throw new \RuntimeException('not supported', 500);
+            throw new \RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         return $subdomain;

+ 3 - 2
src/State/Provider/Access/AccessProfileProvider.php

@@ -12,6 +12,7 @@ use App\Entity\Access\Access;
 use App\Service\Access\AccessProfileCreator;
 use Exception;
 use RuntimeException;
+use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
 use Symfony\Bundle\SecurityBundle\Security;
 
@@ -37,7 +38,7 @@ final class AccessProfileProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?AccessProfile
     {
         if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         /** @var Access $access */
@@ -53,4 +54,4 @@ final class AccessProfileProvider implements ProviderInterface
 
         return $this->accessProfileCreator->getAccessProfile($access, $originalAccess);
     }
-}
+}

+ 4 - 3
src/State/Provider/Access/AdminAccessProvider.php

@@ -9,6 +9,7 @@ use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Access\AdminAccess;
 use App\Entity\Access\Access;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\Access\AdminAccessUtils;
 use Exception;
 use RuntimeException;
@@ -35,12 +36,12 @@ final class AdminAccessProvider implements ProviderInterface
      */
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?AdminAccess
     {
-        if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+        if ($operation instanceof GetCollection) {
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         /** @var Access $access */
         $access = $this->security->getUser();
         return $this->adminAccessUtils->getAdminAccess($access->getOrganization());
     }
-}
+}

+ 2 - 2
src/State/Provider/Core/DownloadRequestProvider.php

@@ -8,13 +8,13 @@ use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Core\File\DownloadRequest;
 use App\Enum\Core\FileStatusEnum;
+use Symfony\Component\HttpFoundation\Response;
 use App\Repository\Core\FileRepository;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\FileManager;
 use RuntimeException;
 use Symfony\Component\HttpFoundation\HeaderUtils;
 use Symfony\Component\HttpFoundation\RedirectResponse;
-use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Custom provider pour le téléchargement des fichiers du LocalStorage
@@ -36,7 +36,7 @@ final class DownloadRequestProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response | RedirectResponse
     {
         if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         return $this->serveFile($uriVariables['fileId']);

+ 2 - 1
src/State/Provider/Cotisation/CotisationProvider.php

@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Cotisation\Cotisation;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\Cotisation\CotisationCreator;
 use RuntimeException;
 
@@ -29,7 +30,7 @@ final class CotisationProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Cotisation
     {
         if ($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         return $this->cotisationCreator->getCotisation($uriVariables['organizationId']);

+ 2 - 1
src/State/Provider/Dolibarr/DolibarrAccountProvider.php

@@ -7,6 +7,7 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Dolibarr\DolibarrAccount;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\ApiResourceBuilder\Dolibarr\DolibarrAccountBuilder;
 use RuntimeException;
 
@@ -31,7 +32,7 @@ final class DolibarrAccountProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?DolibarrAccount
     {
         if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         return $this->dolibarrAccountCreator->getDolibarrAccount($uriVariables['organizationId']);

+ 2 - 1
src/State/Provider/Enum/EnumProvider.php

@@ -7,6 +7,7 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Enum\Enum;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\Utils\Parser\YamlParser;
 use App\Service\Utils\Reflection;
 use Exception;
@@ -35,7 +36,7 @@ final class EnumProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Enum
     {
         if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         $name = $uriVariables['name'];

+ 2 - 1
src/State/Provider/Mobyt/MobytUserStatusProvider.php

@@ -7,6 +7,7 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Mobyt\MobytUserStatus;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\ApiResourceBuilder\Mobyt\MobytUserStatusBuilder;
 use JsonException;
 use RuntimeException;
@@ -32,7 +33,7 @@ final class MobytUserStatusProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?MobytUserStatus
     {
         if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         return $this->mobytUserStatusCreator->getUserStatus((int) $uriVariables['organizationId']);

+ 3 - 3
src/State/Provider/Organization/Subdomain/SubdomainAvailabilityProvider.php

@@ -6,10 +6,10 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Organization\Subdomain\SubdomainAvailability;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\Typo3\SubdomainService;
 use Symfony\Component\HttpFoundation\RedirectResponse;
-use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Custom provider permettant de tester la disponibilité d'un sous-domaine
@@ -30,13 +30,13 @@ final class SubdomainAvailabilityProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?SubdomainAvailability
     {
         if ($operation instanceof GetCollection) {
-            throw new \RuntimeException('not supported', 500);
+            throw new \RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         $filters = $context['filters'] ?? [];
         $subdomain = $filters['subdomain'] ?? null;
         if ($subdomain === null) {
-            throw new \RuntimeException('missing parameter: subdomain', 500);
+            throw new \RuntimeException('missing parameter: subdomain', Response::HTTP_BAD_REQUEST);
         }
 
         $available =

+ 2 - 1
src/State/Provider/Utils/SiretProvider.php

@@ -7,6 +7,7 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Utils\Siret;
+use Symfony\Component\HttpFoundation\Response;
 use App\Service\Utils\Siret as SiretUtils;
 use RuntimeException;
 use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
@@ -38,7 +39,7 @@ final class SiretProvider implements ProviderInterface
     public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Siret
     {
         if($operation instanceof GetCollection) {
-            throw new RuntimeException('not supported', 500);
+            throw new RuntimeException('not supported', Response::HTTP_METHOD_NOT_ALLOWED);
         }
 
         $id = $uriVariables['id'];

+ 16 - 16
tests/Unit/Service/Export/BaseExporterTest.php

@@ -12,7 +12,7 @@ use App\Repository\Core\FileRepository;
 use App\Service\Export\BaseExporter;
 use App\Service\Export\Encoder\EncoderInterface;
 use App\Service\Export\Model\ExportModelInterface;
-use App\Service\File\Storage\LocalStorage;
+use App\Service\File\FileManager;
 use App\Service\ServiceIterator\EncoderIterator;
 use Doctrine\ORM\EntityManagerInterface;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -34,13 +34,13 @@ class TestableBaseExporter extends BaseExporter {
 
 class BaseExporterTest extends TestCase
 {
-    private AccessRepository $accessRepository;
-    private FileRepository $fileRepository;
-    private Environment $twig;
-    private EncoderIterator $encoderIterator;
-    private EntityManagerInterface $entityManager;
-    private LocalStorage $localStorage;
-    private LoggerInterface $logger;
+    private MockObject | AccessRepository $accessRepository;
+    private MockObject | FileRepository $fileRepository;
+    private MockObject | Environment $twig;
+    private MockObject | EncoderIterator $encoderIterator;
+    private MockObject | EntityManagerInterface $entityManager;
+    private MockObject | LoggerInterface $logger;
+    private MockObject | FileManager $fileManager;
 
     public function setUp(): void {
         $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
@@ -48,15 +48,15 @@ class BaseExporterTest extends TestCase
         $this->twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
         $this->encoderIterator = $this->getMockBuilder(EncoderIterator::class)->disableOriginalConstructor()->getMock();
         $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
-        $this->localStorage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock();
         $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
+        $this->fileManager = $this->getMockBuilder(FileManager::class)->disableOriginalConstructor()->getMock();
     }
 
     private function getBaseExporterMockFor(string $method): MockObject | TestableBaseExporter
     {
         $exporter = $this->getMockBuilder(TestableBaseExporter::class)
             ->setMethodsExcept([$method, 'setAccessRepository', 'setFileRepository', 'setTwig', 'setEncoderIterator',
-                'setEntityManager', 'setLocalStorage', 'setLogger'])
+                'setEntityManager', 'setFileManager', 'setLogger'])
             ->getMock();
 
         $exporter->setAccessRepository($this->accessRepository);
@@ -64,8 +64,8 @@ class BaseExporterTest extends TestCase
         $exporter->setTwig($this->twig);
         $exporter->setEncoderIterator($this->encoderIterator);
         $exporter->setEntityManager($this->entityManager);
-        $exporter->setLocalStorage($this->localStorage);
         $exporter->setLogger($this->logger);
+        $exporter->setFileManager($this->fileManager);
 
         return $exporter;
     }
@@ -106,7 +106,7 @@ class BaseExporterTest extends TestCase
         $file = $this->getMockBuilder(File::class)->disableOriginalConstructor()->getMock();
         $this->fileRepository->method('find')->with(456, null, null)->willReturn($file);
 
-        $this->localStorage->expects(self::once())->method('writeFile')->with($file, $data, $access);
+        $this->fileManager->expects(self::once())->method('write')->with($file, $data, $access);
 
         $exporter->export($exportRequest);
     }
@@ -138,7 +138,7 @@ class BaseExporterTest extends TestCase
         $file = $this->getMockBuilder(File::class)->disableOriginalConstructor()->getMock();
         $exporter->expects(self::once())->method('prepareFile')->willReturn($file);
 
-        $this->localStorage->expects(self::once())->method('writeFile')->with($file, $data, $access);
+        $this->fileManager->expects(self::once())->method('write')->with($file, $data, $access);
 
         $exporter->export($exportRequest);
     }
@@ -155,7 +155,7 @@ class BaseExporterTest extends TestCase
 
         $this->accessRepository->method('find')->with(123, null, null)->willReturn(null);
 
-        $this->localStorage->expects(self::never())->method('writeFile');
+        $this->fileManager->expects(self::never())->method('write');
 
         $this->expectException(\RuntimeException::class);
         $this->expectExceptionMessage('Unable to determine the user; abort.');
@@ -186,7 +186,7 @@ class BaseExporterTest extends TestCase
         $access->method('getOrganization')->willReturn(null);
         $this->accessRepository->method('find')->with(123, null, null)->willReturn($access);
 
-        $this->localStorage->expects(self::never())->method('writeFile');
+        $this->fileManager->expects(self::never())->method('write');
 
         $this->expectException(\RuntimeException::class);
         $this->expectExceptionMessage('Unable to determine the organization of the curent user; abort.');
@@ -212,7 +212,7 @@ class BaseExporterTest extends TestCase
 
         $file = $this->getMockBuilder(File::class)->disableOriginalConstructor()->getMock();
 
-        $this->localStorage->expects(self::once())
+        $this->fileManager->expects(self::once())
             ->method('prepareFile')
             ->with(
                 $access, 'Foo.pdf', FileTypeEnum::UNKNOWN(), $access, true, 'NOBODY', 'application/pdf', false

+ 1 - 5
tests/Unit/Service/Export/LicenceCmfExporterTest.php

@@ -17,7 +17,6 @@ use App\Service\Export\Encoder\PdfEncoder;
 use App\Service\Export\LicenceCmfExporter;
 use App\Service\Export\Model\LicenceCmf;
 use App\Service\File\FileManager;
-use App\Service\File\Storage\LocalStorage;
 use App\Service\ServiceIterator\EncoderIterator;
 use App\Tests\Unit\TestToolsTrait;
 use Doctrine\Common\Collections\Collection;
@@ -36,7 +35,6 @@ class LicenceCmfExporterTest extends TestCase
     private MockObject | Environment $twig;
     private MockObject | EncoderIterator $encoderIterator;
     private MockObject | EntityManagerInterface $em;
-    private MockObject | LocalStorage $localStorage;
     private MockObject | FileManager $fileManager;
     private MockObject | OrganizationRepository $organizationRepo;
     private MockObject | Access $access;
@@ -59,7 +57,6 @@ class LicenceCmfExporterTest extends TestCase
         $this->encoderIterator = $this->getMockBuilder(EncoderIterator::class)->disableOriginalConstructor()->getMock();
         $this->encoder = $this->getMockBuilder(PdfEncoder::class)->disableOriginalConstructor()->getMock();
         $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
-        $this->localStorage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock();
         $this->fileManager = $this->getMockBuilder(FileManager::class)->disableOriginalConstructor()->getMock();
         $this->organizationRepo = $this->getMockBuilder(OrganizationRepository::class)->disableOriginalConstructor()->getMock();
         $this->access = $this->getMockBuilder(Access::class)->getMock();
@@ -80,14 +77,13 @@ class LicenceCmfExporterTest extends TestCase
         $exporter = $this->getMockBuilder(LicenceCmfExporter::class)
             ->setConstructorArgs([$this->organizationRepo])
             ->setMethodsExcept(['setAccessRepository', 'setTwig', 'setEncoderIterator',
-                'setEntityManager', 'setEntityManager', 'setLocalStorage', 'setFileManager', $methodUnderTest])
+                'setEntityManager', 'setEntityManager', 'setFileManager', $methodUnderTest])
             ->getMock();
 
         $exporter->setAccessRepository($this->accessRepo);
         $exporter->setTwig($this->twig);
         $exporter->setEncoderIterator($this->encoderIterator);
         $exporter->setEntityManager($this->em);
-        $exporter->setLocalStorage($this->localStorage);
         $exporter->setFileManager($this->fileManager);
 
         return $exporter;

+ 65 - 3
tests/Unit/Service/File/FileManagerTest.php

@@ -6,20 +6,24 @@ use ApiPlatform\Api\IriConverterInterface;
 use ApiPlatform\Api\UrlGeneratorInterface;
 use ApiPlatform\Metadata\Get;
 use App\ApiResources\Core\File\DownloadRequest;
+use App\Entity\Access\Access;
 use App\Entity\Core\File;
+use App\Entity\Organization\Organization;
 use App\Enum\Core\FileHostEnum;
+use App\Enum\Core\FileTypeEnum;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\FileManager;
 use App\Service\File\Storage\ApiLegacyStorage;
 use App\Service\File\Storage\FileStorageInterface;
 use App\Service\File\Storage\LocalStorage;
+use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
 
 class FileManagerTest extends TestCase
 {
-    private LocalStorage $localStorage;
-    private ApiLegacyStorage $apiLegacyStorage;
-    private IriConverterInterface $iriConverter;
+    private MockObject | LocalStorage $localStorage;
+    private MockObject | ApiLegacyStorage $apiLegacyStorage;
+    private MockObject | IriConverterInterface $iriConverter;
 
     public function setUp(): void
     {
@@ -82,6 +86,64 @@ class FileManagerTest extends TestCase
         );
     }
 
+    public function testPrepareFile(): void
+    {
+        $fileManager = $this->getFileManagerMockFor('prepareFile');
+
+        $owner = $this->createMock(Organization::class);
+        $filename = "example_file.txt";
+        $fileType = FileTypeEnum::NONE(); // Update with your specific enum type
+        $createdBy = $this->createMock(Access::class);
+        $isTemporary = false;
+        $visibility = 'NOBODY';
+        $mimeType = null;
+        $flushFile = true;
+
+        $this->localStorage
+            ->expects(self::once())
+            ->method('prepareFile')
+            ->with($owner, $filename, $fileType, $createdBy, $isTemporary, $visibility, $mimeType, $flushFile);
+
+        $file = $fileManager->prepareFile($owner, $filename, $fileType, $createdBy, $isTemporary, $visibility, $mimeType, $flushFile);
+    }
+
+    public function testWrite(): void
+    {
+        $fileManager = $this->getFileManagerMockFor('write');
+
+        $file = $this->createMock(File::class);
+        $content = "Some content";
+        $author = $this->createMock(Access::class);
+
+        $this->localStorage
+            ->expects(self::once())
+            ->method('write')
+            ->with($file, $content, $author);
+
+        $result = $fileManager->write($file, $content, $author);
+    }
+
+    public function testMakeFile(): void
+    {
+        $fileManager = $this->getFileManagerMockFor('makeFile');
+
+        $owner = $this->createMock(Organization::class);
+        $filename = "example_file.txt";
+        $fileType = FileTypeEnum::NONE(); // Update with your specific enum type
+        $content = "Some content";
+        $author = $this->createMock(Access::class);
+        $isTemporary = false;
+        $visibility = 'NOBODY';
+        $mimeType = null;
+
+        $this->localStorage
+            ->expects(self::once())
+            ->method('makeFile')
+            ->with($owner, $filename, $fileType, $content, $author, $isTemporary, $visibility, $mimeType);
+
+        $file = $fileManager->makeFile($owner, $filename, $fileType, $content, $author, $isTemporary, $visibility, $mimeType);
+    }
+
     /**
      * @see LocalStorage::guessMimeTypeFromFilename()
      */

+ 22 - 2
tests/Unit/Service/File/Storage/ApiLegacyStorageTest.php

@@ -9,13 +9,33 @@ use PHPUnit\Framework\TestCase;
 
 class ApiLegacyStorageTest extends TestCase
 {
+    public function testExists(): void
+    {
+        $apiLegacyRequestService = $this->getMockBuilder(ApiLegacyRequestService::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $apiLegacyStorage = $this
+            ->getMockBuilder(ApiLegacyStorage::class)
+            ->setConstructorArgs([$apiLegacyRequestService])
+            ->setMethodsExcept(['exists'])
+            ->getMock();
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionMessage('not implemented error');
+
+        $file = $this->getMockBuilder(File::class)->getMock();
+
+        $apiLegacyStorage->exists($file);
+    }
+
     public function testRead(): void
     {
         $apiLegacyRequestService = $this->getMockBuilder(ApiLegacyRequestService::class)
             ->disableOriginalConstructor()
             ->getMock();
 
-        $apiLegacyStorageTest = $this
+        $apiLegacyStorage = $this
             ->getMockBuilder(ApiLegacyStorage::class)
             ->setConstructorArgs([$apiLegacyRequestService])
             ->setMethodsExcept(['read'])
@@ -30,7 +50,7 @@ class ApiLegacyStorageTest extends TestCase
             ->with('_internal/secure/files/123')
             ->willReturn('xyz');
 
-        $result = $apiLegacyStorageTest->read($file);
+        $result = $apiLegacyStorage->read($file);
 
         $this->assertEquals('xyz', $result);
     }

+ 22 - 22
tests/Unit/Service/File/Storage/LocalStorageTest.php

@@ -229,12 +229,12 @@ class LocalStorageTest extends TestCase
     }
 
     /**
-     * @see LocalStorage::writeFile()
+     * @see LocalStorage::write()
      */
-    public function testWriteFileNewFile(): void {
+    public function testWriteNewFile(): void {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['writeFile'])
+            ->setMethodsExcept(['write'])
             ->getMock();
 
         $organization = $this->getMockBuilder(Organization::class)->getMock();
@@ -277,18 +277,18 @@ class LocalStorageTest extends TestCase
 
         $this->entityManager->expects(self::once())->method('flush');
 
-        $returned = $fileStorage->writeFile($file, $content, $author);
+        $returned = $fileStorage->write($file, $content, $author);
 
-        $this->assertEquals($file, $returned);
+        $this->assertSame($file, $returned);
     }
 
     /**
-     * @see LocalStorage::writeFile()
+     * @see LocalStorage::write()
      */
-    public function testWriteFileExistingFile(): void {
+    public function testWriteExistingFile(): void {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['writeFile'])
+            ->setMethodsExcept(['write'])
             ->getMock();
 
         $person = $this->getMockBuilder(Person::class)->getMock();
@@ -329,19 +329,19 @@ class LocalStorageTest extends TestCase
 
         $this->entityManager->expects(self::once())->method('flush');
 
-        $returned = $fileStorage->writeFile($file, $content, $author);
+        $returned = $fileStorage->write($file, $content, $author);
 
         $this->assertEquals($file, $returned);
     }
 
     /**
-     * @see LocalStorage::writeFile()
+     * @see LocalStorage::write()
      */
-    public function testWriteFileExistingButMissingFile(): void
+    public function testWriteExistingButMissingFile(): void
     {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['writeFile'])
+            ->setMethodsExcept(['write'])
             ->getMock();
 
         $person = $this->getMockBuilder(Person::class)->getMock();
@@ -365,16 +365,16 @@ class LocalStorageTest extends TestCase
         $this->expectException(RuntimeException::class);
         $this->expectExceptionMessage('The file `' . $key . '` does not exist in the file storage');
 
-        $fileStorage->writeFile($file, '12346', $author);
+        $fileStorage->write($file, '12346', $author);
     }
 
     /**
-     * @see LocalStorage::writeFile()
+     * @see LocalStorage::write()
      */
-    public function testWriteFileWithAccessOwner(): void {
+    public function testWriteWithAccessOwner(): void {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['writeFile'])
+            ->setMethodsExcept(['write'])
             ->getMock();
 
         $access = $this->getMockBuilder(Access::class)->getMock();
@@ -407,17 +407,17 @@ class LocalStorageTest extends TestCase
         $content = '1';
         $this->filesystem->method('write')->willReturn(1);
 
-        $fileStorage->writeFile($file, $content, $author);
+        $fileStorage->write($file, $content, $author);
     }
 
     /**
-     * @see LocalStorage::writeFile()
+     * @see LocalStorage::write()
      */
-    public function testWriteFileWithNoName(): void
+    public function testWriteWithNoName(): void
     {
         $fileStorage = $this->getMockBuilder(TestableLocalStorage::class)
             ->setConstructorArgs([$this->filesystemMap, $this->entityManager, $this->accessRepository, $this->iriConverter])
-            ->setMethodsExcept(['writeFile'])
+            ->setMethodsExcept(['write'])
             ->getMock();
 
         $author = $this->getMockBuilder(Access::class)->getMock();
@@ -428,7 +428,7 @@ class LocalStorageTest extends TestCase
         $this->expectException(RuntimeException::class);
         $this->expectExceptionMessage('File has no filename');
 
-        $fileStorage->writeFile($file, '...', $author);
+        $fileStorage->write($file, '...', $author);
     }
 
     /**
@@ -452,7 +452,7 @@ class LocalStorageTest extends TestCase
 
         $fileStorage
             ->expects(self::once())
-            ->method('writeFile')
+            ->method('write')
             ->with($file, '...', $author)
             ->willReturn($file);
 

部分文件因为文件数量过多而无法显示