mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +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