diff --git a/.github/workflows/phpunit-integration.yml b/.github/workflows/phpunit-integration.yml index 08b2bb8..9ba9329 100644 --- a/.github/workflows/phpunit-integration.yml +++ b/.github/workflows/phpunit-integration.yml @@ -45,8 +45,9 @@ jobs: php-versions: ['8.3'] databases: ['mysql'] server-versions: ['stable31'] + backend: ['remote', 'local'] # Do not change these names, they're used in the integration tests - name: php-integrationtests${{ matrix.php-versions }}-${{ matrix.databases }} + name: php-integrationtests-${{ matrix.backend }}-${{ matrix.php-versions }}-${{ matrix.databases }} steps: - name: Checkout server @@ -75,6 +76,11 @@ jobs: extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, gd, zip, imagick coverage: none + - name: Install ocrmypdf + if: matrix.backend == 'local' + run: | + apt-get update && apt-get install -y ocrmypdf + - name: Install composer dependencies working-directory: apps/${{ env.APP_NAME }} run: composer i @@ -93,12 +99,14 @@ jobs: - name: Checkout AppApi uses: actions/checkout@v4 + if: matrix.backend == 'remote' with: repository: nextcloud/app_api ref: ${{ matrix.server-versions }} path: apps/app_api - name: Set up AppApi/ExApp infrastructure + if: matrix.backend == 'remote' run: | ./occ app:enable app_api ./occ app_api:daemon:register local_docker "docker-socket-proxy" \ @@ -110,10 +118,12 @@ jobs: - name: PHPUnit working-directory: apps/${{ env.APP_NAME }} + env: + GITHUB_MATRIX_BACKEND: ${{ matrix.backend }} run: make php-integrationtest - name: Write OCR Backend logs to file - if: failure() + if: failure() && matrix.backend == 'remote' run: | docker logs nc_app_workflow_ocr_backend > data/ocr_backend.log diff --git a/lib/Helper/SidecarFileAccessor.php b/lib/Helper/SidecarFileAccessor.php index 0ce696e..dbf84c7 100644 --- a/lib/Helper/SidecarFileAccessor.php +++ b/lib/Helper/SidecarFileAccessor.php @@ -49,6 +49,8 @@ public function getOrCreateSidecarFile() { $this->sidecarFilePath = $this->tempManager->getTemporaryFile('sidecar'); if (!$this->sidecarFilePath) { $this->logger->warning('Could not create temporary sidecar file'); + } else if (!is_writable($this->sidecarFilePath)) { + $this->logger->warning('Temporary sidecar file is not writable'); } } return $this->sidecarFilePath; diff --git a/lib/OcrProcessors/CommandLineUtils.php b/lib/OcrProcessors/CommandLineUtils.php index 6c9e52f..9ba1f9b 100644 --- a/lib/OcrProcessors/CommandLineUtils.php +++ b/lib/OcrProcessors/CommandLineUtils.php @@ -23,7 +23,6 @@ namespace OCA\WorkflowOcr\OcrProcessors; -use OCA\WorkflowOcr\Helper\ISidecarFileAccessor; use OCA\WorkflowOcr\Model\GlobalSettings; use OCA\WorkflowOcr\Model\WorkflowSettings; use OCA\WorkflowOcr\Service\IOcrBackendInfoService; @@ -38,13 +37,12 @@ class CommandLineUtils implements ICommandLineUtils { ]; public function __construct( - private ISidecarFileAccessor $sidecarFileAccessor, private IOcrBackendInfoService $ocrBackendInfoService, private LoggerInterface $logger, ) { } - public function getCommandlineArgs(WorkflowSettings $settings, GlobalSettings $globalSettings, array $additionalCommandlineArgs = []): string { + public function getCommandlineArgs(WorkflowSettings $settings, GlobalSettings $globalSettings, string $sidecarFile = null, array $additionalCommandlineArgs = []): string { $isLocalExecution = !$this->ocrBackendInfoService->isRemoteBackend(); // Default setting is quiet @@ -75,12 +73,9 @@ public function getCommandlineArgs(WorkflowSettings $settings, GlobalSettings $g $args[] = '--jobs ' . $processorCount; } - if ($isLocalExecution) { + if ($isLocalExecution && $sidecarFile !== null) { // Save recognized text in tempfile - $sidecarFilePath = $this->sidecarFileAccessor->getOrCreateSidecarFile(); - if ($sidecarFilePath) { - $args[] = '--sidecar ' . $sidecarFilePath; - } + $args[] = '--sidecar ' . $sidecarFile; } $resultArgs = array_filter(array_merge( diff --git a/lib/OcrProcessors/ICommandLineUtils.php b/lib/OcrProcessors/ICommandLineUtils.php index 141c446..981b2a6 100644 --- a/lib/OcrProcessors/ICommandLineUtils.php +++ b/lib/OcrProcessors/ICommandLineUtils.php @@ -27,5 +27,5 @@ use OCA\WorkflowOcr\Model\WorkflowSettings; interface ICommandLineUtils { - public function getCommandlineArgs(WorkflowSettings $settings, GlobalSettings $globalSettings, array $additionalCommandlineArgs = []): string; + public function getCommandlineArgs(WorkflowSettings $settings, GlobalSettings $globalSettings, string $sidecarFile = null, array $additionalCommandlineArgs = []): string; } diff --git a/lib/OcrProcessors/Local/OcrMyPdfBasedProcessor.php b/lib/OcrProcessors/Local/OcrMyPdfBasedProcessor.php index 5d2a88e..528ff66 100644 --- a/lib/OcrProcessors/Local/OcrMyPdfBasedProcessor.php +++ b/lib/OcrProcessors/Local/OcrMyPdfBasedProcessor.php @@ -46,7 +46,8 @@ public function __construct( public function ocrFile(File $file, WorkflowSettings $settings, GlobalSettings $globalSettings): OcrProcessorResult { $additionalCommandlineArgs = $this->getAdditionalCommandlineArgs($settings, $globalSettings); - $commandStr = 'ocrmypdf ' . $this->commandLineUtils->getCommandlineArgs($settings, $globalSettings, $additionalCommandlineArgs) . ' - - || exit $? ; cat'; + $sidecarFile = $this->sidecarFileAccessor->getOrCreateSidecarFile(); + $commandStr = 'ocrmypdf ' . $this->commandLineUtils->getCommandlineArgs($settings, $globalSettings, $sidecarFile, $additionalCommandlineArgs) . ' - - || exit $? ; cat'; $inputFileContent = $file->getContent(); @@ -82,7 +83,7 @@ public function ocrFile(File $file, WorkflowSettings $settings, GlobalSettings $ $recognizedText = $this->sidecarFileAccessor->getSidecarFileContent(); if (!$recognizedText) { - $this->logger->info('Temporary sidecar file at \'{path}\' was empty', ['path' => $this->sidecarFileAccessor->getOrCreateSidecarFile()]); + $this->logger->info('Temporary sidecar file at \'{path}\' was empty', ['path' => $sidecarFile]); } $this->logger->debug('OCR processing was successful'); diff --git a/tests/Integration/BackendTestBase.php b/tests/Integration/BackendTestBase.php new file mode 100644 index 0000000..3dd0420 --- /dev/null +++ b/tests/Integration/BackendTestBase.php @@ -0,0 +1,215 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\WorkflowOcr\Tests\Integration; + +use CurlHandle; +use DomainException; +use OCA\WorkflowEngine\Helper\ScopeContext; +use OCA\WorkflowEngine\Manager; +use OCA\WorkflowOcr\AppInfo\Application; +use OCA\WorkflowOcr\BackgroundJobs\ProcessFileJob; +use OCA\WorkflowOcr\Operation; +use OCA\WorkflowOcr\Service\IOcrBackendInfoService; +use OCA\WorkflowOcr\Wrapper\CommandWrapper; +use OCP\AppFramework\App; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\WorkflowEngine\IManager; +use Psr\Container\ContainerInterface; +use Test\TestCase; + +abstract class BackendTestBase extends TestCase { + private IConfig $config; + private ScopeContext $context; + private Manager $workflowEngineManager; + private $oldLogLevel; + private $operationClass = Operation::class; + private array $uploadedFiles = []; + + protected ContainerInterface $container; + + protected function setUp(): void { + parent::setUp(); + + $app = new App(Application::APP_NAME); + $this->container = $app->getContainer(); + $this->config = $this->container->get(IConfig::class); + $this->workflowEngineManager = $this->container->get(Manager::class); + $this->context = new ScopeContext(IManager::SCOPE_ADMIN); + + $this->setNextcloudLogLevel(); + $this->deleteOperation(); + } + + protected function tearDown(): void { + $this->restoreNextcloudLogLevel(); + $this->deleteTestFilesIfExist(); + $this->deleteOperation(); + parent::tearDown(); + } + + protected function setNextcloudLogLevel() : void { + $this->oldLogLevel = $this->config->getSystemValue('loglevel', 3); + $this->config->setSystemValue('loglevel', 0); + } + + protected function restoreNextcloudLogLevel() : void { + $this->config->setSystemValue('loglevel', $this->oldLogLevel); + } + + protected function runOcrBackgroundJob() { + /** @var IJoblist */ + $jobList = $this->container->get(IJobList::class); + $job = $jobList->getNext(false, [ProcessFileJob::class]); + $this->assertNotNull($job, 'Expected one background job'); + $job->start($jobList); + } + + protected function checkOcrBackendServiceInstalled() : bool { + $ocrBackendInfoService = $this->container->get(IOcrBackendInfoService::class); + return $ocrBackendInfoService->isRemoteBackend(); + } + + protected function checkOcrMyPdfInstalled() : bool { + $command = new CommandWrapper(); + $command->setCommand('ocrmypdf --version')->execute(); + return $command->getExitCode() === 0; + } + + protected function addOperation(string $mimeType) { + // NOTE :: we're creating the workflow operation via + // REST API because if we'd use the manager directly, we'd + // face some issues because of caching etc (test ist running + // in another process than webserver ...) + $url = $this->getNextcloudOcsApiUrl() . 'apps/workflowengine/api/v1/workflows/global?format=json'; + $json = ' + { + "id":-1, + "class":"' . str_replace('\\', '\\\\', $this->operationClass) . '", + "entity":"OCA\\\\WorkflowEngine\\\\Entity\\\\File", + "events":["\\\\OCP\\\\Files::postCreate"], + "name":"", + "checks":[ + { + "class":"OCA\\\\WorkflowEngine\\\\Check\\\\FileMimeType", + "operator":"is", + "value":"' . $mimeType . '", + "invalid":false + } + ], + "operation":"", + "valid":true + }'; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'OCS-APIREQUEST: true' + ]); + + $this->executeCurl($ch); + } + + private function deleteOperation() { + $operations = $this->workflowEngineManager->getOperations($this->operationClass, $this->context); + foreach ($operations as $operation) { + try { + $this->workflowEngineManager->deleteOperation($operation['id'], $this->context); + } catch (DomainException) { + // ignore + } + } + + } + + protected function uploadTestFile(string $testFile) { + $localFile = __DIR__ . "/testdata/" . $testFile; + $this->uploadedFiles[] = $localFile; + $file = fopen($localFile, 'r'); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $this->getNextcloudWebdavUrl() . basename($localFile)); + curl_setopt($ch, CURLOPT_PUT, true); + curl_setopt($ch, CURLOPT_INFILE, $file); + curl_setopt($ch, CURLOPT_INFILESIZE, filesize($localFile)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $this->executeCurl($ch); + fclose($file); + } + + private function deleteTestFilesIfExist() { + foreach ($this->uploadedFiles as $localFile) { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $this->getNextcloudWebdavUrl() . basename($localFile)); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $this->executeCurl($ch, [404]); + } + $this->uploadedFiles = []; + } + + private function executeCurl(CurlHandle $ch, array $allowedNonSuccessResponseCodes = []) : string|bool { + curl_setopt($ch, CURLOPT_USERPWD, $this->getNextcloudCredentials()); + + $result = curl_exec($ch); + + if (curl_errno($ch)) { + $this->fail('cURL Error: ' . curl_error($ch)); + } + + $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($responseCode >= 400 && !in_array($responseCode, $allowedNonSuccessResponseCodes)) { + $responseBody = curl_multi_getcontent($ch); + $this->fail('cURL HTTP Error ' . $responseCode . ': ' . $responseBody); + } + + curl_close($ch); + + return $result; + } + + private function getNextcloudWebdavUrl() : string { + $port = getenv('NEXTCLOUD_PORT') ?: '80'; + $user = getenv('NEXTCLOUD_USER') ?: 'admin'; + return 'http://localhost:' . $port . '/remote.php/dav/files/' . $user . '/'; + } + + private function getNextcloudOcsApiUrl() : string { + $port = getenv('NEXTCLOUD_PORT') ?: '80'; + return 'http://localhost:' . $port . '/ocs/v2.php/'; + } + + private function getNextcloudCredentials() : string { + $user = getenv('NEXTCLOUD_USER') ?: 'admin'; + $pass = getenv('NEXTCLOUD_PASS') ?: 'admin'; + return $user . ':' . $pass; + } +} \ No newline at end of file diff --git a/tests/Integration/LocalBackendTest.php b/tests/Integration/LocalBackendTest.php new file mode 100644 index 0000000..69b77de --- /dev/null +++ b/tests/Integration/LocalBackendTest.php @@ -0,0 +1,95 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\WorkflowOcr\Tests\Integration; + +use OCA\WorkflowOcr\Events\TextRecognizedEvent; +use OCA\WorkflowOcr\OcrProcessors\Remote\Client\IApiClient; +use OCP\EventDispatcher\IEventDispatcher; + +/** + * Full test case for registering new OCR Workflow, uploading file and + * processing it via local OCR (ocrmypdf CLI) backend. + * @group DB + */ +class LocalBackendTest extends BackendTestBase { + private IntegrationTestApiClient $apiClient; + private IEventDispatcher $dispatcher; + private array $capturedEvents = []; + + /** @var callable */ + private $eventListener; + + protected function setUp(): void { + parent::setUp(); + + $githubActionsJob = getenv('GITHUB_JOB'); + $githubActionsMatrixBackend = getenv('GITHUB_MATRIX_BACKEND'); + $isOcrMyPdfInstalled = $this->checkOcrMyPdfInstalled(); + $shouldRunOnCi = $githubActionsJob === 'github-php-integrationtests' && $githubActionsMatrixBackend == 'local'; + + if ($shouldRunOnCi && !$isOcrMyPdfInstalled) { + $this->fail('Running Github Actions Integrationtests but ocrmypdf CLI is not installed'); + return; + } + + if (!$isOcrMyPdfInstalled) { + $this->markTestSkipped('ocrmypdf is not installed'); + return; + } + + if ($this->checkOcrBackendServiceInstalled()) { + $this->markTestSkipped('OCR Backend service is installed, cannot use local backend (ocrmypdf)'); + return; + } + + $this->dispatcher = $this->container->get(IEventDispatcher::class); + $this->apiClient = $this->container->get(IntegrationTestApiClient::class); + $this->overwriteService(IApiClient::class, $this->apiClient); + + $this->eventListener = function(TextRecognizedEvent $event) { + $this->capturedEvents[] = $event; + }; + $this->dispatcher->addListener(TextRecognizedEvent::class, $this->eventListener); + } + + protected function tearDown(): void { + parent::tearDown(); + $this->dispatcher->removeListener(TextRecognizedEvent::class, $this->eventListener); + } + + /** + * Test processing a file via ocrmypdf CLI. + */ + public function testWorkflowOcrLocalBackend(): void { + $this->addOperation('application/pdf'); + $this->uploadTestFile('document-ready-for-ocr.pdf'); + $this->runOcrBackgroundJob(); + + $this->assertEmpty($this->apiClient->getRequests(), 'Expected no OCR Backend Service requests'); + $this->assertEquals(1, count($this->capturedEvents), 'Expected 1 TextRecognizedEvent'); + $textRecognizedEvent = $this->capturedEvents[0]; + $this->assertInstanceOf(TextRecognizedEvent::class, $textRecognizedEvent, 'Expected TextRecognizedEvent instance'); + $this->assertEquals('This document is ready for OCR', trim($textRecognizedEvent->getRecognizedText()), 'Expected recognized text'); + } +} \ No newline at end of file diff --git a/tests/Integration/OcrBackendServiceTest.php b/tests/Integration/OcrBackendServiceTest.php index 704d9e7..c1a3235 100644 --- a/tests/Integration/OcrBackendServiceTest.php +++ b/tests/Integration/OcrBackendServiceTest.php @@ -23,83 +23,43 @@ namespace OCA\WorkflowOcr\Tests\Integration; -use CurlHandle; -use DomainException; -use OCA\WorkflowEngine\Helper\ScopeContext; -use OCA\WorkflowEngine\Manager; -use OCA\WorkflowOcr\AppInfo\Application; -use OCA\WorkflowOcr\BackgroundJobs\ProcessFileJob; use OCA\WorkflowOcr\OcrProcessors\Remote\Client\IApiClient; use OCA\WorkflowOcr\OcrProcessors\Remote\Client\Model\OcrResult; -use OCA\WorkflowOcr\Operation; -use OCA\WorkflowOcr\Service\IOcrBackendInfoService; -use OCP\AppFramework\App; -use OCP\BackgroundJob\IJobList; -use OCP\IConfig; -use OCP\WorkflowEngine\IManager; -use Psr\Container\ContainerInterface; -use Test\TestCase; /** * Full test case for registering new OCR Workflow, uploading file and * processing it via OCR Backend Service. * @group DB */ -class OcrBackendServiceTest extends TestCase { - private ContainerInterface $container; - private Manager $workflowEngineManager; +class OcrBackendServiceTest extends BackendTestBase { private IntegrationTestApiClient $apiClient; - private ScopeContext $context; - private IConfig $config; - private $operationClass = Operation::class; - private $oldLogLevel; protected function setUp(): void { parent::setUp(); - $app = new App(Application::APP_NAME); - $this->container = $app->getContainer(); - $this->workflowEngineManager = $this->container->get(Manager::class); - $this->apiClient = $this->container->get(IntegrationTestApiClient::class); - $this->config = $this->container->get(IConfig::class); - $this->context = new ScopeContext(IManager::SCOPE_ADMIN); - - $this->overwriteService(IApiClient::class, $this->apiClient); - $githubActionsJob = getenv('GITHUB_JOB'); - $isOcrBackendInstalled = $this->checkOcrBackendInstalled(); + $githubActionsMatrixBackend = getenv('GITHUB_MATRIX_BACKEND'); + $isOcrBackendServiceInstalled = $this->checkOcrBackendServiceInstalled(); + $shouldRunOnCi = $githubActionsJob === 'github-php-integrationtests' && $githubActionsMatrixBackend == 'remote'; - if ($githubActionsJob === 'github-php-integrationtests' && !$isOcrBackendInstalled) { + if ($shouldRunOnCi && !$isOcrBackendServiceInstalled) { $this->fail('Running Github Actions Integrationtests but OCR Backend is not installed'); return; } - if (!$isOcrBackendInstalled) { + if (!$isOcrBackendServiceInstalled) { $this->markTestSkipped('OCR Backend is not installed'); return; } - $this->setNextcloudLogLevel(); - $this->deleteTestFileIfExists(); - $this->deleteOperation(); - } - - protected function tearDown(): void { - if (!$this->checkOcrBackendInstalled()) { - return; - } - - $this->deleteTestFileIfExists(); - $this->deleteOperation(); - $this->restoreNextcloudLogLevel(); - - parent::tearDown(); + $this->apiClient = $this->container->get(IntegrationTestApiClient::class); + $this->overwriteService(IApiClient::class, $this->apiClient); } public function testWorkflowOcrBackendService() { - $this->addOperation(); - $this->uploadTestFile(); + $this->addOperation('application/pdf'); + $this->uploadTestFile('document-ready-for-ocr.pdf'); $this->runOcrBackgroundJob(); $requests = $this->apiClient->getRequests(); @@ -117,144 +77,4 @@ public function testWorkflowOcrBackendService() { $this->assertEquals('application/pdf', $ocrResult->getContentType(), 'Expected content type in response'); $this->assertTrue(strpos($ocrResult->getRecognizedText(), 'This document is ready for OCR') >= 0, 'Expected recognized text in response'); } - - private function addOperation() { - // NOTE :: we're creating the workflow operation via - // REST API because if we'd use the manager directly, we'd - // face some issues because of caching etc (test ist running - // in another process than webserver ...) - $url = $this->getNextcloudOcsApiUrl() . 'apps/workflowengine/api/v1/workflows/global?format=json'; - $json = ' - { - "id":-1, - "class":"' . str_replace('\\', '\\\\', $this->operationClass) . '", - "entity":"OCA\\\\WorkflowEngine\\\\Entity\\\\File", - "events":["\\\\OCP\\\\Files::postCreate"], - "name":"", - "checks":[ - { - "class":"OCA\\\\WorkflowEngine\\\\Check\\\\FileMimeType", - "operator":"is", - "value":"application/pdf", - "invalid":false - } - ], - "operation":"", - "valid":true - }'; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $json); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'OCS-APIREQUEST: true' - ]); - - $this->executeCurl($ch); - } - - private function deleteOperation() { - $operations = $this->workflowEngineManager->getOperations($this->operationClass, $this->context); - foreach ($operations as $operation) { - try { - $this->workflowEngineManager->deleteOperation($operation['id'], $this->context); - } catch (DomainException) { - // ignore - } - } - - } - - private function uploadTestFile() { - $localFile = $this->getTestFileReadyForOcr(); - $file = fopen($localFile, 'r'); - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $this->getNextcloudWebdavUrl() . basename($localFile)); - curl_setopt($ch, CURLOPT_PUT, true); - curl_setopt($ch, CURLOPT_INFILE, $file); - curl_setopt($ch, CURLOPT_INFILESIZE, filesize($localFile)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - $this->executeCurl($ch); - fclose($file); - } - - private function deleteTestFileIfExists() { - $localFile = $this->getTestFileReadyForOcr(); - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $this->getNextcloudWebdavUrl() . basename($localFile)); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - $this->executeCurl($ch, [404]); - } - - private function executeCurl(CurlHandle $ch, array $allowedNonSuccessResponseCodes = []) : string|bool { - curl_setopt($ch, CURLOPT_USERPWD, $this->getNextcloudCredentials()); - - $result = curl_exec($ch); - - if (curl_errno($ch)) { - $this->fail('cURL Error: ' . curl_error($ch)); - } - - $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($responseCode >= 400 && !in_array($responseCode, $allowedNonSuccessResponseCodes)) { - $responseBody = curl_multi_getcontent($ch); - $this->fail('cURL HTTP Error ' . $responseCode . ': ' . $responseBody); - } - - curl_close($ch); - - return $result; - } - - private function runOcrBackgroundJob() { - /** @var IJoblist */ - $jobList = $this->container->get(IJobList::class); - $job = $jobList->getNext(false, [ProcessFileJob::class]); - $this->assertNotNull($job); - $job->start($jobList); - } - - private function checkOcrBackendInstalled() : bool { - $ocrBackendInfoService = $this->container->get(IOcrBackendInfoService::class); - return $ocrBackendInfoService->isRemoteBackend(); - } - - private function getNextcloudWebdavUrl() : string { - $port = getenv('NEXTCLOUD_PORT') ?: '80'; - $user = getenv('NEXTCLOUD_USER') ?: 'admin'; - return 'http://localhost:' . $port . '/remote.php/dav/files/' . $user . '/'; - } - - private function getNextcloudOcsApiUrl() : string { - $port = getenv('NEXTCLOUD_PORT') ?: '80'; - return 'http://localhost:' . $port . '/ocs/v2.php/'; - } - - private function getNextcloudCredentials() : string { - $user = getenv('NEXTCLOUD_USER') ?: 'admin'; - $pass = getenv('NEXTCLOUD_PASS') ?: 'admin'; - return $user . ':' . $pass; - } - - private function getTestFileReadyForOcr() : string { - return __DIR__ . '/testdata/document-ready-for-ocr.pdf'; - } - - private function setNextcloudLogLevel() : void { - $this->oldLogLevel = $this->config->getSystemValue('loglevel', 3); - $this->config->setSystemValue('loglevel', 0); - } - - private function restoreNextcloudLogLevel() : void { - $this->config->setSystemValue('loglevel', $this->oldLogLevel); - } } diff --git a/tests/Unit/Helper/SidecarFileAccessorTest.php b/tests/Unit/Helper/SidecarFileAccessorTest.php index 2fe03ed..8beb16c 100644 --- a/tests/Unit/Helper/SidecarFileAccessorTest.php +++ b/tests/Unit/Helper/SidecarFileAccessorTest.php @@ -42,19 +42,20 @@ class SidecarFileAccessorTest extends TestCase { /** @var string */ private $tmpFilePath; + /** @var resource */ + private $tmpFileHandle; + public function setUp(): void { $this->tempManager = $this->createMock(ITempManager::class); $this->logger = $this->createMock(LoggerInterface::class); $this->accessor = new SidecarFileAccessor($this->tempManager, $this->logger); - $tmpFile = tmpfile(); - $this->tmpFilePath = stream_get_meta_data($tmpFile)['uri']; + $this->tmpFileHandle = tmpfile(); + $this->tmpFilePath = stream_get_meta_data($this->tmpFileHandle)['uri']; parent::setUp(); } public function tearDown(): void { - if (file_exists($this->tmpFilePath)) { - unlink($this->tmpFilePath); - } + fclose($this->tmpFileHandle); parent::tearDown(); } @@ -94,4 +95,19 @@ public function testLogsWarningIfCreateFails() { $sidecarFileContent = $this->accessor->getSidecarFileContent(); $this->assertEquals('', $sidecarFileContent); } + + public function testLogsWarningIfFileIsNotWriteable() { + chmod($this->tmpFilePath, 0111); + $this->tempManager->expects($this->once()) + ->method('getTemporaryFile') + ->with('sidecar') + ->willReturn($this->tmpFilePath); + + $this->logger->expects($this->once()) + ->method('warning') + ->with('Temporary sidecar file is not writable'); + + $sidecarFilePath = $this->accessor->getOrCreateSidecarFile(); + $this->assertEquals($this->tmpFilePath, $sidecarFilePath); + } } diff --git a/tests/Unit/OcrProcessors/Local/ImageOcrProcessorTest.php b/tests/Unit/OcrProcessors/Local/ImageOcrProcessorTest.php index d0a0567..46a9a4c 100644 --- a/tests/Unit/OcrProcessors/Local/ImageOcrProcessorTest.php +++ b/tests/Unit/OcrProcessors/Local/ImageOcrProcessorTest.php @@ -47,7 +47,7 @@ public function testOcrFileSetsImageDpi() { /** @var ICommandLineUtils|MockObject $commandLineUtils */ $commandLineUtils = $this->createMock(ICommandLineUtils::class); $commandLineUtils->method('getCommandlineArgs') - ->willReturnCallback(fn ($settings, $globalSettings, $additionalCommandlineArgs) => implode(' ', $additionalCommandlineArgs)); + ->willReturnCallback(fn ($settings, $globalSettings, $sidecarFile, $additionalCommandlineArgs) => implode(' ', $additionalCommandlineArgs)); $processor = new ImageOcrProcessor($command, $logger, $sidecarFileAccessor, $commandLineUtils); diff --git a/tests/Unit/OcrProcessors/Local/PdfOcrProcessorTest.php b/tests/Unit/OcrProcessors/Local/PdfOcrProcessorTest.php index c064527..9ab7444 100644 --- a/tests/Unit/OcrProcessors/Local/PdfOcrProcessorTest.php +++ b/tests/Unit/OcrProcessors/Local/PdfOcrProcessorTest.php @@ -70,7 +70,7 @@ protected function setUp(): void { $this->logger = $this->createMock(LoggerInterface::class); $this->sidecarFileAccessor = $this->createMock(ISidecarFileAccessor::class); $this->ocrBackendInfoService = $this->createMock(IOcrBackendInfoService::class); - $this->commandLineUtils = new CommandLineUtils($this->sidecarFileAccessor, $this->ocrBackendInfoService, $this->logger); + $this->commandLineUtils = new CommandLineUtils($this->ocrBackendInfoService, $this->logger); $this->defaultSettings = new WorkflowSettings(); $this->defaultGlobalSettings = new GlobalSettings();