From 39b17a315573c98ee85a938d6904a5584c739800 Mon Sep 17 00:00:00 2001 From: XueSheng-GIT Date: Sun, 5 Jan 2025 07:53:24 +0100 Subject: [PATCH 1/2] Check if user can update node ...and add suffix '_OCR' to processed file if not. Signed-off-by: XueSheng-GIT --- lib/Helper/IProcessingFileAccessor.php | 10 ++++----- lib/Helper/ProcessingFileAccessor.php | 14 ++++++------- lib/Operation.php | 2 +- lib/Service/OcrService.php | 21 +++++++++++++------ .../Helper/ProcessingFIleAccessorTest.php | 6 +++--- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/Helper/IProcessingFileAccessor.php b/lib/Helper/IProcessingFileAccessor.php index 251a3a8..23c5b2d 100644 --- a/lib/Helper/IProcessingFileAccessor.php +++ b/lib/Helper/IProcessingFileAccessor.php @@ -25,15 +25,15 @@ interface IProcessingFileAccessor { /** - * Returns the id of the file which is currently + * Returns the path of the file which is currently * processed via OCR - * @return ?int + * @return ?string */ - public function getCurrentlyProcessedFileId() : ?int; + public function getCurrentlyProcessedFilePath() : ?string; /** - * Sets the id of the file which is currently + * Sets the path of the file which is currently * processed via OCR */ - public function setCurrentlyProcessedFileId(?int $fileId) : void; + public function setCurrentlyProcessedFilePath(?string $filePath) : void; } diff --git a/lib/Helper/ProcessingFileAccessor.php b/lib/Helper/ProcessingFileAccessor.php index 2ec1760..8b6a19f 100644 --- a/lib/Helper/ProcessingFileAccessor.php +++ b/lib/Helper/ProcessingFileAccessor.php @@ -24,15 +24,15 @@ namespace OCA\WorkflowOcr\Helper; /** - * This class is a singleton which holds the id + * This class is a singleton which holds the path * of the currently OCR processed file. This ensures * that a files is not added to the processing queue * if the 'postWrite' hook was triggered by a new * version created by the OCR process. */ class ProcessingFileAccessor implements IProcessingFileAccessor { - /** @var ?int */ - private $currentlyProcessedFileId; + /** @var ?string */ + private $currentlyProcessedFilePath; /** @var ProcessingFileAccessor */ private static $instance; @@ -50,14 +50,14 @@ private function __construct() { /** * @inheritdoc */ - public function getCurrentlyProcessedFileId() : ?int { - return $this->currentlyProcessedFileId; + public function getCurrentlyProcessedFilePath() : ?string { + return $this->currentlyProcessedFilePath; } /** * @inheritdoc */ - public function setCurrentlyProcessedFileId(?int $fileId) : void { - $this->currentlyProcessedFileId = $fileId; + public function setCurrentlyProcessedFilePath(?string $filePath) : void { + $this->currentlyProcessedFilePath = $filePath; } } diff --git a/lib/Operation.php b/lib/Operation.php index 8f07aa1..2c1848e 100644 --- a/lib/Operation.php +++ b/lib/Operation.php @@ -226,7 +226,7 @@ private function tryGetJobArgs(Node $node, $operation, & $argsArray) : bool { private function eventTriggeredByOcrProcess(Node $node) : bool { // Check if the event was triggered by OCR rewrite of the file - if ($node->getId() === $this->processingFileAccessor->getCurrentlyProcessedFileId()) { + if ($node->getPath() === $this->processingFileAccessor->getCurrentlyProcessedFilePath()) { $this->logger->debug('Not processing event because file with path \'{path}\' was written by OCR process.', ['path' => $node->getPath()]); return true; diff --git a/lib/Service/OcrService.php b/lib/Service/OcrService.php index 92dcfc6..7d7cebc 100644 --- a/lib/Service/OcrService.php +++ b/lib/Service/OcrService.php @@ -252,7 +252,7 @@ private function createNewFileVersion(string $filePath, string $ocrContent, int $dirPath = dirname($filePath); $filename = basename($filePath); - $this->processingFileAccessor->setCurrentlyProcessedFileId($fileId); + $this->processingFileAccessor->setCurrentlyProcessedFilePath($filePath); try { $view = $this->viewFactory->create($dirPath); @@ -267,7 +267,7 @@ private function createNewFileVersion(string $filePath, string $ocrContent, int $view->touch($filename, $fileMtime); } } finally { - $this->processingFileAccessor->setCurrentlyProcessedFileId(null); + $this->processingFileAccessor->setCurrentlyProcessedFilePath(null); } } @@ -315,14 +315,23 @@ private function doPostProcessing(Node $file, string $uid, WorkflowSettings $set // Only create a new file version if the file OCR result was not empty #130 if ($result->getRecognizedText() !== '') { - if ($settings->getKeepOriginalFileVersion()) { + if ($settings->getKeepOriginalFileVersion() && $file->isUpdateable()) { // Add label to original file to prevent its expiry $this->setFileVersionsLabel($file, $uid, self::FILE_VERSION_LABEL_VALUE); } - $newFilePath = $originalFileExtension === $newFileExtension ? - $filePath : - $filePath . '.pdf'; + if ($originalFileExtension === $newFileExtension) { + if (!$file->isUpdateable()) { + // Add suffix '_OCR' if original file cannot be updated + $fileInfo = pathinfo($filePath); + $newFilePath = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_OCR.' . $originalFileExtension; + } else { + $newFilePath = $filePath; + } + } else { + $newFilePath = $filePath . '.pdf'; + } + $this->createNewFileVersion($newFilePath, $fileContent, $fileId, $fileMtime); } diff --git a/tests/Unit/Helper/ProcessingFIleAccessorTest.php b/tests/Unit/Helper/ProcessingFIleAccessorTest.php index 4d220e1..31eadc2 100644 --- a/tests/Unit/Helper/ProcessingFIleAccessorTest.php +++ b/tests/Unit/Helper/ProcessingFIleAccessorTest.php @@ -36,8 +36,8 @@ public function testSingleton() { public function testGetSet() { $o = ProcessingFileAccessor::getInstance(); - $o ->setCurrentlyProcessedFileId(42); - $this->assertEquals(42, $o->getCurrentlyProcessedFileId()); - $o->setCurrentlyProcessedFileId(null); + $o ->setCurrentlyProcessedFilePath('/someuser/files/somefile.pdf'); + $this->assertEquals('/someuser/files/somefile.pdf', $o->getCurrentlyProcessedFilePath()); + $o->setCurrentlyProcessedFilePath(null); } } From eaccebba0e68bff7e525da37c50f5ab3a4eedbbe Mon Sep 17 00:00:00 2001 From: Robin Windey Date: Wed, 15 Jan 2025 19:53:00 +0100 Subject: [PATCH 2/2] Node update permissions adjustments * Create dedicated private function for determining new file name * Adjust and add tests * Update Composer Deps --- composer.lock | 36 ++++++------- lib/Service/OcrService.php | 39 +++++++++----- tests/Unit/OperationTest.php | 77 +++++---------------------- tests/Unit/Service/OcrServiceTest.php | 53 +++++++++++++----- 4 files changed, 98 insertions(+), 107 deletions(-) diff --git a/composer.lock b/composer.lock index 2a7ced8..908366a 100644 --- a/composer.lock +++ b/composer.lock @@ -959,12 +959,12 @@ "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "15a749fe867b97ae5e699a3ce9e1d50a792c1777" + "reference": "3d9b3b4d13b3a8ad441c1fd862dd7365e72840dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/15a749fe867b97ae5e699a3ce9e1d50a792c1777", - "reference": "15a749fe867b97ae5e699a3ce9e1d50a792c1777", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/3d9b3b4d13b3a8ad441c1fd862dd7365e72840dd", + "reference": "3d9b3b4d13b3a8ad441c1fd862dd7365e72840dd", "shasum": "" }, "require": { @@ -1000,7 +1000,7 @@ "issues": "https://github.com/nextcloud-deps/ocp/issues", "source": "https://github.com/nextcloud-deps/ocp/tree/master" }, - "time": "2024-12-21T00:43:01+00:00" + "time": "2025-01-15T00:42:43+00:00" }, { "name": "nikic/php-parser", @@ -1178,16 +1178,16 @@ }, { "name": "php-cs-fixer/shim", - "version": "v3.65.0", + "version": "v3.68.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "4983ec79b9dee926695ac324ea6e8d291935525d" + "reference": "23acc692a99304559d4c94e9f299158ecd0ed7d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/4983ec79b9dee926695ac324ea6e8d291935525d", - "reference": "4983ec79b9dee926695ac324ea6e8d291935525d", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/23acc692a99304559d4c94e9f299158ecd0ed7d1", + "reference": "23acc692a99304559d4c94e9f299158ecd0ed7d1", "shasum": "" }, "require": { @@ -1224,9 +1224,9 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.65.0" + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.68.0" }, - "time": "2024-11-25T00:39:41+00:00" + "time": "2025-01-13T17:01:38+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3347,12 +3347,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3803,12 +3803,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { diff --git a/lib/Service/OcrService.php b/lib/Service/OcrService.php index 7d7cebc..52ffc6e 100644 --- a/lib/Service/OcrService.php +++ b/lib/Service/OcrService.php @@ -307,7 +307,6 @@ private function setFileVersionsLabel(File $file, string $uid, string $label): v private function doPostProcessing(Node $file, string $uid, WorkflowSettings $settings, OcrProcessorResult $result, ?int $fileMtime = null): void { $this->processTagsAfterSuccessfulOcr($file, $settings); - $filePath = $file->getPath(); $fileId = $file->getId(); $fileContent = $result->getFileContent(); $originalFileExtension = $file->getExtension(); @@ -320,19 +319,7 @@ private function doPostProcessing(Node $file, string $uid, WorkflowSettings $set $this->setFileVersionsLabel($file, $uid, self::FILE_VERSION_LABEL_VALUE); } - if ($originalFileExtension === $newFileExtension) { - if (!$file->isUpdateable()) { - // Add suffix '_OCR' if original file cannot be updated - $fileInfo = pathinfo($filePath); - $newFilePath = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_OCR.' . $originalFileExtension; - } else { - $newFilePath = $filePath; - } - } else { - $newFilePath = $filePath . '.pdf'; - } - - + $newFilePath = $this->determineNewFilePath($file, $originalFileExtension, $newFileExtension); $this->createNewFileVersion($newFilePath, $fileContent, $fileId, $fileMtime); } @@ -342,4 +329,28 @@ private function doPostProcessing(Node $file, string $uid, WorkflowSettings $set $this->notificationService->createSuccessNotification($uid, $fileId); } } + + /** + * Determines the new file path for a given file by analyzing the original- and new file extension. + * Also takes into consideration, if the file can be updated by the current user. + * + * @param Node $file The original file node for which the OCR processing has been succeeded. + * @param string $originalFileExtension The original file extension. + * @param string $newFileExtension The new file extension to be applied. + * @return string The new file path with the updated extension. + */ + private function determineNewFilePath(Node $file, string $originalFileExtension, string $newFileExtension): string { + $filePath = $file->getPath(); + if ($originalFileExtension !== $newFileExtension) { + // If the extension changed, will create a new file with the new extension + return $filePath . '.' . $newFileExtension; + } + if (!$file->isUpdateable()) { + // Add suffix '_OCR' if original file cannot be updated + $fileInfo = pathinfo($filePath); + return $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_OCR.' . $newFileExtension; + } + // By returning the original file path, we will create a new file version of the original file + return $filePath; + } } diff --git a/tests/Unit/OperationTest.php b/tests/Unit/OperationTest.php index 4e400d6..e8ec131 100644 --- a/tests/Unit/OperationTest.php +++ b/tests/Unit/OperationTest.php @@ -26,6 +26,7 @@ use OCA\WorkflowEngine\Entity\File; use OCA\WorkflowOcr\BackgroundJobs\ProcessFileJob; use OCA\WorkflowOcr\Helper\IProcessingFileAccessor; +use OCA\WorkflowOcr\Helper\ProcessingFileAccessor; use OCA\WorkflowOcr\Operation; use OCP\BackgroundJob\IJobList; use OCP\EventDispatcher\Event; @@ -36,7 +37,6 @@ use OCP\Files\Node; use OCP\IL10N; use OCP\IURLGenerator; -use OCP\IUser; use OCP\SystemTag\MapperEvent; use OCP\WorkflowEngine\IManager; use OCP\WorkflowEngine\IRuleMatcher; @@ -70,7 +70,7 @@ protected function setUp(): void { $this->l = $this->createMock(IL10N::class); $this->logger = $this->createMock(LoggerInterface::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); - $this->processingFileAccessor = $this->createMock(IProcessingFileAccessor::class); + $this->processingFileAccessor = ProcessingFileAccessor::getInstance(); $this->ruleMatcher = $this->createMock(IRuleMatcher::class); $this->rootFolder = $this->createMock(IRootFolder::class); @@ -79,6 +79,10 @@ protected function setUp(): void { ->willReturn($match); // simulate single matching operation } + protected function tearDown(): void { + $this->processingFileAccessor->setCurrentlyProcessedFilePath(null); + } + public function testDoesNothingIfRuleMatcherDoesNotMatch() { $this->jobList->expects($this->never()) ->method('add') @@ -120,7 +124,7 @@ public function testDoesNothingOnFolderEvent() { } public function testDoesNothingOnPostWriteTriggeredByCurrentOcrProcess() { - $fileId = 42; + $filePath = '/user/files/somefile.pdf'; $this->jobList->expects($this->never()) ->method('add') @@ -129,26 +133,16 @@ public function testDoesNothingOnPostWriteTriggeredByCurrentOcrProcess() { ->method('debug') ->withAnyParameters(); - $this->processingFileAccessor->expects($this->once()) - ->method('getCurrentlyProcessedFileId') - ->willReturn($fileId); - + $this->processingFileAccessor->setCurrentlyProcessedFilePath($filePath); $operation = new Operation($this->jobList, $this->l, $this->logger, $this->urlGenerator, $this->processingFileAccessor, $this->rootFolder); - /** @var MockObject|IUser */ - $userMock = $this->createMock(IUser::class); - $userMock->expects($this->never()) - ->method('getUID'); /** @var MockObject|Node */ $fileMock = $this->createMock(Node::class); $fileMock->method('getType') ->willReturn(FileInfo::TYPE_FILE); - $fileMock->method('getPath') - ->willReturn('/someuser/files/somefile.pdf'); - $fileMock->method('getOwner') - ->willReturn($userMock); - $fileMock->method('getId') - ->willReturn($fileId); + $fileMock->expects($this->never())->method('getOwner'); + $fileMock->expects($this->atLeastOnce())->method('getPath') + ->willReturn($filePath); $event = new GenericEvent($fileMock); $eventName = '\OCP\Files::postCreate'; @@ -183,31 +177,6 @@ public function testDoesNothingOnInvalidFilePath(string $filePath) { $operation->onEvent($eventName, $event, $this->ruleMatcher); } - public function testDoesNothingOnFileWithoutOwner() { - $this->jobList->expects($this->never()) - ->method('add') - ->withAnyParameters(); - $this->logger->expects($this->atLeastOnce()) - ->method('debug') - ->withAnyParameters(); - - $operation = new Operation($this->jobList, $this->l, $this->logger, $this->urlGenerator, $this->processingFileAccessor, $this->rootFolder); - - /** @var MockObject|Node */ - $fileMock = $this->createMock(Node::class); - $fileMock->method('getType') - ->willReturn(FileInfo::TYPE_FILE); - $fileMock->method('getPath') - ->willReturn('/admin/files/path/to/file.pdf'); - $fileMock->method('getOwner') - ->willReturn(null); - - $event = new GenericEvent($fileMock); - $eventName = '\OCP\Files::postCreate'; - - $operation->onEvent($eventName, $event, $this->ruleMatcher); - } - public function testAddWithCorrectFilePathAndUser() { $filePath = '/admin/files/path/to/file.pdf'; $fileId = 42; @@ -218,19 +187,13 @@ public function testAddWithCorrectFilePathAndUser() { $operation = new Operation($this->jobList, $this->l, $this->logger, $this->urlGenerator, $this->processingFileAccessor, $this->rootFolder); - /** @var MockObject|IUser */ - $userMock = $this->createMock(IUser::class); - $userMock->expects($this->never()) - ->method('getUID') - ->willReturn($uid); /** @var MockObject|Node */ $fileMock = $this->createMock(Node::class); $fileMock->method('getType') ->willReturn(FileInfo::TYPE_FILE); $fileMock->method('getPath') ->willReturn($filePath); - $fileMock->method('getOwner') - ->willReturn($userMock); + $fileMock->expects($this->never())->method('getOwner'); $fileMock->method('getId') ->willReturn($fileId); $event = new GenericEvent($fileMock); @@ -406,19 +369,13 @@ public function testFileAddedToQueueOnTagAssignedEvent() { ->method('add') ->with(ProcessFileJob::class, ['fileId' => $fileId, 'uid' => $uid, 'settings' => self::SETTINGS]); - /** @var MockObject|IUser */ - $userMock = $this->createMock(IUser::class); - $userMock->expects($this->never()) - ->method('getUID') - ->willReturn($uid); /** @var MockObject|\OCP\Files\File */ $fileMock = $this->createMock(\OCP\Files\File::class); $fileMock->method('getType') ->willReturn(FileInfo::TYPE_FILE); $fileMock->method('getPath') ->willReturn($filePath); - $fileMock->method('getOwner') - ->willReturn($userMock); + $fileMock->expects($this->never())->method('getOwner'); $fileMock->method('getId') ->willReturn($fileId); /** @var MockObject|IRootFolder */ @@ -446,19 +403,13 @@ public function testAddsEventOnEventWhereSubjectIsArray(string $eventName) { $operation = new Operation($this->jobList, $this->l, $this->logger, $this->urlGenerator, $this->processingFileAccessor, $this->rootFolder); - /** @var MockObject|IUser */ - $userMock = $this->createMock(IUser::class); - $userMock->expects($this->never()) - ->method('getUID') - ->willReturn($uid); /** @var MockObject|Node */ $fileMockSecondFile = $this->createMock(Node::class); $fileMockSecondFile->method('getType') ->willReturn(FileInfo::TYPE_FILE); $fileMockSecondFile->method('getPath') ->willReturn($filePath); - $fileMockSecondFile->method('getOwner') - ->willReturn($userMock); + $fileMockSecondFile->expects($this->never())->method('getOwner'); $fileMockSecondFile->method('getId') ->willReturn($fileId); /** @var MockObject|Node */ diff --git a/tests/Unit/Service/OcrServiceTest.php b/tests/Unit/Service/OcrServiceTest.php index 01f04f9..a5bec7d 100644 --- a/tests/Unit/Service/OcrServiceTest.php +++ b/tests/Unit/Service/OcrServiceTest.php @@ -449,6 +449,7 @@ public function testCallsProcessingFileAccessor() { $mimeType = 'application/pdf'; $content = 'someFileContent'; $ocrContent = 'someOcrProcessedFile'; + $filePath = '/admin/files/somefile.pdf'; $ocrResult = new OcrProcessorResult($ocrContent, 'pdf', $ocrContent); // Extend this cases if we add new OCR processors $this->rootFolderGetById42ReturnValue = [$this->createValidFileMock($mimeType, $content)]; @@ -462,17 +463,17 @@ public function testCallsProcessingFileAccessor() { ->method('create') ->willReturn($viewMock); - $calledWithFileId42 = 0; + $calledWithFilePath = 0; $calledWithNull = 0; - $withIdCalledFirst = false; + $withFilePathCalledFirst = false; $this->processingFileAccessor->expects($this->exactly(2)) - ->method('setCurrentlyProcessedFileId') - ->with($this->callback(function ($id) use (&$calledWithFileId42, &$calledWithNull, &$withIdCalledFirst) { - if ($id === 42) { - $calledWithFileId42++; - $withIdCalledFirst = $calledWithNull === 0; - } elseif ($id === null) { + ->method('setCurrentlyProcessedFilePath') + ->with($this->callback(function ($fPath) use (&$calledWithFilePath, &$calledWithNull, &$withFilePathCalledFirst, $filePath) { + if ($fPath === $filePath) { + $calledWithFilePath++; + $withFilePathCalledFirst = $calledWithNull === 0; + } elseif ($fPath === null) { $calledWithNull++; } @@ -481,9 +482,9 @@ public function testCallsProcessingFileAccessor() { $this->ocrService->runOcrProcess(42, 'usr', $settings); - $this->assertEquals(1, $calledWithFileId42); + $this->assertEquals(1, $calledWithFilePath); $this->assertEquals(1, $calledWithNull); - $this->assertTrue($withIdCalledFirst); + $this->assertTrue($withFilePathCalledFirst); } public function testDoesNotCreateNewFileVersionIfOcrContentWasEmpty() { @@ -510,7 +511,7 @@ public function testDoesNotCreateNewFileVersionIfOcrContentWasEmpty() { ->willReturn($viewMock); $this->processingFileAccessor->expects($this->never()) - ->method('setCurrentlyProcessedFileId'); + ->method('setCurrentlyProcessedFilePath'); $this->eventService->expects($this->once()) ->method('textRecognized'); @@ -649,6 +650,32 @@ public function testRunOcrProcessWithJobArgumentLogsErrorOnException(Exception $ $this->ocrService->runOcrProcessWithJobArgument($this->defaultArgument); } + public function testCreatesNewFileVersionWithSuffixIfNodeIsNotUpdateable() { + $settings = new WorkflowSettings(); + $mimeType = 'application/pdf'; + $content = 'someFileContent'; + $ocrContent = 'someOcrProcessedFile'; + $ocrResult = new OcrProcessorResult($ocrContent, 'pdf', $ocrContent); // Extend this cases if we add new OCR processors + + $fileMock = $this->createValidFileMock($mimeType, $content, '/admin/files', 'somefile.pdf', false); + $this->rootFolderGetById42ReturnValue = [$fileMock]; + + $this->ocrProcessor->expects($this->once()) + ->method('ocrFile') + ->willReturn($ocrResult); + + $viewMock = $this->createMock(IView::class); + $this->viewFactory->expects($this->once()) + ->method('create') + ->willReturn($viewMock); + + $viewMock->expects($this->once()) + ->method('file_put_contents') + ->with('somefile_OCR.pdf', $ocrContent); + + $this->ocrService->runOcrProcess(42, 'usr', $settings); + } + public function dataProvider_InvalidNodes() { /** @var MockObject|Node */ $folderMock = $this->createMock(Node::class); @@ -699,7 +726,7 @@ public function dataProvider_ExceptionsToBeCaught() { /** * @return File|MockObject */ - private function createValidFileMock(string $mimeType = 'application/pdf', string $content = 'someFileContent', string $rootFolderPath = '/admin/files', string $fileName = 'somefile.pdf'): File { + private function createValidFileMock(string $mimeType = 'application/pdf', string $content = 'someFileContent', string $rootFolderPath = '/admin/files', string $fileName = 'somefile.pdf', bool $updatable = true): File { /** @var MockObject|File */ $fileMock = $this->createMock(File::class); $fileMock->method('getType') @@ -715,6 +742,8 @@ private function createValidFileMock(string $mimeType = 'application/pdf', strin #get extension from filename $fileMock->method('getExtension') ->willReturn(pathinfo($fileName, PATHINFO_EXTENSION)); + $fileMock->method('isUpdateable') + ->willReturn($updatable); return $fileMock; } }