mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-04 03:08:25 +00:00 
			
		
		
		
	Add WopiConverter service and update Collabora integration tests
Introduce the WopiConverter service to handle document-to-PDF conversion using Collabora Online. Extend and update related tests in WopiConvertToPdfTest and ConvertControllerTest for better coverage and reliability. Enhance the GitLab CI configuration to exclude new test category "collabora-integration".
This commit is contained in:
		
							
								
								
									
										2
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.env
									
									
									
									
									
								
							@@ -23,7 +23,7 @@ TRUSTED_HOSTS='^(localhost|example\.com|nginx)$'
 | 
			
		||||
###< symfony/framework-bundle ###
 | 
			
		||||
 | 
			
		||||
## Wopi server for editing documents online
 | 
			
		||||
WOPI_SERVER=http://collabora:9980
 | 
			
		||||
EDITOR_SERVER=http://collabora:9980
 | 
			
		||||
 | 
			
		||||
# must be manually set in .env.local
 | 
			
		||||
# ADMIN_PASSWORD=
 | 
			
		||||
 
 | 
			
		||||
@@ -41,3 +41,5 @@ DATABASE_URL="postgresql://postgres:postgres@db:5432/test?serverVersion=14&chars
 | 
			
		||||
ASYNC_UPLOAD_TEMP_URL_KEY=
 | 
			
		||||
ASYNC_UPLOAD_TEMP_URL_BASE_PATH=
 | 
			
		||||
ASYNC_UPLOAD_TEMP_URL_CONTAINER=
 | 
			
		||||
 | 
			
		||||
EDITOR_SERVER=https://localhost:9980
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,7 @@ unit_tests:
 | 
			
		||||
        - php tests/console chill:db:sync-views --env=test
 | 
			
		||||
        - php -d memory_limit=2G tests/console cache:clear --env=test
 | 
			
		||||
        - php -d memory_limit=3G tests/console doctrine:fixtures:load -n --env=test
 | 
			
		||||
        - php -d memory_limit=4G bin/phpunit --colors=never --exclude-group dbIntensive,openstack-integration
 | 
			
		||||
        - php -d memory_limit=4G bin/phpunit --colors=never --exclude-group dbIntensive,openstack-integration,collabora-integration
 | 
			
		||||
    artifacts:
 | 
			
		||||
        expire_in: 1 day
 | 
			
		||||
        paths:
 | 
			
		||||
 
 | 
			
		||||
@@ -14,84 +14,44 @@ namespace Chill\WopiBundle\Controller;
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObject;
 | 
			
		||||
use Chill\DocStoreBundle\Service\StoredObjectManager;
 | 
			
		||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\WopiBundle\Service\WopiConverter;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\JsonResponse;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 | 
			
		||||
use Symfony\Component\Mime\Part\DataPart;
 | 
			
		||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\ResponseInterface;
 | 
			
		||||
 | 
			
		||||
class ConvertController
 | 
			
		||||
{
 | 
			
		||||
    private const LOG_PREFIX = '[convert] ';
 | 
			
		||||
 | 
			
		||||
    private readonly string $collaboraDomain;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param StoredObjectManager $storedObjectManager
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private readonly HttpClientInterface $httpClient,
 | 
			
		||||
        private readonly RequestStack $requestStack,
 | 
			
		||||
        private readonly Security $security,
 | 
			
		||||
        private readonly StoredObjectManagerInterface $storedObjectManager,
 | 
			
		||||
        private readonly WopiConverter $wopiConverter,
 | 
			
		||||
        private readonly LoggerInterface $logger,
 | 
			
		||||
        ParameterBagInterface $parameters,
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->collaboraDomain = $parameters->get('wopi')['server'];
 | 
			
		||||
    }
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function __invoke(StoredObject $storedObject): Response
 | 
			
		||||
    public function __invoke(StoredObject $storedObject, Request $request): Response
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->security->getUser() instanceof User) {
 | 
			
		||||
        if (!($this->security->isGranted('ROLE_USER') || $this->security->isGranted('ROLE_ADMIN'))) {
 | 
			
		||||
            throw new AccessDeniedHttpException('User must be authenticated');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $content = $this->storedObjectManager->read($storedObject);
 | 
			
		||||
        $query = [];
 | 
			
		||||
        if (null !== $request = $this->requestStack->getCurrentRequest()) {
 | 
			
		||||
            $query['lang'] = $request->getLocale();
 | 
			
		||||
        }
 | 
			
		||||
        $lang = $request->getLocale();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $url = sprintf('%s/cool/convert-to/pdf', $this->collaboraDomain);
 | 
			
		||||
            $form = new FormDataPart([
 | 
			
		||||
                'data' => new DataPart($content, $storedObject->getUuid()->toString(), $storedObject->getType()),
 | 
			
		||||
            ]);
 | 
			
		||||
            $response = $this->httpClient->request('POST', $url, [
 | 
			
		||||
                'headers' => $form->getPreparedHeaders()->toArray(),
 | 
			
		||||
                'query' => $query,
 | 
			
		||||
                'body' => $form->bodyToString(),
 | 
			
		||||
                'timeout' => 10,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            return new Response($response->getContent(), Response::HTTP_OK, [
 | 
			
		||||
            return new Response($this->wopiConverter->convert($lang, $content, $storedObject->getType()), Response::HTTP_OK, [
 | 
			
		||||
                'Content-Type' => 'application/pdf',
 | 
			
		||||
            ]);
 | 
			
		||||
        } catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface $exception) {
 | 
			
		||||
            return $this->onConversionFailed($url, $exception->getResponse());
 | 
			
		||||
        } catch (\RuntimeException $exception) {
 | 
			
		||||
            $this->logger->alert(self::LOG_PREFIX.'Could not convert document', ['message' => $exception->getMessage(), 'exception', $exception->getTraceAsString()]);
 | 
			
		||||
 | 
			
		||||
            return new Response('convert server not available', Response::HTTP_SERVICE_UNAVAILABLE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function onConversionFailed(string $url, ResponseInterface $response): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->logger->error(self::LOG_PREFIX.' could not convert document', [
 | 
			
		||||
            'response_status' => $response->getStatusCode(),
 | 
			
		||||
            'message' => $response->getContent(false),
 | 
			
		||||
            'server' => $this->collaboraDomain,
 | 
			
		||||
            'url' => $url,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return new JsonResponse(['message' => 'conversion failed : '.$response->getContent(false)], Response::HTTP_SERVICE_UNAVAILABLE);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								src/Bundle/ChillWopiBundle/src/Service/WopiConverter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/Bundle/ChillWopiBundle/src/Service/WopiConverter.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\WopiBundle\Service;
 | 
			
		||||
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
 | 
			
		||||
use Symfony\Component\Mime\Part\DataPart;
 | 
			
		||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles the conversion of documents to PDF using the Collabora Online server.
 | 
			
		||||
 */
 | 
			
		||||
class WopiConverter
 | 
			
		||||
{
 | 
			
		||||
    private readonly string $collaboraDomain;
 | 
			
		||||
 | 
			
		||||
    private const LOG_PREFIX = '[WopiConverterPDF] ';
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private readonly HttpClientInterface $httpClient,
 | 
			
		||||
        private readonly LoggerInterface $logger,
 | 
			
		||||
        ParameterBagInterface $parameters,
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->collaboraDomain = $parameters->get('wopi')['server'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function convert(string $lang, string $content, string $contentType, $convertTo = 'pdf'): string
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $url = sprintf('%s/cool/convert-to/%s', $this->collaboraDomain, $convertTo);
 | 
			
		||||
 | 
			
		||||
            $form = new FormDataPart([
 | 
			
		||||
                'data' => new DataPart($content, uniqid('temp-file-'), contentType: $contentType),
 | 
			
		||||
            ]);
 | 
			
		||||
            $response = $this->httpClient->request('POST', $url, [
 | 
			
		||||
                'headers' => $form->getPreparedHeaders()->toArray(),
 | 
			
		||||
                'query' => ['lang' => $lang],
 | 
			
		||||
                'body' => $form->bodyToString(),
 | 
			
		||||
                'timeout' => 10,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            if (200 === $response->getStatusCode()) {
 | 
			
		||||
                $this->logger->info(self::LOG_PREFIX.'document converted successfully', ['size' => strlen($content)]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return $response->getContent();
 | 
			
		||||
        } catch (ClientExceptionInterface $e) {
 | 
			
		||||
            throw new \LogicException('no correct request to collabora online', previous: $e);
 | 
			
		||||
        } catch (RedirectionExceptionInterface $e) {
 | 
			
		||||
            throw new \RuntimeException('no redirection expected', previous: $e);
 | 
			
		||||
        } catch (ServerExceptionInterface|TransportExceptionInterface $e) {
 | 
			
		||||
            throw new \RuntimeException('error while converting document', previous: $e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,16 +13,12 @@ namespace Chill\WopiBundle\Tests\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObject;
 | 
			
		||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\WopiBundle\Controller\ConvertController;
 | 
			
		||||
use Chill\WopiBundle\Service\WopiConverter;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
use Prophecy\PhpUnit\ProphecyTrait;
 | 
			
		||||
use Psr\Log\NullLogger;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
 | 
			
		||||
use Symfony\Component\HttpClient\MockHttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\Response\MockResponse;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -39,28 +35,27 @@ final class ConvertControllerTest extends TestCase
 | 
			
		||||
        $storedObject = new StoredObject();
 | 
			
		||||
        $storedObject->registerVersion(type: 'application/vnd.oasis.opendocument.text');
 | 
			
		||||
 | 
			
		||||
        $httpClient = new MockHttpClient([
 | 
			
		||||
            new MockResponse('not authorized', ['http_code' => 401]),
 | 
			
		||||
        ], 'http://collabora:9980');
 | 
			
		||||
 | 
			
		||||
        $security = $this->prophesize(Security::class);
 | 
			
		||||
        $security->getUser()->willReturn(new User());
 | 
			
		||||
        $security->isGranted('ROLE_USER')->willReturn(true);
 | 
			
		||||
 | 
			
		||||
        $storeManager = $this->prophesize(StoredObjectManagerInterface::class);
 | 
			
		||||
        $storeManager->read($storedObject)->willReturn('content');
 | 
			
		||||
 | 
			
		||||
        $parameterBag = new ParameterBag(['wopi' => ['server' => 'http://collabora:9980']]);
 | 
			
		||||
        $wopiConverter = $this->prophesize(WopiConverter::class);
 | 
			
		||||
        $wopiConverter->convert('fr', 'content', 'application/vnd.oasis.opendocument.text')
 | 
			
		||||
            ->willThrow(new \RuntimeException());
 | 
			
		||||
 | 
			
		||||
        $convert = new ConvertController(
 | 
			
		||||
            $httpClient,
 | 
			
		||||
            $this->makeRequestStack(),
 | 
			
		||||
        $controller = new ConvertController(
 | 
			
		||||
            $security->reveal(),
 | 
			
		||||
            $storeManager->reveal(),
 | 
			
		||||
            $wopiConverter->reveal(),
 | 
			
		||||
            new NullLogger(),
 | 
			
		||||
            $parameterBag
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $response = $convert($storedObject);
 | 
			
		||||
        $request = new Request();
 | 
			
		||||
        $request->setLocale('fr');
 | 
			
		||||
 | 
			
		||||
        $response = $controller($storedObject, $request);
 | 
			
		||||
 | 
			
		||||
        $this->assertNotEquals(200, $response->getStatusCode());
 | 
			
		||||
    }
 | 
			
		||||
@@ -70,38 +65,29 @@ final class ConvertControllerTest extends TestCase
 | 
			
		||||
        $storedObject = new StoredObject();
 | 
			
		||||
        $storedObject->registerVersion(type: 'application/vnd.oasis.opendocument.text');
 | 
			
		||||
 | 
			
		||||
        $httpClient = new MockHttpClient([
 | 
			
		||||
            new MockResponse('1234', ['http_code' => 200]),
 | 
			
		||||
        ], 'http://collabora:9980');
 | 
			
		||||
 | 
			
		||||
        $security = $this->prophesize(Security::class);
 | 
			
		||||
        $security->getUser()->willReturn(new User());
 | 
			
		||||
        $security->isGranted('ROLE_USER')->willReturn(true);
 | 
			
		||||
 | 
			
		||||
        $storeManager = $this->prophesize(StoredObjectManagerInterface::class);
 | 
			
		||||
        $storeManager->read($storedObject)->willReturn('content');
 | 
			
		||||
 | 
			
		||||
        $parameterBag = new ParameterBag(['wopi' => ['server' => 'http://collabora:9980']]);
 | 
			
		||||
        $wopiConverter = $this->prophesize(WopiConverter::class);
 | 
			
		||||
        $wopiConverter->convert('fr', 'content', 'application/vnd.oasis.opendocument.text')
 | 
			
		||||
            ->willReturn('1234');
 | 
			
		||||
 | 
			
		||||
        $convert = new ConvertController(
 | 
			
		||||
            $httpClient,
 | 
			
		||||
            $this->makeRequestStack(),
 | 
			
		||||
        $controller = new ConvertController(
 | 
			
		||||
            $security->reveal(),
 | 
			
		||||
            $storeManager->reveal(),
 | 
			
		||||
            $wopiConverter->reveal(),
 | 
			
		||||
            new NullLogger(),
 | 
			
		||||
            $parameterBag
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $response = $convert($storedObject);
 | 
			
		||||
        $request = new Request();
 | 
			
		||||
        $request->setLocale('fr');
 | 
			
		||||
 | 
			
		||||
        $response = $controller($storedObject, $request);
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals(200, $response->getStatusCode());
 | 
			
		||||
        $this->assertEquals('1234', $response->getContent());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function makeRequestStack(): RequestStack
 | 
			
		||||
    {
 | 
			
		||||
        $requestStack = new RequestStack();
 | 
			
		||||
        $requestStack->push(new Request());
 | 
			
		||||
 | 
			
		||||
        return $requestStack;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\WopiBundle\Tests\Service;
 | 
			
		||||
 | 
			
		||||
use Chill\WopiBundle\Service\WopiConverter;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
use Psr\Log\NullLogger;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\MockHttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\Response\MockResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @internal
 | 
			
		||||
 *
 | 
			
		||||
 * @coversNothing
 | 
			
		||||
 */
 | 
			
		||||
class WopiConvertToPdfTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @group collabora-integration
 | 
			
		||||
     */
 | 
			
		||||
    public function testConvertToPdfWithRealServer(): void
 | 
			
		||||
    {
 | 
			
		||||
        $content = file_get_contents(__DIR__.'/fixtures/test-document.odt');
 | 
			
		||||
 | 
			
		||||
        $client = HttpClient::create();
 | 
			
		||||
        $parameters = new ParameterBag([
 | 
			
		||||
            'wopi' => ['server' => $_ENV['EDITOR_SERVER']],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $converter = new WopiConverter($client, new NullLogger(), $parameters);
 | 
			
		||||
 | 
			
		||||
        $actual = $converter->convert('fr', $content, 'application/vnd.oasis.opendocument.text');
 | 
			
		||||
 | 
			
		||||
        self::assertIsString($actual);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testConvertToPdfWithMock(): void
 | 
			
		||||
    {
 | 
			
		||||
        $httpClient = new MockHttpClient([
 | 
			
		||||
            new MockResponse('1234', ['http_code' => 200]),
 | 
			
		||||
        ], 'http://collabora:9980');
 | 
			
		||||
        $parameters = new ParameterBag([
 | 
			
		||||
            'wopi' => ['server' => 'http://collabora:9980'],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $converter = new WopiConverter($httpClient, new NullLogger(), $parameters);
 | 
			
		||||
 | 
			
		||||
        $actual = $converter->convert('fr', 'content-string', 'application/vnd.oasis.opendocument.text');
 | 
			
		||||
 | 
			
		||||
        self::assertEquals('1234', $actual);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								tests/app/config/packages/wopi.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/app/config/packages/wopi.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
wopi:
 | 
			
		||||
    server: "%env(resolve:EDITOR_SERVER)%"
 | 
			
		||||
		Reference in New Issue
	
	Block a user