diff --git a/REUSE.toml b/REUSE.toml index 965e981dd9..ed835c94c3 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -33,6 +33,7 @@ path = [ "jsconfig.json", "l10n/*.js", "l10n/*.json", + "lib/Handler/Templates/footer.twig", "Makefile", "lib/Vendor/.gitkeep", "openapi-administration.json", diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php index 13d2e10d04..21f1933427 100644 --- a/lib/Controller/AdminController.php +++ b/lib/Controller/AdminController.php @@ -791,7 +791,7 @@ public function deleteTsaConfig(): DataResponse { * * Returns the current footer template if set, otherwise returns the default template. * - * @return DataResponse + * @return DataResponse * * 200: OK */ @@ -800,6 +800,8 @@ public function getFooterTemplate(): DataResponse { return new DataResponse([ 'template' => $this->footerService->getTemplate(), 'isDefault' => $this->footerService->isDefaultTemplate(), + 'preview_width' => $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_width', 595), + 'preview_height' => $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_height', 100), ]); } diff --git a/lib/Handler/FooterHandler.php b/lib/Handler/FooterHandler.php index 8d2864e88e..946311692d 100644 --- a/lib/Handler/FooterHandler.php +++ b/lib/Handler/FooterHandler.php @@ -36,8 +36,6 @@ class FooterHandler { private const MIN_QRCODE_SIZE = 100; private const POINT_TO_MILIMETER = 0.3527777778; - private TemplateVariables $templateVars; - public function __construct( private IAppConfig $appConfig, private PdfParserService $pdfParserService, @@ -45,8 +43,8 @@ public function __construct( private IL10N $l10n, private IFactory $l10nFactory, private ITempManager $tempManager, + private TemplateVariables $templateVars, ) { - $this->templateVars = new TemplateVariables(); } public function getFooter(array $dimensions): string { @@ -203,6 +201,10 @@ private function getQrCodeImageBase64(string $text): string { return $qrcode; } + public function getTemplateVariablesMetadata(): array { + return $this->templateVars->getVariablesMetadata(); + } + private function setQrCodeSize(): void { $blockValues = $this->getQrCodeBlocks(); $this->qrCode->setSize(self::MIN_QRCODE_SIZE); diff --git a/lib/Handler/TemplateVariables.php b/lib/Handler/TemplateVariables.php index b5cd786873..efd4f59bab 100644 --- a/lib/Handler/TemplateVariables.php +++ b/lib/Handler/TemplateVariables.php @@ -9,6 +9,8 @@ namespace OCA\Libresign\Handler; +use OCP\IL10N; + /** * @method self setDirection(string $value) * @method self setLinkToSite(string $value) @@ -31,21 +33,66 @@ */ class TemplateVariables { private array $variables = []; + private array $variablesMetadata = []; - /** - * Allowed template variable names with their expected types - */ - private const ALLOWED_VARIABLES = [ - 'direction' => 'string', - 'linkToSite' => 'string', - 'qrcode' => 'string', - 'qrcodeSize' => 'integer', - 'signedBy' => 'string', - 'signers' => 'array', - 'uuid' => 'string', - 'validateIn' => 'string', - 'validationSite' => 'string', - ]; + public function __construct( + private IL10N $l10n, + ) { + $this->initializeVariablesMetadata(); + } + + private function initializeVariablesMetadata(): void { + $this->variablesMetadata = [ + 'direction' => [ + 'type' => 'string', + 'description' => $this->l10n->t('Text direction for the footer (ltr or rtl based on language)'), + 'example' => 'ltr', + ], + 'linkToSite' => [ + 'type' => 'string', + 'description' => $this->l10n->t('Link to LibreSign or custom website'), + 'example' => 'https://libresign.coop', + 'default' => 'https://libresign.coop', + ], + 'qrcode' => [ + 'type' => 'string', + 'description' => $this->l10n->t('QR Code image in base64 format for document validation'), + 'example' => 'iVBORw0KGgoAAAANSUhEUgAA...', + ], + 'qrcodeSize' => [ + 'type' => 'integer', + 'description' => $this->l10n->t('QR Code size in pixels (includes margin)'), + 'example' => 108, + ], + 'signedBy' => [ + 'type' => 'string', + 'description' => $this->l10n->t('Message indicating the document was digitally signed'), + 'example' => 'Digitally signed by LibreSign.', + 'default' => $this->l10n->t('Digitally signed by LibreSign.'), + ], + 'signers' => [ + 'type' => 'array', + 'description' => $this->l10n->t('Array of signers with displayName and signed timestamp'), + 'example' => '[{"displayName": "John Doe", "signed": "2025-01-01T10:00:00Z"}]', + ], + 'uuid' => [ + 'type' => 'string', + 'description' => $this->l10n->t('Document unique identifier (UUID format)'), + 'example' => 'de0a18d4-fe65-4abc-bdd1-84e819700260', + ], + 'validateIn' => [ + 'type' => 'string', + 'description' => $this->l10n->t('Validation message template with placeholder'), + 'example' => 'Validate in %s.', + 'default' => $this->l10n->t('Validate in %s.', ['%s']), + ], + 'validationSite' => [ + 'type' => 'string', + 'description' => $this->l10n->t('Complete URL for document validation with UUID'), + 'example' => 'https://example.com/validation/de0a18d4-fe65-4abc-bdd1-84e819700260', + ], + ]; + } /** * @throws \InvalidArgumentException if trying to access non-whitelisted variable or wrong type @@ -70,13 +117,13 @@ public function __call(string $method, array $args): mixed { } private function ensureAllowed(string $key): void { - if (!array_key_exists($key, self::ALLOWED_VARIABLES)) { + if (!array_key_exists($key, $this->variablesMetadata)) { throw new \InvalidArgumentException("Template variable '{$key}' is not allowed"); } } private function ensureType(string $key, mixed $value): void { - $expected = self::ALLOWED_VARIABLES[$key]; + $expected = $this->variablesMetadata[$key]['type']; $actual = gettype($value); if ($actual !== $expected) { @@ -92,6 +139,15 @@ public function toArray(): array { return $this->variables; } + /** + * Get metadata for all available template variables + * + * @return array Associative array of variable metadata (name => config) + */ + public function getVariablesMetadata(): array { + return $this->variablesMetadata; + } + /** * Merge additional variables, validating against whitelist and types * diff --git a/lib/Handler/Templates/footer.twig b/lib/Handler/Templates/footer.twig index 6640dfd209..7a0bf52722 100644 --- a/lib/Handler/Templates/footer.twig +++ b/lib/Handler/Templates/footer.twig @@ -1,7 +1,3 @@ -{# - SPDX-FileCopyrightText: 2024 LibreCode coop and contributors - SPDX-License-Identifier: AGPL-3.0-or-later -#} {% if qrcode %} diff --git a/lib/Service/FooterService.php b/lib/Service/FooterService.php index 75b83cac7e..abbe7055d0 100644 --- a/lib/Service/FooterService.php +++ b/lib/Service/FooterService.php @@ -46,8 +46,18 @@ public function renderPreviewPdf(string $template = '', int $width = 595, int $h $this->saveTemplate($template); } + // Generate a realistic UUID format for preview (36 chars with hyphens, same as real UUIDs) + // This ensures QR code size matches the final document + $previewUuid = sprintf( + 'preview-%04x-%04x-%04x-%012x', + random_int(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0xffffffffffff) + ); + return $this->footerHandler - ->setTemplateVar('uuid', 'preview-' . bin2hex(random_bytes(8))) + ->setTemplateVar('uuid', $previewUuid) ->setTemplateVar('signers', [ [ 'displayName' => 'Preview Signer', @@ -56,4 +66,8 @@ public function renderPreviewPdf(string $template = '', int $width = 595, int $h ]) ->getFooter([['w' => $width, 'h' => $height]]); } + + public function getTemplateVariablesMetadata(): array { + return $this->footerHandler->getTemplateVariablesMetadata(); + } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 6456a3d0e6..917d2171e6 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -12,6 +12,7 @@ use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory; use OCA\Libresign\Service\CertificatePolicyService; +use OCA\Libresign\Service\FooterService; use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\SignatureBackgroundService; use OCA\Libresign\Service\SignatureTextService; @@ -32,6 +33,7 @@ public function __construct( private IAppConfig $appConfig, private SignatureTextService $signatureTextService, private SignatureBackgroundService $signatureBackgroundService, + private FooterService $footerService, ) { } public function getForm(): TemplateResponse { @@ -58,6 +60,12 @@ public function getForm(): TemplateResponse { $this->initialState->provideInitialState('signature_font_size', $this->signatureTextService->getSignatureFontSize()); $this->initialState->provideInitialState('signature_height', $this->signatureTextService->getFullSignatureHeight()); $this->initialState->provideInitialState('signature_preview_zoom_level', $this->appConfig->getValueFloat(Application::APP_ID, 'signature_preview_zoom_level', 100)); + $this->initialState->provideInitialState('footer_preview_zoom_level', $this->appConfig->getValueFloat(Application::APP_ID, 'footer_preview_zoom_level', 100)); + $this->initialState->provideInitialState('footer_preview_width', $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_width', 595)); + $this->initialState->provideInitialState('footer_preview_height', $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_height', 100)); + $this->initialState->provideInitialState('footer_template_variables', $this->footerService->getTemplateVariablesMetadata()); + $this->initialState->provideInitialState('footer_template', $this->footerService->getTemplate()); + $this->initialState->provideInitialState('footer_template_is_default', $this->footerService->isDefaultTemplate()); $this->initialState->provideInitialState('signature_render_mode', $this->signatureTextService->getRenderMode()); $this->initialState->provideInitialState('signature_text_template', $this->signatureTextService->getTemplate()); $this->initialState->provideInitialState('signature_width', $this->signatureTextService->getFullSignatureWidth()); diff --git a/openapi-administration.json b/openapi-administration.json index 37317baaab..12dda961f7 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -2664,7 +2664,9 @@ "type": "object", "required": [ "template", - "isDefault" + "isDefault", + "preview_width", + "preview_height" ], "properties": { "template": { @@ -2672,6 +2674,14 @@ }, "isDefault": { "type": "boolean" + }, + "preview_width": { + "type": "integer", + "format": "int64" + }, + "preview_height": { + "type": "integer", + "format": "int64" } } } diff --git a/openapi-full.json b/openapi-full.json index 12d1deaf8b..fed1aeaef5 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -11141,7 +11141,9 @@ "type": "object", "required": [ "template", - "isDefault" + "isDefault", + "preview_width", + "preview_height" ], "properties": { "template": { @@ -11149,6 +11151,14 @@ }, "isDefault": { "type": "boolean" + }, + "preview_width": { + "type": "integer", + "format": "int64" + }, + "preview_height": { + "type": "integer", + "format": "int64" } } } diff --git a/package-lock.json b/package-lock.json index f558404e30..cce6d544d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "agpl", "dependencies": { "@fontsource/dancing-script": "^5.2.8", - "@libresign/vue-pdf-editor": "^1.4.8", + "@libresign/vue-pdf-editor": "^1.5.0", "@marionebl/option": "^1.0.8", "@mdi/js": "^7.4.47", "@mdi/svg": "^7.4.47", @@ -31,6 +31,7 @@ "@nextcloud/vue": "^8.34.0", "@vueuse/core": "^11.3.0", "blueimp-md5": "^2.19.0", + "codemirror": "^5.65.20", "copy-webpack-plugin": "^13.0.1", "crypto-js": "^4.2.0", "debounce": "^2.2.0", @@ -39,6 +40,7 @@ "signature_pad": "^5.1.1", "vue": "^2.7.16", "vue-advanced-cropper": "^1.11.7", + "vue-codemirror": "^4.0.6", "vue-drag-resize": "^1.5.4", "vue-material-design-icons": "^5.3.1", "vue-router": "^3.6.5", @@ -2794,9 +2796,9 @@ "peer": true }, "node_modules/@libresign/vue-pdf-editor": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@libresign/vue-pdf-editor/-/vue-pdf-editor-1.4.8.tgz", - "integrity": "sha512-EU95u+MG97bjhAWDBUGqGRfjkK9lDmqvHTWUCbB6AmwU1bTEr5z385kZbpOBY+ES9e0XwN3kNt9JRAD3xLE+KA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@libresign/vue-pdf-editor/-/vue-pdf-editor-1.5.0.tgz", + "integrity": "sha512-O14H/+w2bROiAyVq5R9r5X9doWemCkhkxNFZNhxRZeAObqDIrTe5bmRPWKaKwesjSjIj6mv39SvXO4Vgk+Xg7w==", "license": "MIT", "dependencies": { "@cantoo/pdf-lib": "^2.2.3", @@ -6839,6 +6841,12 @@ "node": ">=0.10.0" } }, + "node_modules/codemirror": { + "version": "5.65.20", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.20.tgz", + "integrity": "sha512-i5dLDDxwkFCbhjvL2pNjShsojoL3XHyDwsGv1jqETUoW+lzpBKKqNTUWgQwVAOa0tUm4BwekT455ujafi8payA==", + "license": "MIT" + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -7690,6 +7698,12 @@ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", "license": "MIT" }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -17077,6 +17091,20 @@ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", "license": "MIT" }, + "node_modules/vue-codemirror": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/vue-codemirror/-/vue-codemirror-4.0.6.tgz", + "integrity": "sha512-ilU7Uf0mqBNSSV3KT7FNEeRIxH4s1fmpG4TfHlzvXn0QiQAbkXS9lLfwuZpaBVEnpP5CSE62iGJjoliTuA8poQ==", + "license": "MIT", + "dependencies": { + "codemirror": "^5.41.0", + "diff-match-patch": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/vue-color": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.2.tgz", diff --git a/package.json b/package.json index aea9215310..52bd520bde 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@fontsource/dancing-script": "^5.2.8", - "@libresign/vue-pdf-editor": "^1.4.8", + "@libresign/vue-pdf-editor": "^1.5.0", "@marionebl/option": "^1.0.8", "@mdi/js": "^7.4.47", "@mdi/svg": "^7.4.47", @@ -42,6 +42,7 @@ "@nextcloud/vue": "^8.34.0", "@vueuse/core": "^11.3.0", "blueimp-md5": "^2.19.0", + "codemirror": "^5.65.20", "copy-webpack-plugin": "^13.0.1", "crypto-js": "^4.2.0", "debounce": "^2.2.0", @@ -50,6 +51,7 @@ "signature_pad": "^5.1.1", "vue": "^2.7.16", "vue-advanced-cropper": "^1.11.7", + "vue-codemirror": "^4.0.6", "vue-drag-resize": "^1.5.4", "vue-material-design-icons": "^5.3.1", "vue-router": "^3.6.5", diff --git a/src/components/CodeEditor.vue b/src/components/CodeEditor.vue new file mode 100644 index 0000000000..72586adbb2 --- /dev/null +++ b/src/components/CodeEditor.vue @@ -0,0 +1,124 @@ + + + + + + diff --git a/src/components/FooterTemplateEditor.vue b/src/components/FooterTemplateEditor.vue new file mode 100644 index 0000000000..ed864a8a00 --- /dev/null +++ b/src/components/FooterTemplateEditor.vue @@ -0,0 +1,456 @@ + + + + + + + + diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index 12aa601ad7..702e288280 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -1427,6 +1427,10 @@ export interface operations { data: { template: string; isDefault: boolean; + /** Format: int64 */ + preview_width: number; + /** Format: int64 */ + preview_height: number; }; }; }; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 1183bf28a1..7dac34568d 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -5823,6 +5823,10 @@ export interface operations { data: { template: string; isDefault: boolean; + /** Format: int64 */ + preview_width: number; + /** Format: int64 */ + preview_height: number; }; }; }; diff --git a/src/views/Settings/Validation.vue b/src/views/Settings/Validation.vue index 84db88c2ad..c4255fcbe9 100644 --- a/src/views/Settings/Validation.vue +++ b/src/views/Settings/Validation.vue @@ -25,7 +25,7 @@ {{ t('libresign', 'Write QR code on footer with validation URL') }}

-

+

{{ t('libresign', 'To validate the signature of the documents. Only change this value if you want to replace the default validation URL with a different one.') }}

-

- +

+ + {{ t('libresign', 'Customize footer template') }} +

+ - diff --git a/tests/php/Unit/Handler/FooterHandlerTest.php b/tests/php/Unit/Handler/FooterHandlerTest.php index 5d4c6816e2..cdc099041f 100644 --- a/tests/php/Unit/Handler/FooterHandlerTest.php +++ b/tests/php/Unit/Handler/FooterHandlerTest.php @@ -8,6 +8,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Handler\FooterHandler; +use OCA\Libresign\Handler\TemplateVariables; use OCA\Libresign\Service\PdfParserService; use OCP\IAppConfig; use OCP\IL10N; @@ -30,11 +31,11 @@ public function setUp(): void { $this->pdfParserService = $this->createMock(PdfParserService::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->tempManager = \OCP\Server::get(ITempManager::class); - $this->l10nFactory = \OCP\Server::get(IFactory::class); } private function getClass(): FooterHandler { + $templateVars = new TemplateVariables($this->l10n); $this->footerHandler = new FooterHandler( $this->appConfig, $this->pdfParserService, @@ -42,6 +43,7 @@ private function getClass(): FooterHandler { $this->l10n, $this->l10nFactory, $this->tempManager, + $templateVars, ); return $this->footerHandler; } @@ -306,4 +308,18 @@ public function testGetTemplateReturnsDefaultWhenEmpty(): void { $defaultTemplate = file_get_contents(__DIR__ . '/../../../../lib/Handler/Templates/footer.twig'); $this->assertSame($defaultTemplate, $template); } + + public function testGetTemplateVariablesMetadata(): void { + $this->l10n = $this->l10nFactory->get(Application::APP_ID, 'en'); + $metadata = $this->getClass()->getTemplateVariablesMetadata(); + + $this->assertIsArray($metadata); + $this->assertCount(9, $metadata); + $this->assertArrayHasKey('direction', $metadata); + $this->assertArrayHasKey('uuid', $metadata); + $this->assertArrayHasKey('signedBy', $metadata); + $this->assertSame('string', $metadata['direction']['type']); + $this->assertSame('string', $metadata['uuid']['type']); + $this->assertArrayHasKey('default', $metadata['signedBy']); + } } diff --git a/tests/php/Unit/Handler/TemplateVariablesTest.php b/tests/php/Unit/Handler/TemplateVariablesTest.php index a070dabaed..aab56ec943 100644 --- a/tests/php/Unit/Handler/TemplateVariablesTest.php +++ b/tests/php/Unit/Handler/TemplateVariablesTest.php @@ -11,17 +11,23 @@ use OCA\Libresign\Handler\TemplateVariables; use OCA\Libresign\Tests\Unit\TestCase; +use OCP\IL10N; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\MockObject; /** * @internal */ final class TemplateVariablesTest extends TestCase { private TemplateVariables $variables; + private IL10N&MockObject $l10n; public function setUp(): void { parent::setUp(); - $this->variables = new TemplateVariables(); + $this->l10n = $this->createMock(IL10N::class); + $this->l10n->method('t')->willReturnCallback(fn ($text) => $text); + + $this->variables = new TemplateVariables($this->l10n); } public static function provideValidValues(): array { @@ -121,4 +127,59 @@ public function testRejectsInvalidMethod(): void { $this->variables->invalidMethod(); } + + public function testGetVariablesMetadataStructure(): void { + $metadata = $this->variables->getVariablesMetadata(); + + $this->assertIsArray($metadata); + $this->assertCount(9, $metadata); + + $expectedVariables = [ + 'direction', 'linkToSite', 'qrcode', 'qrcodeSize', + 'signedBy', 'signers', 'uuid', 'validateIn', 'validationSite' + ]; + foreach ($expectedVariables as $varName) { + $this->assertArrayHasKey($varName, $metadata); + $this->assertArrayHasKey('type', $metadata[$varName]); + $this->assertArrayHasKey('description', $metadata[$varName]); + $this->assertArrayHasKey('example', $metadata[$varName]); + } + } + + public static function provideVariablesWithDefaults(): array { + return [ + 'linkToSite' => ['linkToSite', 'https://libresign.coop'], + 'signedBy' => ['signedBy', 'Digitally signed by LibreSign.'], + 'validateIn' => ['validateIn', 'Validate in %s.'], + ]; + } + + #[DataProvider('provideVariablesWithDefaults')] + public function testMetadataDefaultValues(string $variable, string $expectedDefault): void { + $metadata = $this->variables->getVariablesMetadata(); + + $this->assertArrayHasKey('default', $metadata[$variable]); + $this->assertSame($expectedDefault, $metadata[$variable]['default']); + } + + public static function provideVariableTypes(): array { + return [ + 'direction is string' => ['direction', 'string'], + 'linkToSite is string' => ['linkToSite', 'string'], + 'qrcode is string' => ['qrcode', 'string'], + 'qrcodeSize is integer' => ['qrcodeSize', 'integer'], + 'signedBy is string' => ['signedBy', 'string'], + 'signers is array' => ['signers', 'array'], + 'uuid is string' => ['uuid', 'string'], + 'validateIn is string' => ['validateIn', 'string'], + 'validationSite is string' => ['validationSite', 'string'], + ]; + } + + #[DataProvider('provideVariableTypes')] + public function testMetadataTypes(string $variable, string $expectedType): void { + $metadata = $this->variables->getVariablesMetadata(); + + $this->assertSame($expectedType, $metadata[$variable]['type']); + } } diff --git a/tests/php/Unit/Service/FooterServiceTest.php b/tests/php/Unit/Service/FooterServiceTest.php index 35942758a0..372f9b652d 100644 --- a/tests/php/Unit/Service/FooterServiceTest.php +++ b/tests/php/Unit/Service/FooterServiceTest.php @@ -101,7 +101,7 @@ public function testRenderPreviewPdf(?string $template, int $width, int $height, ->method('setTemplateVar') ->willReturnCallback(function ($key, $value) { if ($key === 'uuid') { - $this->assertMatchesRegularExpression('/^preview-[a-f0-9]{16}$/', $value); + $this->assertMatchesRegularExpression('/^preview-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/', $value); } elseif ($key === 'signers') { $this->assertIsArray($value); $this->assertCount(1, $value); @@ -141,4 +141,28 @@ public static function provideRenderPreviewPdfScenarios(): array { 'large dimensions' => ['', 2000, 500, false], ]; } + + public function testGetTemplateVariablesMetadata(): void { + $expectedMetadata = [ + 'direction' => [ + 'type' => 'string', + 'description' => 'Text direction for the footer', + 'example' => 'ltr', + ], + 'uuid' => [ + 'type' => 'string', + 'description' => 'Document unique identifier', + 'example' => 'de0a18d4-fe65-4abc-bdd1-84e819700260', + ], + ]; + + $this->footerHandler + ->expects($this->once()) + ->method('getTemplateVariablesMetadata') + ->willReturn($expectedMetadata); + + $result = $this->service->getTemplateVariablesMetadata(); + + $this->assertSame($expectedMetadata, $result); + } } diff --git a/tests/php/Unit/Settings/AdminTest.php b/tests/php/Unit/Settings/AdminTest.php index 4f4b89aaa7..16da49a6fa 100644 --- a/tests/php/Unit/Settings/AdminTest.php +++ b/tests/php/Unit/Settings/AdminTest.php @@ -11,6 +11,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory; use OCA\Libresign\Service\CertificatePolicyService; +use OCA\Libresign\Service\FooterService; use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\SignatureBackgroundService; use OCA\Libresign\Service\SignatureTextService; @@ -31,6 +32,7 @@ final class AdminTest extends \OCA\Libresign\Tests\Unit\TestCase { private IAppConfig&MockObject $appConfig; private SignatureTextService&MockObject $signatureTextService; private SignatureBackgroundService&MockObject $signatureBackgroundService; + private FooterService&MockObject $footerService; public function setUp(): void { $this->initialState = $this->createMock(IInitialState::class); $this->identifyMethodService = $this->createMock(IdentifyMethodService::class); @@ -39,6 +41,7 @@ public function setUp(): void { $this->appConfig = $this->createMock(IAppConfig::class); $this->signatureTextService = $this->createMock(SignatureTextService::class); $this->signatureBackgroundService = $this->createMock(SignatureBackgroundService::class); + $this->footerService = $this->createMock(FooterService::class); $this->admin = new Admin( $this->initialState, $this->identifyMethodService, @@ -47,6 +50,7 @@ public function setUp(): void { $this->appConfig, $this->signatureTextService, $this->signatureBackgroundService, + $this->footerService, ); }