diff --git a/lib/Controller/RequestSignatureController.php b/lib/Controller/RequestSignatureController.php index 1849b646c9..7ef493ecfb 100644 --- a/lib/Controller/RequestSignatureController.php +++ b/lib/Controller/RequestSignatureController.php @@ -54,14 +54,14 @@ public function __construct( /** * Request signature * - * Request that a file be signed by a group of people. - * Each user in the users array can optionally include a 'signing_order' field + * Request that a file be signed by a list of signers. + * Each signer in the signers array can optionally include a 'signingOrder' field * to control the order of signatures when ordered signing flow is enabled. * When the created entity is an envelope (`nodeType` = `envelope`), * the returned `data` includes `filesCount` and `files` as a list of * envelope child files. * - * @param LibresignNewSigner[] $users Collection of users who must sign the document. Each user can have: identify, displayName, description, notify, signing_order + * @param LibresignNewSigner[] $signers Collection of signers who must sign the document. Each signer can have: identify, displayName, description, notify, signingOrder * @param string $name The name of file to sign * @param LibresignFolderSettings $settings Settings to define how and where the file should be stored * @param LibresignNewFile $file File object. @@ -79,8 +79,8 @@ public function __construct( #[RequireManager] #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])] public function request( - array $users, - string $name, + array $signers = [], + string $name = '', array $settings = [], array $file = [], array $files = [], @@ -96,7 +96,7 @@ public function request( $files, $name, $settings, - $users, + $signers, $status, $callback, $signatureFlow @@ -127,9 +127,9 @@ public function request( /** * Updates signatures data * - * Is necessary to inform the UUID of the file and a list of people + * It is necessary to inform the UUID of the file and a list of signers. * - * @param LibresignNewSigner[]|null $users Collection of users who must sign the document + * @param LibresignNewSigner[]|null $signers Collection of signers who must sign the document * @param string|null $uuid UUID of sign request. The signer UUID is what the person receives via email when asked to sign. This is not the file UUID. * @param LibresignVisibleElement[]|null $visibleElements Visible elements on document * @param LibresignNewFile|array|null $file File object. @@ -148,7 +148,7 @@ public function request( #[RequireManager] #[ApiRoute(verb: 'PATCH', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])] public function updateSign( - ?array $users = [], + ?array $signers = [], ?string $uuid = null, ?array $visibleElements = null, ?array $file = [], @@ -160,6 +160,7 @@ public function updateSign( ): DataResponse { try { $user = $this->userSession->getUser(); + $signers = is_array($signers) ? $signers : []; if (empty($uuid)) { return $this->createSignatureRequest( @@ -168,7 +169,7 @@ public function updateSign( $files, $name, $settings, - $users, + $signers, $status, null, $signatureFlow, @@ -179,7 +180,7 @@ public function updateSign( $data = [ 'uuid' => $uuid, 'file' => $file, - 'users' => $users, + 'signers' => $signers, 'userManager' => $user, 'status' => $status, 'visibleElements' => $visibleElements, @@ -221,7 +222,7 @@ private function createSignatureRequest( array $files, string $name, array $settings, - array $users, + array $signers, ?int $status, ?string $callback, ?string $signatureFlow, @@ -238,7 +239,7 @@ private function createSignatureRequest( $data = [ 'file' => $file, 'name' => $name, - 'users' => $users, + 'signers' => $signers, 'status' => $status, 'callback' => $callback, 'userManager' => $user, diff --git a/lib/Helper/ValidateHelper.php b/lib/Helper/ValidateHelper.php index 4b051083ac..09aa9dce63 100644 --- a/lib/Helper/ValidateHelper.php +++ b/lib/Helper/ValidateHelper.php @@ -535,19 +535,19 @@ public function validateFileStatus(array $data): void { } public function validateIdentifySigners(array $data): void { - if (empty($data['users'])) { + if (empty($data['signers'])) { return; } $this->validateSignersDataStructure($data); - foreach ($data['users'] as $signer) { + foreach ($data['signers'] as $signer) { $this->validateSignerData($signer); } } private function validateSignersDataStructure(array $data): void { - if (empty($data) || !array_key_exists('users', $data) || !is_array($data['users']) || empty($data['users'])) { + if (empty($data) || !array_key_exists('signers', $data) || !is_array($data['signers']) || empty($data['signers'])) { throw new LibresignException($this->l10n->t('No signers')); } } diff --git a/lib/Service/RequestSignatureService.php b/lib/Service/RequestSignatureService.php index 08d9ecfb5b..21fd767fc2 100644 --- a/lib/Service/RequestSignatureService.php +++ b/lib/Service/RequestSignatureService.php @@ -110,7 +110,7 @@ public function saveFiles(array $data): array { 'name' => $data['name'], 'userManager' => $data['userManager'], 'settings' => $data['settings'], - 'users' => $data['users'] ?? [], + 'signers' => $data['signers'] ?? [], 'status' => $data['status'] ?? FileStatus::DRAFT->value, 'visibleElements' => $data['visibleElements'] ?? [], 'signatureFlow' => $data['signatureFlow'] ?? null, @@ -136,15 +136,15 @@ public function save(array $data): FileEntity { } private function propagateSignersToChildren(FileEntity $envelope, array $data): void { - if ($envelope->getNodeType() !== 'envelope' || empty($data['users'])) { + if ($envelope->getNodeType() !== 'envelope' || empty($data['signers'])) { return; } $children = $this->fileMapper->getChildrenFiles($envelope->getId()); $dataWithoutNotification = $data; - foreach ($dataWithoutNotification['users'] as &$user) { - $user['notify'] = 0; + foreach ($dataWithoutNotification['signers'] as &$signer) { + $signer['notify'] = 0; } foreach ($children as $child) { @@ -189,7 +189,7 @@ public function saveEnvelope(array $data): array { $files[] = $fileEntity; } - if (!empty($data['users'])) { + if (!empty($data['signers'])) { $this->sequentialSigningService->setFile($envelope); $this->associateToSigners($data, $envelope); $this->propagateSignersToChildren($envelope, $data); @@ -447,7 +447,7 @@ private function removeExtensionFromName(string $name, array $metadata): string return $result ?? $name; } - private function deleteIdentifyMethodIfNotExits(array $users, FileEntity $file): void { + private function deleteIdentifyMethodIfNotExits(array $signers, FileEntity $file): void { $signRequests = $this->signRequestMapper->getByFileId($file->getId()); foreach ($signRequests as $key => $signRequest) { $identifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequest->getId()); @@ -458,7 +458,7 @@ private function deleteIdentifyMethodIfNotExits(array $users, FileEntity $file): foreach ($identifyMethods as $methodName => $list) { foreach ($list as $method) { $exists[$key]['identify'][$methodName] = $method->getEntity()->getIdentifierValue(); - if (!$this->identifyMethodExists($users, $method)) { + if (!$this->identifyMethodExists($signers, $method)) { $this->unassociateToUser($file->getId(), $signRequest->getId()); continue 3; } @@ -467,10 +467,10 @@ private function deleteIdentifyMethodIfNotExits(array $users, FileEntity $file): } } - private function identifyMethodExists(array $users, IIdentifyMethod $identifyMethod): bool { - foreach ($users as $user) { - if (!empty($user['identifyMethods'])) { - foreach ($user['identifyMethods'] as $data) { + private function identifyMethodExists(array $signers, IIdentifyMethod $identifyMethod): bool { + foreach ($signers as $signer) { + if (!empty($signer['identifyMethods'])) { + foreach ($signer['identifyMethods'] as $data) { if ($identifyMethod->getEntity()->getIdentifierKey() !== $data['method']) { continue; } @@ -479,7 +479,7 @@ private function identifyMethodExists(array $users, IIdentifyMethod $identifyMet } } } else { - foreach ($user['identify'] as $method => $value) { + foreach ($signer['identify'] as $method => $value) { if ($identifyMethod->getEntity()->getIdentifierKey() !== $method) { continue; } @@ -499,27 +499,27 @@ private function identifyMethodExists(array $users, IIdentifyMethod $identifyMet */ private function associateToSigners(array $data, FileEntity $file): array { $return = []; - if (!empty($data['users'])) { - $this->deleteIdentifyMethodIfNotExits($data['users'], $file); + if (!empty($data['signers'])) { + $this->deleteIdentifyMethodIfNotExits($data['signers'], $file); $this->identifyMethod->clearCache(); $this->sequentialSigningService->resetOrderCounter(); $fileStatus = $data['status'] ?? null; - foreach ($data['users'] as $user) { - $userProvidedOrder = isset($user['signingOrder']) ? (int)$user['signingOrder'] : null; + foreach ($data['signers'] as $signer) { + $userProvidedOrder = isset($signer['signingOrder']) ? (int)$signer['signingOrder'] : null; $signingOrder = $this->sequentialSigningService->determineSigningOrder($userProvidedOrder); - $signerStatus = $user['status'] ?? null; - $shouldNotify = !isset($user['notify']) || $user['notify'] !== 0; + $signerStatus = $signer['status'] ?? null; + $shouldNotify = !isset($signer['notify']) || $signer['notify'] !== 0; - if (isset($user['identifyMethods'])) { - foreach ($user['identifyMethods'] as $identifyMethod) { + if (isset($signer['identifyMethods'])) { + foreach ($signer['identifyMethods'] as $identifyMethod) { $return[] = $this->signRequestService->createOrUpdateSignRequest( identifyMethods: [ $identifyMethod['method'] => $identifyMethod['value'], ], - displayName: $user['displayName'] ?? '', - description: $user['description'] ?? '', + displayName: $signer['displayName'] ?? '', + description: $signer['description'] ?? '', notify: $shouldNotify, fileId: $file->getId(), signingOrder: $signingOrder, @@ -529,9 +529,9 @@ private function associateToSigners(array $data, FileEntity $file): array { } } else { $return[] = $this->signRequestService->createOrUpdateSignRequest( - identifyMethods: $user['identify'], - displayName: $user['displayName'] ?? '', - description: $user['description'] ?? '', + identifyMethods: $signer['identify'], + displayName: $signer['displayName'] ?? '', + description: $signer['description'] ?? '', notify: $shouldNotify, fileId: $file->getId(), signingOrder: $signingOrder, @@ -574,7 +574,7 @@ private function saveVisibleElements(array $data, FileEntity $file): array { public function validateNewRequestToFile(array $data): void { $this->validateNewFile($data); - $this->validateUsers($data); + $this->validateSigners($data); $this->validateHelper->validateFileStatus($data); } @@ -585,22 +585,22 @@ public function validateNewFile(array $data): void { $this->validateHelper->validateNewFile($data); } - public function validateUsers(array $data): void { - if (empty($data['users'])) { + public function validateSigners(array $data): void { + if (empty($data['signers'])) { if (($data['status'] ?? FileStatus::ABLE_TO_SIGN->value) === FileStatus::DRAFT->value) { return; } - throw new \Exception($this->l10n->t('Empty users list')); + throw new \Exception($this->l10n->t('Empty signers list')); } - if (!is_array($data['users'])) { - // TRANSLATION This message will be displayed when the request to API with the key users has a value that is not an array - throw new \Exception($this->l10n->t('User list needs to be an array')); + if (!is_array($data['signers'])) { + // TRANSLATION This message will be displayed when the request to API with the key signers has a value that is not an array + throw new \Exception($this->l10n->t('Signers list needs to be an array')); } - foreach ($data['users'] as $user) { - if (!array_key_exists('identify', $user)) { + foreach ($data['signers'] as $signer) { + if (!array_key_exists('identify', $signer)) { throw new \Exception('Identify key not found'); } - $this->identifyMethod->setAllEntityData($user); + $this->identifyMethod->setAllEntityData($signer); } } diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 5ead004ed3..cd78ced76a 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -139,16 +139,16 @@ public function canDeleteRequestSignature(array $data): void { if ($signed) { throw new \Exception($this->l10n->t('Document already signed')); } - array_walk($data['users'], function ($user) use ($signatures): void { - $exists = array_filter($signatures, function (SignRequestEntity $signRequest) use ($user) { + array_walk($data['signers'], function ($signer) use ($signatures): void { + $exists = array_filter($signatures, function (SignRequestEntity $signRequest) use ($signer) { $identifyMethod = $this->identifyMethodService->getIdentifiedMethod($signRequest->getId()); if ($identifyMethod->getName() === 'email') { - return $identifyMethod->getEntity()->getIdentifierValue() === $user['email']; + return $identifyMethod->getEntity()->getIdentifierValue() === $signer['email']; } return false; }); if (!$exists) { - throw new \Exception($this->l10n->t('No signature was requested to %s', $user['email'])); + throw new \Exception($this->l10n->t('No signature was requested to %s', $signer['email'])); } }); } diff --git a/openapi-full.json b/openapi-full.json index d78c7f193d..c4c058fb37 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -7628,7 +7628,7 @@ "post": { "operationId": "request_signature-request", "summary": "Request signature", - "description": "Request that a file be signed by a group of people. Each user in the users array can optionally include a 'signing_order' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files.", + "description": "Request that a file be signed by a list of signers. Each signer in the signers array can optionally include a 'signingOrder' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files.", "tags": [ "request_signature" ], @@ -7641,25 +7641,23 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "users", - "name" - ], "properties": { - "users": { + "signers": { "type": "array", - "description": "Collection of users who must sign the document. Each user can have: identify, displayName, description, notify, signing_order", + "default": [], + "description": "Collection of signers who must sign the document. Each signer can have: identify, displayName, description, notify, signingOrder", "items": { "$ref": "#/components/schemas/NewSigner" } }, "name": { "type": "string", + "default": "", "description": "The name of file to sign" }, "settings": { @@ -7818,7 +7816,7 @@ "patch": { "operationId": "request_signature-update-sign", "summary": "Updates signatures data", - "description": "Is necessary to inform the UUID of the file and a list of people", + "description": "It is necessary to inform the UUID of the file and a list of signers.", "tags": [ "request_signature" ], @@ -7837,11 +7835,11 @@ "schema": { "type": "object", "properties": { - "users": { + "signers": { "type": "array", "nullable": true, "default": [], - "description": "Collection of users who must sign the document", + "description": "Collection of signers who must sign the document", "items": { "$ref": "#/components/schemas/NewSigner" } diff --git a/openapi.json b/openapi.json index ea4fe6d3f4..78b02034b8 100644 --- a/openapi.json +++ b/openapi.json @@ -7478,7 +7478,7 @@ "post": { "operationId": "request_signature-request", "summary": "Request signature", - "description": "Request that a file be signed by a group of people. Each user in the users array can optionally include a 'signing_order' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files.", + "description": "Request that a file be signed by a list of signers. Each signer in the signers array can optionally include a 'signingOrder' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files.", "tags": [ "request_signature" ], @@ -7491,25 +7491,23 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "users", - "name" - ], "properties": { - "users": { + "signers": { "type": "array", - "description": "Collection of users who must sign the document. Each user can have: identify, displayName, description, notify, signing_order", + "default": [], + "description": "Collection of signers who must sign the document. Each signer can have: identify, displayName, description, notify, signingOrder", "items": { "$ref": "#/components/schemas/NewSigner" } }, "name": { "type": "string", + "default": "", "description": "The name of file to sign" }, "settings": { @@ -7668,7 +7666,7 @@ "patch": { "operationId": "request_signature-update-sign", "summary": "Updates signatures data", - "description": "Is necessary to inform the UUID of the file and a list of people", + "description": "It is necessary to inform the UUID of the file and a list of signers.", "tags": [ "request_signature" ], @@ -7687,11 +7685,11 @@ "schema": { "type": "object", "properties": { - "users": { + "signers": { "type": "array", "nullable": true, "default": [], - "description": "Collection of users who must sign the document", + "description": "Collection of signers who must sign the document", "items": { "$ref": "#/components/schemas/NewSigner" } diff --git a/src/components/PdfEditor/PdfEditor.vue b/src/components/PdfEditor/PdfEditor.vue index 20d0ab0605..a9be3a45f3 100644 --- a/src/components/PdfEditor/PdfEditor.vue +++ b/src/components/PdfEditor/PdfEditor.vue @@ -254,15 +254,21 @@ export default { cancelAdding() { this.$refs.pdfElements?.cancelAdding() }, - addSigner(signer) { + async addSigner(signer) { + const pdfElements = this.$refs.pdfElements + if (!pdfElements) { + return + } + const docIndex = signer.element.documentIndex !== undefined ? signer.element.documentIndex - : this.$refs.pdfElements.selectedDocIndex + : pdfElements.selectedDocIndex const pageIndex = signer.element.coordinates.page - 1 + await this.waitForPageRender(docIndex, pageIndex) + const coordinates = signer.element.coordinates || {} - const pdfElements = this.$refs.pdfElements const pageHeight = pdfElements?.getPageHeight?.(docIndex, pageIndex) || 0 const width = Number.isFinite(coordinates.width) ? coordinates.width @@ -298,11 +304,17 @@ export default { y, } - pdfElements.addObjectToPage( - object, - pageIndex, - docIndex, - ) + pdfElements.addObjectToPage(object, pageIndex, docIndex) + }, + async waitForPageRender(docIndex, pageIndex) { + const pdfElements = this.$refs.pdfElements + const doc = pdfElements?.pdfDocuments?.[docIndex] + if (!doc?.pages?.[pageIndex]) { + return + } + await doc.pages[pageIndex] + await this.$nextTick() + await this.$nextTick() }, }, } diff --git a/src/components/Request/VisibleElements.vue b/src/components/Request/VisibleElements.vue index 8a4d3dd180..e489e90b02 100644 --- a/src/components/Request/VisibleElements.vue +++ b/src/components/Request/VisibleElements.vue @@ -87,6 +87,14 @@ import Signer from '../Signers/Signer.vue' import { FILE_STATUS } from '../../constants.js' import { useFilesStore } from '../../store/files.js' +import { + aggregateVisibleElementsByFiles, + findFileById, + getFileSigners, + getFileUrl, + getVisibleElementsFromDocument, + idsMatch, +} from '../../services/visibleElementsService.js' export default { name: 'VisibleElements', @@ -133,7 +141,7 @@ export default { }, pdfFiles() { return (this.document.files || []) - .map(file => file?.file) + .map(file => getFileUrl(file)) .filter(Boolean) }, pdfFileNames() { @@ -217,21 +225,16 @@ export default { const allVisibleElements = this.aggregateVisibleElementsByFiles(this.document.files) if (allVisibleElements.length > 0) { this.document.visibleElements = allVisibleElements + return + } + + const nestedDocumentElements = getVisibleElementsFromDocument(this.document) + if (nestedDocumentElements.length > 0) { + this.document.visibleElements = nestedDocumentElements } }, aggregateVisibleElementsByFiles(files) { - if (!Array.isArray(files) || files.length === 0) { - return [] - } - - const allVisibleElements = [] - files.forEach(file => { - if (Array.isArray(file?.visibleElements)) { - allVisibleElements.push(...file.visibleElements) - } - }) - - return allVisibleElements + return aggregateVisibleElementsByFiles(files) }, buildFilePagesMap() { this.filePagesMap = {} @@ -276,20 +279,20 @@ export default { const pdfElements = this.getPdfElements() const fileIndexById = new Map( - filesToProcess.map((file, index) => [file.id, index]), + filesToProcess.map((file, index) => [String(file.id), index]), ) - const elements = Array.isArray(this.document.visibleElements) ? this.document.visibleElements : [] + const elements = getVisibleElementsFromDocument(this.document) const elementsByDoc = new Map() elements.forEach(element => { - const fileInfo = filesToProcess.find(f => f.id === element.fileId) + const fileInfo = findFileById(filesToProcess, element.fileId) if (!fileInfo) { return } - const docIndex = fileIndexById.get(element.fileId) + const docIndex = fileIndexById.get(String(element.fileId)) if (docIndex === undefined) { return } - const signer = (fileInfo.signers || []).find(s => s.signRequestId === element.signRequestId) + const signer = getFileSigners(fileInfo).find((s) => idsMatch(s.signRequestId, element.signRequestId)) if (!signer) { return } diff --git a/src/services/visibleElementsService.js b/src/services/visibleElementsService.js new file mode 100644 index 0000000000..58a71911cd --- /dev/null +++ b/src/services/visibleElementsService.js @@ -0,0 +1,85 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreSign contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +const keyOf = (value) => { + if (value === null || value === undefined) { + return '' + } + return String(value) +} + +const deduplicateVisibleElements = (elements) => { + const seen = new Set() + const unique = [] + elements.forEach((element) => { + if (!element || typeof element !== 'object') { + return + } + const signature = [ + keyOf(element.elementId), + keyOf(element.fileId), + keyOf(element.signRequestId), + keyOf(element.type), + keyOf(element.coordinates?.page), + keyOf(element.coordinates?.left), + keyOf(element.coordinates?.top), + ].join('|') + if (seen.has(signature)) { + return + } + seen.add(signature) + unique.push(element) + }) + return unique +} + +const collectSignerVisibleElements = (signers) => { + if (!Array.isArray(signers)) { + return [] + } + return signers.flatMap((signer) => Array.isArray(signer?.visibleElements) ? signer.visibleElements : []) +} + +export const idsMatch = (left, right) => keyOf(left) === keyOf(right) + +export const getFileUrl = (file) => file?.file || file?.files?.[0]?.file || null + +export const getFileSigners = (file) => { + if (Array.isArray(file?.signers) && file.signers.length > 0) { + return file.signers + } + if (Array.isArray(file?.files?.[0]?.signers)) { + return file.files[0].signers + } + return [] +} + +export const getVisibleElementsFromDocument = (document) => { + const topLevel = Array.isArray(document?.visibleElements) ? document.visibleElements : [] + const signers = Array.isArray(document?.signers) ? document.signers : [] + const nested = collectSignerVisibleElements(signers) + return deduplicateVisibleElements([...topLevel, ...nested]) +} + +export const getVisibleElementsFromFile = (file) => { + const topLevel = Array.isArray(file?.visibleElements) ? file.visibleElements : [] + const nested = collectSignerVisibleElements(getFileSigners(file)) + return deduplicateVisibleElements([...topLevel, ...nested]) +} + +export const aggregateVisibleElementsByFiles = (files) => { + if (!Array.isArray(files) || files.length === 0) { + return [] + } + const all = files.flatMap(getVisibleElementsFromFile) + return deduplicateVisibleElements(all) +} + +export const findFileById = (files, fileId) => { + if (!Array.isArray(files)) { + return null + } + return files.find((file) => idsMatch(file?.id, fileId)) || null +} diff --git a/src/store/files.js b/src/store/files.js index b7ac8c7402..aaeebc5d08 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -639,7 +639,7 @@ export const useFilesStore = function(...args) { method: uuid || file.id ? 'patch' : 'post', data: { name: file?.name, - users: signers || file?.signers || [], + signers: signers || file?.signers || [], visibleElements, status, signatureFlow: flowValue, diff --git a/src/tests/components/PdfEditor/PdfEditor.spec.js b/src/tests/components/PdfEditor/PdfEditor.spec.js index f26e2aa5fb..33fdb2f61e 100644 --- a/src/tests/components/PdfEditor/PdfEditor.spec.js +++ b/src/tests/components/PdfEditor/PdfEditor.spec.js @@ -223,10 +223,13 @@ describe('PdfEditor Component - Business Rules', () => { selectedDocIndex: 0, getPageHeight: vi.fn(() => 841.89), addObjectToPage: vi.fn(), + pdfDocuments: [ + { pages: [Promise.resolve({})] }, + ], } }) - it('converts coordinates from left/top format', () => { + it('converts coordinates from left/top format', async () => { const signer = { element: { documentIndex: 0, @@ -240,7 +243,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledWith( expect.objectContaining({ @@ -254,7 +257,7 @@ describe('PdfEditor Component - Business Rules', () => { ) }) - it('converts coordinates from llx/lly/urx/ury format to x/y', () => { + it('converts coordinates from llx/lly/urx/ury format to x/y', async () => { const signer = { element: { documentIndex: 0, @@ -268,7 +271,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) const call = wrapper.vm.$refs.pdfElements.addObjectToPage.mock.calls[0][0] @@ -277,7 +280,7 @@ describe('PdfEditor Component - Business Rules', () => { expect(call.x).toBe(50) // llx }) - it('calculates y from ury when using PDF coordinates', () => { + it('calculates y from ury when using PDF coordinates', async () => { const pageHeight = 841.89 wrapper.vm.$refs.pdfElements.getPageHeight.mockReturnValue(pageHeight) @@ -294,7 +297,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) const call = wrapper.vm.$refs.pdfElements.addObjectToPage.mock.calls[0][0] @@ -302,7 +305,7 @@ describe('PdfEditor Component - Business Rules', () => { expect(call.y).toBeCloseTo(141.89, 2) }) - it('uses default coordinates when missing', () => { + it('uses default coordinates when missing', async () => { const signer = { element: { documentIndex: 0, @@ -312,7 +315,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledWith( expect.objectContaining({ @@ -326,7 +329,11 @@ describe('PdfEditor Component - Business Rules', () => { ) }) - it('uses correct page index (page - 1)', () => { + it('uses correct page index (page - 1)', async () => { + wrapper.vm.$refs.pdfElements.pdfDocuments = [ + { pages: [Promise.resolve({}), Promise.resolve({}), Promise.resolve({}), Promise.resolve({}), Promise.resolve({})] }, + ] + const signer = { element: { documentIndex: 0, @@ -340,7 +347,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledWith( expect.anything(), @@ -349,8 +356,13 @@ describe('PdfEditor Component - Business Rules', () => { ) }) - it('uses selectedDocIndex when documentIndex not specified', () => { + it('uses selectedDocIndex when documentIndex not specified', async () => { wrapper.vm.$refs.pdfElements.selectedDocIndex = 2 + wrapper.vm.$refs.pdfElements.pdfDocuments = [ + { pages: [Promise.resolve({})] }, + { pages: [Promise.resolve({})] }, + { pages: [Promise.resolve({})] }, + ] const signer = { element: { @@ -364,7 +376,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledWith( expect.anything(), @@ -373,7 +385,7 @@ describe('PdfEditor Component - Business Rules', () => { ) }) - it('generates unique object ID', () => { + it('generates unique object ID', async () => { const signer = { element: { documentIndex: 0, @@ -381,14 +393,14 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) const call = wrapper.vm.$refs.pdfElements.addObjectToPage.mock.calls[0][0] expect(call.id).toMatch(/^obj-\d+-[a-z0-9]{6}$/) }) - it('includes signer data in object', () => { + it('includes signer data in object', async () => { const signer = { email: 'test@example.com', displayName: 'Test User', @@ -398,7 +410,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledWith( expect.objectContaining({ @@ -649,6 +661,142 @@ describe('PdfEditor Component - Business Rules', () => { }) }) + describe('RULE: waitForPageRender awaits page Promise', () => { + it('resolves immediately when page promise is already resolved', async () => { + wrapper.vm.$refs.pdfElements = { + pdfDocuments: [ + { pages: [Promise.resolve({})] }, + ], + } + + await wrapper.vm.waitForPageRender(0, 0) + }) + + it('waits for a pending page promise before resolving', async () => { + let resolveSecondPage + const secondPagePromise = new Promise(resolve => { resolveSecondPage = resolve }) + + wrapper.vm.$refs.pdfElements = { + pdfDocuments: [ + { pages: [Promise.resolve({})] }, + { pages: [secondPagePromise] }, + ], + } + + let resolved = false + const promise = wrapper.vm.waitForPageRender(1, 0).then(() => { resolved = true }) + + await wrapper.vm.$nextTick() + expect(resolved).toBe(false) + + resolveSecondPage({}) + await promise + expect(resolved).toBe(true) + }) + + it('resolves immediately when document does not exist', async () => { + wrapper.vm.$refs.pdfElements = { + pdfDocuments: [], + } + + await wrapper.vm.waitForPageRender(5, 0) + }) + + it('resolves immediately when pdfElements is null', async () => { + wrapper.vm.$refs.pdfElements = null + + await wrapper.vm.waitForPageRender(0, 0) + }) + }) + + describe('RULE: addSigner awaits page render for multi-document envelopes', () => { + it('awaits second document page before adding element', async () => { + let resolveSecondPage + const secondPagePromise = new Promise(resolve => { resolveSecondPage = resolve }) + + wrapper.vm.$refs.pdfElements = { + ...pdfElementsMethods, + selectedDocIndex: 0, + getPageHeight: vi.fn(() => 841.89), + addObjectToPage: vi.fn(), + pdfDocuments: [ + { pages: [Promise.resolve({})] }, + { pages: [secondPagePromise] }, + ], + } + + const signer = { + displayName: 'admin', + element: { + documentIndex: 1, + coordinates: { + page: 1, + left: 148, + top: 16, + width: 350, + height: 100, + }, + }, + } + + const addPromise = wrapper.vm.addSigner(signer) + + await wrapper.vm.$nextTick() + expect(wrapper.vm.$refs.pdfElements.addObjectToPage).not.toHaveBeenCalled() + + resolveSecondPage({}) + await addPromise + + expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledWith( + expect.objectContaining({ + x: 148, + y: 16, + width: 350, + height: 100, + }), + 0, + 1, + ) + }) + + it('adds immediately when page is already rendered', async () => { + wrapper.vm.$refs.pdfElements = { + ...pdfElementsMethods, + selectedDocIndex: 0, + getPageHeight: vi.fn(() => 841.89), + addObjectToPage: vi.fn(), + pdfDocuments: [ + { pages: [Promise.resolve({})] }, + { pages: [Promise.resolve({})] }, + ], + } + + const signer = { + element: { + documentIndex: 1, + coordinates: { page: 1, left: 100, top: 50, width: 200, height: 80 }, + }, + } + + await wrapper.vm.addSigner(signer) + + expect(wrapper.vm.$refs.pdfElements.addObjectToPage).toHaveBeenCalledTimes(1) + }) + + it('does nothing when pdfElements is null', async () => { + wrapper.vm.$refs.pdfElements = null + + const signer = { + element: { + documentIndex: 0, + coordinates: { page: 1 }, + }, + } + + await wrapper.vm.addSigner(signer) + }) + }) + describe('RULE: readOnly prop behavior', () => { it('passes readOnly to PDFElements', async () => { await wrapper.setProps({ readOnly: true }) @@ -665,10 +813,13 @@ describe('PdfEditor Component - Business Rules', () => { selectedDocIndex: 0, getPageHeight: vi.fn(() => 841.89), // A4 height in points addObjectToPage: vi.fn(), + pdfDocuments: [ + { pages: [Promise.resolve({})] }, + ], } }) - it('handles bottom-left origin PDF coordinates correctly', () => { + it('handles bottom-left origin PDF coordinates correctly', async () => { // PDF uses bottom-left origin, web uses top-left const pageHeight = 841.89 const signer = { @@ -684,7 +835,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) const call = wrapper.vm.$refs.pdfElements.addObjectToPage.mock.calls[0][0] @@ -693,7 +844,7 @@ describe('PdfEditor Component - Business Rules', () => { expect(call.height).toBe(100) // ury - lly }) - it('ensures y coordinate never negative', () => { + it('ensures y coordinate never negative', async () => { const signer = { element: { documentIndex: 0, @@ -707,7 +858,7 @@ describe('PdfEditor Component - Business Rules', () => { }, } - wrapper.vm.addSigner(signer) + await wrapper.vm.addSigner(signer) const call = wrapper.vm.$refs.pdfElements.addObjectToPage.mock.calls[0][0] diff --git a/src/tests/components/Request/VisibleElements.spec.js b/src/tests/components/Request/VisibleElements.spec.js index a7f80db528..675bb2594e 100644 --- a/src/tests/components/Request/VisibleElements.spec.js +++ b/src/tests/components/Request/VisibleElements.spec.js @@ -340,6 +340,18 @@ describe('VisibleElements Component - Business Rules', () => { expect(wrapper.vm.pdfFiles).toEqual([]) }) + + it('extracts file objects from nested files array payload', () => { + const file1 = { path: '/path/to/file1.pdf' } + const file2 = { path: '/path/to/file2.pdf' } + + filesStore.files[1].files = [ + { id: 10, files: [{ file: file1 }] }, + { id: 20, files: [{ file: file2 }] }, + ] + + expect(wrapper.vm.pdfFiles).toEqual([file1, file2]) + }) }) describe('RULE: page height retrieval', () => { @@ -698,5 +710,53 @@ describe('VisibleElements Component - Business Rules', () => { expect(wrapper.vm.document.files).toEqual(childFiles) expect(wrapper.vm.document.visibleElements).toEqual(expectedVisibleElements) }) + + it('uses signer visibleElements when file-level visibleElements is empty', async () => { + filesStore.files[1].id = 544 + filesStore.files[1].files = [] + filesStore.files[1].visibleElements = [] + + const childFiles = [ + { + id: 545, + name: 'file1.pdf', + visibleElements: [], + signers: [ + { + signRequestId: 603, + visibleElements: [{ elementId: 185, fileId: 545, signRequestId: 603 }], + }, + ], + }, + { + id: 546, + name: 'file2.pdf', + visibleElements: [], + signers: [ + { + signRequestId: 604, + visibleElements: [{ elementId: 186, fileId: 546, signRequestId: 604 }], + }, + ], + }, + ] + + axios.get.mockResolvedValue({ + data: { + ocs: { + data: { + data: childFiles, + }, + }, + }, + }) + + await wrapper.vm.fetchFiles() + + expect(wrapper.vm.document.visibleElements).toEqual([ + { elementId: 185, fileId: 545, signRequestId: 603 }, + { elementId: 186, fileId: 546, signRequestId: 604 }, + ]) + }) }) }) diff --git a/src/tests/store/files.spec.js b/src/tests/store/files.spec.js index 2ff90ecd8f..73cc72e291 100644 --- a/src/tests/store/files.spec.js +++ b/src/tests/store/files.spec.js @@ -913,6 +913,25 @@ describe('files store - critical business rules', () => { }) describe('RULE: saveOrUpdateSignatureRequest payload rules', () => { + it('sends signers field as canonical payload', async () => { + const store = useFilesStore() + store.selectedFileId = 1 + store.files[1] = { + id: 1, + name: 'contract.pdf', + signatureFlow: 'parallel', + signers: [{ email: 'signer@example.com' }], + } + axios.mockResolvedValue({ + data: { ocs: { data: { id: 1, nodeId: 99, signatureFlow: 'parallel', signers: [] } } }, + }) + + await store.saveOrUpdateSignatureRequest({ status: 1 }) + + const config = axios.mock.calls[0][0] + expect(config.data.signers).toEqual([{ email: 'signer@example.com' }]) + }) + it('maps numeric signatureFlow to ordered_numeric', async () => { const store = useFilesStore() store.selectedFileId = 1 diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 03b059b57b..b40b1621ab 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -863,7 +863,7 @@ export type paths = { put?: never; /** * Request signature - * @description Request that a file be signed by a group of people. Each user in the users array can optionally include a 'signing_order' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files. + * @description Request that a file be signed by a list of signers. Each signer in the signers array can optionally include a 'signingOrder' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files. */ post: operations["request_signature-request"]; delete?: never; @@ -871,7 +871,7 @@ export type paths = { head?: never; /** * Updates signatures data - * @description Is necessary to inform the UUID of the file and a list of people + * @description It is necessary to inform the UUID of the file and a list of signers. */ patch: operations["request_signature-update-sign"]; trace?: never; @@ -4474,13 +4474,19 @@ export interface operations { }; cookie?: never; }; - requestBody: { + requestBody?: { content: { "application/json": { - /** @description Collection of users who must sign the document. Each user can have: identify, displayName, description, notify, signing_order */ - users: components["schemas"]["NewSigner"][]; - /** @description The name of file to sign */ - name: string; + /** + * @description Collection of signers who must sign the document. Each signer can have: identify, displayName, description, notify, signingOrder + * @default [] + */ + signers?: components["schemas"]["NewSigner"][]; + /** + * @description The name of file to sign + * @default + */ + name?: string; /** * @description Settings to define how and where the file should be stored * @default [] @@ -4564,10 +4570,10 @@ export interface operations { content: { "application/json": { /** - * @description Collection of users who must sign the document + * @description Collection of signers who must sign the document * @default [] */ - users?: components["schemas"]["NewSigner"][] | null; + signers?: components["schemas"]["NewSigner"][] | null; /** @description UUID of sign request. The signer UUID is what the person receives via email when asked to sign. This is not the file UUID. */ uuid?: string | null; /** @description Visible elements on document */ diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 8173efbb90..28a4916917 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -863,7 +863,7 @@ export type paths = { put?: never; /** * Request signature - * @description Request that a file be signed by a group of people. Each user in the users array can optionally include a 'signing_order' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files. + * @description Request that a file be signed by a list of signers. Each signer in the signers array can optionally include a 'signingOrder' field to control the order of signatures when ordered signing flow is enabled. When the created entity is an envelope (`nodeType` = `envelope`), the returned `data` includes `filesCount` and `files` as a list of envelope child files. */ post: operations["request_signature-request"]; delete?: never; @@ -871,7 +871,7 @@ export type paths = { head?: never; /** * Updates signatures data - * @description Is necessary to inform the UUID of the file and a list of people + * @description It is necessary to inform the UUID of the file and a list of signers. */ patch: operations["request_signature-update-sign"]; trace?: never; @@ -3955,13 +3955,19 @@ export interface operations { }; cookie?: never; }; - requestBody: { + requestBody?: { content: { "application/json": { - /** @description Collection of users who must sign the document. Each user can have: identify, displayName, description, notify, signing_order */ - users: components["schemas"]["NewSigner"][]; - /** @description The name of file to sign */ - name: string; + /** + * @description Collection of signers who must sign the document. Each signer can have: identify, displayName, description, notify, signingOrder + * @default [] + */ + signers?: components["schemas"]["NewSigner"][]; + /** + * @description The name of file to sign + * @default + */ + name?: string; /** * @description Settings to define how and where the file should be stored * @default [] @@ -4045,10 +4051,10 @@ export interface operations { content: { "application/json": { /** - * @description Collection of users who must sign the document + * @description Collection of signers who must sign the document * @default [] */ - users?: components["schemas"]["NewSigner"][] | null; + signers?: components["schemas"]["NewSigner"][] | null; /** @description UUID of sign request. The signer UUID is what the person receives via email when asked to sign. This is not the file UUID. */ uuid?: string | null; /** @description Visible elements on document */ diff --git a/src/views/SignPDF/SignPDF.vue b/src/views/SignPDF/SignPDF.vue index a60e748401..82222ff950 100644 --- a/src/views/SignPDF/SignPDF.vue +++ b/src/views/SignPDF/SignPDF.vue @@ -49,6 +49,14 @@ import { useSignStore } from '../../store/sign.js' import { generateOcsUrl, generateUrl } from '@nextcloud/router' import axios from '@nextcloud/axios' import { FILE_STATUS } from '../../constants.js' +import { + aggregateVisibleElementsByFiles, + findFileById, + getFileSigners, + getFileUrl, + getVisibleElementsFromDocument, + idsMatch, +} from '../../services/visibleElementsService.js' export default { name: 'SignPDF', @@ -216,7 +224,7 @@ export default { } const urls = envelopeFiles - .map(file => file.files?.[0]?.file) + .map(file => getFileUrl(file)) .filter(Boolean) if (!urls.length) { this.signStore.errors = [{ message: t('libresign', 'Failed to load envelope files') }] @@ -249,13 +257,14 @@ export default { updateSigners(data) { if (this.signStore.document.nodeType === 'envelope' && this.envelopeFiles.length > 0) { const fileIndexById = new Map( - this.envelopeFiles.map((file, index) => [file.id, index]), + this.envelopeFiles.map((file, index) => [String(file.id), index]), ) - const elements = this.envelopeFiles.flatMap(file => file.visibleElements || []) + const elements = aggregateVisibleElementsByFiles(this.envelopeFiles) elements.forEach(element => { - const fileInfo = this.envelopeFiles.find(file => file.id === element.fileId) - const signer = fileInfo?.signers?.find(row => row.signRequestId === element.signRequestId) - || fileInfo?.signers?.find(row => row.me) + const fileInfo = findFileById(this.envelopeFiles, element.fileId) + const signers = getFileSigners(fileInfo) + const signer = signers.find(row => idsMatch(row.signRequestId, element.signRequestId)) + || signers.find(row => row.me) if (!signer) { return } @@ -263,7 +272,7 @@ export default { object.readOnly = true object.element = { ...element, - documentIndex: fileIndexById.get(element.fileId) ?? 0, + documentIndex: fileIndexById.get(String(element.fileId)) ?? 0, } this.$refs.pdfEditor.addSigner(object) }) @@ -272,9 +281,9 @@ export default { } const currentSigner = this.signStore.document.signers.find(signer => signer.me) - const visibleElements = this.signStore.document.visibleElements || [] + const visibleElements = getVisibleElementsFromDocument(this.signStore.document) const elementsForSigner = currentSigner - ? visibleElements.filter(element => element.signRequestId === currentSigner.signRequestId) + ? visibleElements.filter(element => idsMatch(element.signRequestId, currentSigner.signRequestId)) : [] if (currentSigner && elementsForSigner.length > 0) { elementsForSigner.forEach(element => { diff --git a/src/views/SignPDF/_partials/Sign.vue b/src/views/SignPDF/_partials/Sign.vue index a69369fa3a..96c9f1d778 100644 --- a/src/views/SignPDF/_partials/Sign.vue +++ b/src/views/SignPDF/_partials/Sign.vue @@ -177,6 +177,7 @@ import { useIdentificationDocumentStore } from '../../../store/identificationDoc import { SigningRequirementValidator } from '../../../services/SigningRequirementValidator.js' import { SignFlowHandler } from '../../../services/SignFlowHandler.js' import { FILE_STATUS } from '../../../constants.js' +import { getVisibleElementsFromDocument, idsMatch } from '../../../services/visibleElementsService.js' export default { name: 'Sign', @@ -223,10 +224,10 @@ export default { return [] } - const visibleElements = (this.signStore.document?.visibleElements || []) + const visibleElements = getVisibleElementsFromDocument(this.signStore.document) .filter(row => { return this.signatureElementsStore.hasSignatureOfType(row.type) - && row.signRequestId === signer.signRequestId + && idsMatch(row.signRequestId, signer.signRequestId) }) return visibleElements }, @@ -235,9 +236,9 @@ export default { }, needCreateSignature() { const signer = this.signStore.document?.signers.find(row => row.me) || {} - const visibleElements = this.signStore.document?.visibleElements || [] + const visibleElements = getVisibleElementsFromDocument(this.signStore.document) return !!signer.signRequestId - && visibleElements.some(row => row.signRequestId === signer.signRequestId) + && visibleElements.some(row => idsMatch(row.signRequestId, signer.signRequestId)) && !this.hasSignatures && this.canCreateSignature }, diff --git a/tests/integration/features/account/create_to_sign.feature b/tests/integration/features/account/create_to_sign.feature index efbe79a048..b4ad0d5a3b 100644 --- a/tests/integration/features/account/create_to_sign.feature +++ b/tests/integration/features/account/create_to_sign.feature @@ -7,7 +7,7 @@ Feature: account/create_to_sign And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"base64":"data:application/pdf;base64,JVBERi0xLjYKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVhbQp4nDPQM1Qo5ypUMFAw0DMwslAwtTTVMzIxV7AwMdSzMDNUKErlCtdSyOMyVADBonQuA4iUhaVCLheKYqBIDlw7xLAcuLEgFlwVVwZXmhZXoAIAI+sZGAplbmRzdHJlYW0KZW5kb2JqCgozIDAgb2JqCjg2CmVuZG9iagoKNSAwIG9iago8PAo+PgplbmRvYmoKCjYgMCBvYmoKPDwvRm9udCA1IDAgUgovUHJvY1NldFsvUERGL1RleHRdCj4+CmVuZG9iagoKMSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQgMCBSL1Jlc291cmNlcyA2IDAgUi9NZWRpYUJveFswIDAgNTk1LjI3NTU5MDU1MTE4MSA4NDEuODg5NzYzNzc5NTI4XS9Hcm91cDw8L1MvVHJhbnNwYXJlbmN5L0NTL0RldmljZVJHQi9JIHRydWU+Pi9Db250ZW50cyAyIDAgUj4+CmVuZG9iagoKNCAwIG9iago8PC9UeXBlL1BhZ2VzCi9SZXNvdXJjZXMgNiAwIFIKL01lZGlhQm94WyAwIDAgNTk1IDg0MSBdCi9LaWRzWyAxIDAgUiBdCi9Db3VudCAxPj4KZW5kb2JqCgo3IDAgb2JqCjw8L1R5cGUvQ2F0YWxvZy9QYWdlcyA0IDAgUgovT3BlbkFjdGlvblsxIDAgUiAvWFlaIG51bGwgbnVsbCAwXQo+PgplbmRvYmoKCjggMCBvYmoKPDwvQ3JlYXRvcjxGRUZGMDA0NDAwNzIwMDYxMDA3Nz4KL1Byb2R1Y2VyPEZFRkYwMDRDMDA2OTAwNjIwMDcyMDA2NTAwNEYwMDY2MDA2NjAwNjkwMDYzMDA2NTAwMjAwMDM3MDAyRTAwMzA+Ci9DcmVhdGlvbkRhdGUoRDoyMDIxMDIyMzExMDgwOS0wMycwMCcpPj4KZW5kb2JqCgp4cmVmCjAgOQowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAyNzAgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMTc2IDAwMDAwIG4gCjAwMDAwMDA0MzggMDAwMDAgbiAKMDAwMDAwMDE5NSAwMDAwMCBuIAowMDAwMDAwMjE3IDAwMDAwIG4gCjAwMDAwMDA1MzYgMDAwMDAgbiAKMDAwMDAwMDYxOSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgOS9Sb290IDcgMCBSCi9JbmZvIDggMCBSCi9JRCBbIDw1RkQ4MDlEMTdFODMwQUU5OTRDODkxNDVBMTMwNUQyQz4KPDVGRDgwOUQxN0U4MzBBRTk5NEM4OTE0NUExMzA1RDJDPiBdCi9Eb2NDaGVja3N1bSAvRDZBQThGQTBBQjMwODg2QkQ5ODU0QzYyMTg5QjI2NDQKPj4Kc3RhcnR4cmVmCjc4NQolJUVPRgo="} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | Then the response should have a status code 200 And there should be 1 emails in my inbox diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 7e3555ae27..4f7d587928 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -262,7 +262,7 @@ Feature: account/signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer@test.coop"}}] | + | signers | [{"identify":{"email":"signer@test.coop"}}] | | name | document | When as user "" And I open the latest email to "signer@test.coop" with subject "LibreSign: There is a file for you to sign" diff --git a/tests/integration/features/file/list.feature b/tests/integration/features/file/list.feature index 080dec4830..dc56618b8c 100644 --- a/tests/integration/features/file/list.feature +++ b/tests/integration/features/file/list.feature @@ -7,7 +7,7 @@ Feature: file-list And set the email of user "signer2" to "" And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}},{"identify":{"account":"signer2"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}},{"identify":{"account":"signer2"}}] | | name | document | And the response should have a status code 200 When sending "get" to ocs "/apps/libresign/api/v1/file/list" @@ -48,25 +48,25 @@ Feature: file-list | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | And the response should have a status code 200 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | And the response should have a status code 200 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | And the response should have a status code 200 # first page diff --git a/tests/integration/features/file/validate.feature b/tests/integration/features/file/validate.feature index 83a2a5ab1e..6a91c3c424 100644 --- a/tests/integration/features/file/validate.feature +++ b/tests/integration/features/file/validate.feature @@ -12,7 +12,7 @@ Feature: validate And user "signer1" exists When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | Document Name | Then the response should have a status code 200 And as user "signer1" @@ -54,7 +54,7 @@ Feature: validate When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"admin"}}] | + | signers | [{"identify":{"account":"admin"}}] | | name | document | And the response should have a status code 200 And sending "get" to ocs "/apps/libresign/api/v1/file/list" diff --git a/tests/integration/features/file/webdav-properties.feature b/tests/integration/features/file/webdav-properties.feature index c55699675f..c279b27620 100644 --- a/tests/integration/features/file/webdav-properties.feature +++ b/tests/integration/features/file/webdav-properties.feature @@ -8,7 +8,7 @@ Feature: webdav-properties Given user "admin" uploads file "test.pdf" to "test-document.pdf" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"path":"/test-document.pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | test-document | Then the response should have a status code 200 When user "admin" gets WebDAV properties for "test-document.pdf" diff --git a/tests/integration/features/notification/custom_message_notification.feature b/tests/integration/features/notification/custom_message_notification.feature index f4d7025fce..ee300cddc4 100644 --- a/tests/integration/features/notification/custom_message_notification.feature +++ b/tests/integration/features/notification/custom_message_notification.feature @@ -17,7 +17,7 @@ Feature: Custom message for signers When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | | name | Document without custom message | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | Then the response should have a status code 200 And there should be 1 emails in my inbox When I open the latest email to "signer1@test.com" with subject "LibreSign: There is a file for you to sign" @@ -27,7 +27,7 @@ Feature: Custom message for signers When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | | name | Document with custom message | - | users | [{"identify":{"account":"signer1"},"description":"Please review section 3 and the appendix before signing."}] | + | signers | [{"identify":{"account":"signer1"},"description":"Please review section 3 and the appendix before signing."}] | Then the response should have a status code 200 And there should be 1 emails in my inbox When I open the latest email to "signer1@test.com" with subject "LibreSign: There is a file for you to sign" @@ -38,14 +38,14 @@ Feature: Custom message for signers When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | | name | Document for email method | - | users | [{"identify":{"email":"external@domain.test"},"displayName":"External Signer"}] | + | signers | [{"identify":{"email":"external@domain.test"},"displayName":"External Signer"}] | Then the response should have a status code 200 Scenario: Email method - custom description via reminder Given sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | | name | Document for email with description | - | users | [{"identify":{"email":"external@domain.test"},"displayName":"External Signer","description":"Urgent: Please sign by end of day."}] | + | signers | [{"identify":{"email":"external@domain.test"},"displayName":"External Signer","description":"Urgent: Please sign by end of day."}] | And the response should have a status code 200 And fetch field "(FILE_ID)ocs.data.id" from previous JSON response And fetch field "(SIGN_REQUEST_ID)ocs.data.signers.0.signRequestId" from previous JSON response diff --git a/tests/integration/features/page/sign_identify_account.feature b/tests/integration/features/page/sign_identify_account.feature index 3ee2ee87b5..f89ddc857a 100644 --- a/tests/integration/features/page/sign_identify_account.feature +++ b/tests/integration/features/page/sign_identify_account.feature @@ -10,7 +10,7 @@ Feature: page/sign_identify_account And reset notifications of user "signer1" And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"base64":"data:application/pdf;base64,JVBERi0xLjYKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVhbQp4nDPQM1Qo5ypUMFAw0DMwslAwtTTVMzIxV7AwMdSzMDNUKErlCtdSyOMyVADBonQuA4iUhaVCLheKYqBIDlw7xLAcuLEgFlwVVwZXmhZXoAIAI+sZGAplbmRzdHJlYW0KZW5kb2JqCgozIDAgb2JqCjg2CmVuZG9iagoKNSAwIG9iago8PAo+PgplbmRvYmoKCjYgMCBvYmoKPDwvRm9udCA1IDAgUgovUHJvY1NldFsvUERGL1RleHRdCj4+CmVuZG9iagoKMSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQgMCBSL1Jlc291cmNlcyA2IDAgUi9NZWRpYUJveFswIDAgNTk1LjI3NTU5MDU1MTE4MSA4NDEuODg5NzYzNzc5NTI4XS9Hcm91cDw8L1MvVHJhbnNwYXJlbmN5L0NTL0RldmljZVJHQi9JIHRydWU+Pi9Db250ZW50cyAyIDAgUj4+CmVuZG9iagoKNCAwIG9iago8PC9UeXBlL1BhZ2VzCi9SZXNvdXJjZXMgNiAwIFIKL01lZGlhQm94WyAwIDAgNTk1IDg0MSBdCi9LaWRzWyAxIDAgUiBdCi9Db3VudCAxPj4KZW5kb2JqCgo3IDAgb2JqCjw8L1R5cGUvQ2F0YWxvZy9QYWdlcyA0IDAgUgovT3BlbkFjdGlvblsxIDAgUiAvWFlaIG51bGwgbnVsbCAwXQo+PgplbmRvYmoKCjggMCBvYmoKPDwvQ3JlYXRvcjxGRUZGMDA0NDAwNzIwMDYxMDA3Nz4KL1Byb2R1Y2VyPEZFRkYwMDRDMDA2OTAwNjIwMDcyMDA2NTAwNEYwMDY2MDA2NjAwNjkwMDYzMDA2NTAwMjAwMDM3MDAyRTAwMzA+Ci9DcmVhdGlvbkRhdGUoRDoyMDIxMDIyMzExMDgwOS0wMycwMCcpPj4KZW5kb2JqCgp4cmVmCjAgOQowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAyNzAgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMTc2IDAwMDAwIG4gCjAwMDAwMDA0MzggMDAwMDAgbiAKMDAwMDAwMDE5NSAwMDAwMCBuIAowMDAwMDAwMjE3IDAwMDAwIG4gCjAwMDAwMDA1MzYgMDAwMDAgbiAKMDAwMDAwMDYxOSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgOS9Sb290IDcgMCBSCi9JbmZvIDggMCBSCi9JRCBbIDw1RkQ4MDlEMTdFODMwQUU5OTRDODkxNDVBMTMwNUQyQz4KPDVGRDgwOUQxN0U4MzBBRTk5NEM4OTE0NUExMzA1RDJDPiBdCi9Eb2NDaGVja3N1bSAvRDZBQThGQTBBQjMwODg2QkQ5ODU0QzYyMTg5QjI2NDQKPj4Kc3RhcnR4cmVmCjc4NQolJUVPRgo="} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | And the response should have a status code 200 And fetch field "(FILE_UUID)ocs.data.uuid" from previous JSON response @@ -64,7 +64,7 @@ Feature: page/sign_identify_account And reset notifications of user "signer1" And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"base64":"data:application/pdf;base64,JVBERi0xLjYKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVhbQp4nDPQM1Qo5ypUMFAw0DMwslAwtTTVMzIxV7AwMdSzMDNUKErlCtdSyOMyVADBonQuA4iUhaVCLheKYqBIDlw7xLAcuLEgFlwVVwZXmhZXoAIAI+sZGAplbmRzdHJlYW0KZW5kb2JqCgozIDAgb2JqCjg2CmVuZG9iagoKNSAwIG9iago8PAo+PgplbmRvYmoKCjYgMCBvYmoKPDwvRm9udCA1IDAgUgovUHJvY1NldFsvUERGL1RleHRdCj4+CmVuZG9iagoKMSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQgMCBSL1Jlc291cmNlcyA2IDAgUi9NZWRpYUJveFswIDAgNTk1LjI3NTU5MDU1MTE4MSA4NDEuODg5NzYzNzc5NTI4XS9Hcm91cDw8L1MvVHJhbnNwYXJlbmN5L0NTL0RldmljZVJHQi9JIHRydWU+Pi9Db250ZW50cyAyIDAgUj4+CmVuZG9iagoKNCAwIG9iago8PC9UeXBlL1BhZ2VzCi9SZXNvdXJjZXMgNiAwIFIKL01lZGlhQm94WyAwIDAgNTk1IDg0MSBdCi9LaWRzWyAxIDAgUiBdCi9Db3VudCAxPj4KZW5kb2JqCgo3IDAgb2JqCjw8L1R5cGUvQ2F0YWxvZy9QYWdlcyA0IDAgUgovT3BlbkFjdGlvblsxIDAgUiAvWFlaIG51bGwgbnVsbCAwXQo+PgplbmRvYmoKCjggMCBvYmoKPDwvQ3JlYXRvcjxGRUZGMDA0NDAwNzIwMDYxMDA3Nz4KL1Byb2R1Y2VyPEZFRkYwMDRDMDA2OTAwNjIwMDcyMDA2NTAwNEYwMDY2MDA2NjAwNjkwMDYzMDA2NTAwMjAwMDM3MDAyRTAwMzA+Ci9DcmVhdGlvbkRhdGUoRDoyMDIxMDIyMzExMDgwOS0wMycwMCcpPj4KZW5kb2JqCgp4cmVmCjAgOQowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAyNzAgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMTc2IDAwMDAwIG4gCjAwMDAwMDA0MzggMDAwMDAgbiAKMDAwMDAwMDE5NSAwMDAwMCBuIAowMDAwMDAwMjE3IDAwMDAwIG4gCjAwMDAwMDA1MzYgMDAwMDAgbiAKMDAwMDAwMDYxOSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgOS9Sb290IDcgMCBSCi9JbmZvIDggMCBSCi9JRCBbIDw1RkQ4MDlEMTdFODMwQUU5OTRDODkxNDVBMTMwNUQyQz4KPDVGRDgwOUQxN0U4MzBBRTk5NEM4OTE0NUExMzA1RDJDPiBdCi9Eb2NDaGVja3N1bSAvRDZBQThGQTBBQjMwODg2QkQ5ODU0QzYyMTg5QjI2NDQKPj4Kc3RhcnR4cmVmCjc4NQolJUVPRgo="} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | And the response should have a status code 200 When as user "signer1" diff --git a/tests/integration/features/page/validate.feature b/tests/integration/features/page/validate.feature index 32fc14aec7..c4f3a1da4c 100644 --- a/tests/integration/features/page/validate.feature +++ b/tests/integration/features/page/validate.feature @@ -8,7 +8,7 @@ Feature: page/validate When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"admin"}}] | + | signers | [{"identify":{"account":"admin"}}] | | name | document | And the response should have a status code 200 And sending "get" to ocs "/apps/libresign/api/v1/file/list" diff --git a/tests/integration/features/sign/cancel.feature b/tests/integration/features/sign/cancel.feature index 02534eb2a6..ef986174be 100644 --- a/tests/integration/features/sign/cancel.feature +++ b/tests/integration/features/sign/cancel.feature @@ -6,7 +6,7 @@ Feature: sign-request-cancel And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | And the response should have a status code 200 And sending "get" to ocs "/apps/libresign/api/v1/file/list" @@ -27,7 +27,7 @@ Feature: sign-request-cancel And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"},"notify":false}] | + | signers | [{"identify":{"account":"signer1"},"notify":false}] | | name | document | | status | 0 | And the response should have a status code 200 @@ -48,7 +48,7 @@ Feature: sign-request-cancel And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | When sending "get" to ocs "/apps/libresign/api/v1/file/list" And fetch field "(FILE_ID)ocs.data.data.0.id" from previous JSON response @@ -64,7 +64,7 @@ Feature: sign-request-cancel And run the command "libresign:configure:openssl --cn test" with result code 0 And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | And the response should have a status code 200 When sending "get" to ocs "/apps/libresign/api/v1/file/list" diff --git a/tests/integration/features/sign/request.feature b/tests/integration/features/sign/request.feature index ef35fb7416..57bfc3453c 100644 --- a/tests/integration/features/sign/request.feature +++ b/tests/integration/features/sign/request.feature @@ -7,7 +7,7 @@ Feature: request-signature And as user "signer1" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"base64":""} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | Then the response should have a status code 422 And the response should be a JSON array with the following mandatory values @@ -21,7 +21,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"invalid":""} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | | Then the response should have a status code 422 And the response should be a JSON array with the following mandatory values @@ -43,7 +43,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | And the response should have a status code 200 And there should be 1 emails in my inbox @@ -80,7 +80,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | And the response should have a status code 200 And there should be 1 emails in my inbox @@ -110,7 +110,7 @@ Feature: request-signature And my inbox is empty And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | Then the response should have a status code 200 And as user "signer1" @@ -133,7 +133,7 @@ Feature: request-signature And my inbox is empty And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | Then the response should have a status code 200 And there should be 1 emails in my inbox @@ -157,7 +157,7 @@ Feature: request-signature And run the command "config:app:set libresign maximum_validity --value=1 --type=integer" with result code 0 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer2@domain.test"}}] | + | signers | [{"identify":{"email":"signer2@domain.test"}}] | | name | document | Then the response should have a status code 200 And there should be 1 emails in my inbox @@ -181,7 +181,7 @@ Feature: request-signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer2@domain.test"}}] | + | signers | [{"identify":{"email":"signer2@domain.test"}}] | | name | document | And the response should have a status code 200 And there should be 1 emails in my inbox @@ -233,7 +233,7 @@ Feature: request-signature | value | (string)[{"name":"account","enabled":true}] | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer2"}}] | + | signers | [{"identify":{"account":"signer2"}}] | | name | document | Then the response should be a JSON array with the following mandatory values | key | value | @@ -249,7 +249,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | Then the response should have a status code 200 And fetch field "(FILE_UUID)ocs.data.uuid" from previous JSON response @@ -266,7 +266,7 @@ Feature: request-signature When as user "admin" And sending "patch" to ocs "/apps/libresign/api/v1/request-signature" | uuid | | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | And the response should have a status code 200 When as user "signer1" Then sending "get" to ocs "/apps/notifications/api/v2/notifications" @@ -285,7 +285,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"invaliddomain.test"}}] | + | signers | [{"identify":{"account":"invaliddomain.test"}}] | | name | document | Then the response should have a status code 422 Then the response should be a JSON array with the following mandatory values @@ -298,7 +298,7 @@ Feature: request-signature | rootCert | {"commonName":"test"} | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer3@domain.test"}}] | + | signers | [{"identify":{"account":"signer3@domain.test"}}] | | name | document | Then the response should have a status code 422 Then the response should be a JSON array with the following mandatory values @@ -313,7 +313,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer2@domain.test"}}] | + | signers | [{"identify":{"email":"signer2@domain.test"}}] | | name | document | Then the response should have a status code 200 And there should be 1 emails in my inbox @@ -329,7 +329,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"account":"signer1"}}] | | name | document | Then the response should have a status code 200 When as user "signer1" @@ -347,7 +347,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | Then the response should have a status code 200 And there should be 1 emails in my inbox @@ -361,7 +361,7 @@ Feature: request-signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"can_create_account":false}] | And I send a file to be signed | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | status | 0 | | name | document | And fetch field "(FILE_UUID)ocs.data.uuid" from previous JSON response @@ -391,7 +391,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}},{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}},{"identify":{"account":"signer1"}}] | | name | document | Then the response should have a status code 200 When as user "signer1" @@ -410,7 +410,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"11111@domain.test"}},{"identify":{"email":"22222@domain.test"}}] | + | signers | [{"identify":{"email":"11111@domain.test"}},{"identify":{"email":"22222@domain.test"}}] | | name | document | Then the response should have a status code 200 When I open the latest email to "11111@domain.test" with subject "LibreSign: There is a file for you to sign" @@ -452,7 +452,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"11111@domain.test"}}] | + | signers | [{"identify":{"email":"11111@domain.test"}}] | | name | document | Then the response should have a status code 200 When I open the latest email to "11111@domain.test" with subject "LibreSign: There is a file for you to sign" @@ -497,7 +497,7 @@ Feature: request-signature And my inbox is empty When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"11111@domain.test"}}] | + | signers | [{"identify":{"email":"11111@domain.test"}}] | | name | document | Then the response should have a status code 200 When I open the latest email to "11111@domain.test" with subject "LibreSign: There is a file for you to sign" @@ -536,7 +536,7 @@ Feature: request-signature And as user "admin" When I send a file to be signed | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}},{"identify":{"account":"signer1"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}},{"identify":{"account":"signer1"}}] | | name | document | And fetch field "(FILE_UUID)ocs.data.uuid" from previous JSON response And the response should have a status code 200 @@ -554,7 +554,7 @@ Feature: request-signature | (jq).ocs.data.data[0].signers[1].me | false | And sending "patch" to ocs "/apps/libresign/api/v1/request-signature" | uuid | | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | And the response should have a status code 200 When sending "get" to ocs "/apps/libresign/api/v1/file/list" And the response should be a JSON array with the following mandatory values @@ -577,13 +577,13 @@ Feature: request-signature | value | (string)[{"name":"email","enabled":true,"mandatory":true,"signatureMethods":{"emailToken":{"enabled":true}}}] | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | | status | 0 | And there should be 0 emails in my inbox When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"email":"signer1@domain.test"}}] | + | signers | [{"identify":{"email":"signer1@domain.test"}}] | | name | document | | status | 1 | And there should be 1 email in my inbox diff --git a/tests/integration/features/sign/sequential_signing.feature b/tests/integration/features/sign/sequential_signing.feature index 80e6f41358..423c91505d 100644 --- a/tests/integration/features/sign/sequential_signing.feature +++ b/tests/integration/features/sign/sequential_signing.feature @@ -15,7 +15,7 @@ Feature: sequential-signing And user "signer2" exists When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"}},{"identify":{"account":"signer2"}}] | + | signers | [{"identify":{"account":"signer1"}},{"identify":{"account":"signer2"}}] | | name | Parallel Document | Then the response should have a status code 200 And as user "signer1" @@ -42,7 +42,7 @@ Feature: sequential-signing And the response should have a status code 200 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify":{"account":"signer1"},"signingOrder":1},{"identify":{"account":"signer2"},"signingOrder":2}] | + | signers | [{"identify":{"account":"signer1"},"signingOrder":1},{"identify":{"account":"signer2"},"signingOrder":2}] | | name | Sequential Document | Then the response should have a status code 200 # Signer2 should NOT see the file yet (their sign_request is in DRAFT status) diff --git a/tests/integration/features/sign/signed.feature b/tests/integration/features/sign/signed.feature index a09529ddc8..0f924a7fb9 100644 --- a/tests/integration/features/sign/signed.feature +++ b/tests/integration/features/sign/signed.feature @@ -15,7 +15,7 @@ Feature: signed And the response should have a status code 200 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"displayName": "Signer Name","description": "Please, sign this document","identify": {"account": "signer1"}}] | + | signers | [{"displayName": "Signer Name","description": "Please, sign this document","identify": {"account": "signer1"}}] | | name | Document Name | And the response should have a status code 200 And as user "signer1" @@ -68,7 +68,7 @@ Feature: signed And reset activity of user "admin" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"displayName": "Signer Name","identify": {"account": "signer1"}},{"displayName": "Admin","identify": {"account": "admin"}}] | + | signers | [{"displayName": "Signer Name","identify": {"account": "signer1"}},{"displayName": "Admin","identify": {"account": "admin"}}] | | name | Document Name | And the response should have a status code 200 And as user "signer1" @@ -133,7 +133,7 @@ Feature: signed And reset activity of user "admin" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"displayName": "Signer Name","identify": {"account": "signer1"}},{"displayName": "Admin","identify": {"account": "admin"}}] | + | signers | [{"displayName": "Signer Name","identify": {"account": "signer1"}},{"displayName": "Admin","identify": {"account": "admin"}}] | | name | Document Name | And the response should have a status code 200 And as user "signer1" @@ -188,7 +188,7 @@ Feature: signed And reset activity of user "admin" When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"displayName": "Signer Name","identify": {"email": "unauthenticated@email.tld"}}] | + | signers | [{"displayName": "Signer Name","identify": {"email": "unauthenticated@email.tld"}}] | | name | Document Name | And the response should have a status code 200 And I open the latest email to "unauthenticated@email.tld" with subject "LibreSign: There is a file for you to sign" diff --git a/tests/integration/features/sign/tsa.feature b/tests/integration/features/sign/tsa.feature index 00a5bbe0f7..0d8b6f9557 100644 --- a/tests/integration/features/sign/tsa.feature +++ b/tests/integration/features/sign/tsa.feature @@ -17,7 +17,7 @@ Feature: TSA Integration - End-to-End Workflow And the response should have a status code 200 When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"displayName": "TSA Signer","identify": {"account": "signer1"}}] | + | signers | [{"displayName": "TSA Signer","identify": {"account": "signer1"}}] | | name | TSA Document Test | Then the response should have a status code 200 And as user "signer1" @@ -63,7 +63,7 @@ Feature: TSA Integration - End-to-End Workflow | value | (string)[{"name":"account","enabled":true,"mandatory":true,"signatureMethods":{"clickToSign":{"enabled":true}},"signatureMethodEnabled":"clickToSign"}] | When sending "post" to ocs "/apps/libresign/api/v1/request-signature" | file | {"url":"/apps/libresign/develop/pdf"} | - | users | [{"identify": {"account": "signer1"}}] | + | signers | [{"identify": {"account": "signer1"}}] | | name | TSA Error Test | And as user "signer1" And sending "get" to ocs "/apps/libresign/api/v1/file/list" diff --git a/tests/php/Api/Controller/FileControllerTest.php b/tests/php/Api/Controller/FileControllerTest.php index 6f98eca69c..905a989a8e 100644 --- a/tests/php/Api/Controller/FileControllerTest.php +++ b/tests/php/Api/Controller/FileControllerTest.php @@ -57,7 +57,7 @@ public function testValidateWithSuccessUsingUnloggedUser():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', @@ -96,7 +96,7 @@ public function testValidateWithSuccessUsingSigner():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', diff --git a/tests/php/Api/Controller/FileElementControllerTest.php b/tests/php/Api/Controller/FileElementControllerTest.php index 41316e79ea..0ed8902844 100644 --- a/tests/php/Api/Controller/FileElementControllerTest.php +++ b/tests/php/Api/Controller/FileElementControllerTest.php @@ -25,7 +25,7 @@ public function testPostSuccess():array { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', diff --git a/tests/php/Api/Controller/NotifyControllerTest.php b/tests/php/Api/Controller/NotifyControllerTest.php index c78ac64f5c..1387371729 100644 --- a/tests/php/Api/Controller/NotifyControllerTest.php +++ b/tests/php/Api/Controller/NotifyControllerTest.php @@ -51,7 +51,7 @@ public function testNotifySignersWithSuccess():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', diff --git a/tests/php/Api/Controller/RequestSignatureControllerTest.php b/tests/php/Api/Controller/RequestSignatureControllerTest.php index 6df036702c..a400cc9324 100644 --- a/tests/php/Api/Controller/RequestSignatureControllerTest.php +++ b/tests/php/Api/Controller/RequestSignatureControllerTest.php @@ -30,7 +30,7 @@ public function testPostRegisterWithValidationFailure():void { ->withRequestBody([ 'name' => 'filename', 'file' => [], - 'users' => [] + 'signers' => [] ]) ->assertResponseCode(422); @@ -63,7 +63,7 @@ public function testPostRegisterWithSuccess():void { 'file' => [ 'base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf')) ], - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'user@test.coop', @@ -74,7 +74,7 @@ public function testPostRegisterWithSuccess():void { $response = $this->assertRequest(); $body = json_decode($response->getBody()->getContents(), true); - $body['ocs']['data']['data']['users'][] = ['email' => 'user@test.coop']; + $body['ocs']['data']['data']['signers'][] = ['email' => 'user@test.coop']; } /** @@ -91,7 +91,7 @@ public function testPatchRegisterWithValidationFailure():void { ]) ->withRequestBody([ 'uuid' => '12345678-1234-1234-1234-123456789012', - 'users' => [] + 'signers' => [] ]) ->assertResponseCode(422); @@ -116,7 +116,7 @@ public function testPatchRegisterWithSuccess():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', @@ -135,7 +135,7 @@ public function testPatchRegisterWithSuccess():void { ]) ->withRequestBody([ 'uuid' => $file->getUuid(), - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'user@test.coop', @@ -146,6 +146,6 @@ public function testPatchRegisterWithSuccess():void { $response = $this->assertRequest(); $body = json_decode($response->getBody()->getContents(), true); - $body['ocs']['data']['data']['users'][] = ['email' => 'user@test.coop']; + $body['ocs']['data']['data']['signers'][] = ['email' => 'user@test.coop']; } } diff --git a/tests/php/Api/Controller/SignFileControllerTest.php b/tests/php/Api/Controller/SignFileControllerTest.php index 550605e7ff..da7b0a8339 100644 --- a/tests/php/Api/Controller/SignFileControllerTest.php +++ b/tests/php/Api/Controller/SignFileControllerTest.php @@ -80,7 +80,7 @@ public function testSignUsingFileIdWithAlreadySignedFile():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'account' => 'username', @@ -124,7 +124,7 @@ public function testSignUsingFileIdWithNotFoundFile():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', @@ -168,7 +168,7 @@ public function testSignUsingUuidWithEmptyToken():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', @@ -205,7 +205,7 @@ public function testSignWithCertificateButEmptyPassword():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', @@ -261,7 +261,7 @@ public function testDeleteSignFileIdSignRequestIdWithSuccess():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', @@ -309,7 +309,7 @@ public function testDeleteUsingSignFileIdWithSuccess():void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'email' => 'person@test.coop', diff --git a/tests/php/Unit/Controller/AEnvironmentPageAwareControllerTest.php b/tests/php/Unit/Controller/AEnvironmentPageAwareControllerTest.php index 07efdc82f0..1a5cd4c8c3 100644 --- a/tests/php/Unit/Controller/AEnvironmentPageAwareControllerTest.php +++ b/tests/php/Unit/Controller/AEnvironmentPageAwareControllerTest.php @@ -74,7 +74,7 @@ public function testLoadFileUuidWhenFileNotFound(): void { $file = $this->requestSignFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ [ 'identify' => [ 'account' => 'username', diff --git a/tests/php/Unit/Helper/ValidateHelperTest.php b/tests/php/Unit/Helper/ValidateHelperTest.php index 49c5f06b42..665b20a49e 100644 --- a/tests/php/Unit/Helper/ValidateHelperTest.php +++ b/tests/php/Unit/Helper/ValidateHelperTest.php @@ -980,7 +980,7 @@ public function testValidateIdentifySignersIntegration(): void { ->willReturn($identifyMethod); $data = [ - 'users' => [ + 'signers' => [ ['identify' => ['account' => 'user@example.com']] ] ]; @@ -1023,7 +1023,7 @@ public static function providerValidateIdentifySigners(): array { return [ 'valid data with identify structure single method' => [ [ - 'users' => [ + 'signers' => [ [ 'identify' => [ 'account' => 'user@example.com' @@ -1035,7 +1035,7 @@ public static function providerValidateIdentifySigners(): array { ], 'valid data with identify structure multiple methods' => [ [ - 'users' => [ + 'signers' => [ [ 'identify' => [ 'account' => 'user@example.com', @@ -1048,7 +1048,7 @@ public static function providerValidateIdentifySigners(): array { ], 'valid data with identifyMethods structure single method' => [ [ - 'users' => [ + 'signers' => [ [ 'identifyMethods' => [ ['method' => 'account', 'value' => 'user@example.com'] @@ -1060,7 +1060,7 @@ public static function providerValidateIdentifySigners(): array { ], 'valid data with identifyMethods structure multiple methods' => [ [ - 'users' => [ + 'signers' => [ [ 'identifyMethods' => [ ['method' => 'account', 'value' => 'user@example.com'], @@ -1073,7 +1073,7 @@ public static function providerValidateIdentifySigners(): array { ], 'mixed structures in same data' => [ [ - 'users' => [ + 'signers' => [ [ 'identify' => [ 'account' => 'user1@example.com' @@ -1093,49 +1093,49 @@ public static function providerValidateIdentifySigners(): array { false, // should not throw '' ], - 'missing users key' => [ + 'missing signers key' => [ ['someOtherKey' => 'value'], false, // should not throw '' ], - 'empty users array' => [ - ['users' => []], + 'empty signers array' => [ + ['signers' => []], false, // should not throw '' ], - 'users not array' => [ - ['users' => 'not-an-array'], + 'signers not array' => [ + ['signers' => 'not-an-array'], true, // should throw 'No signers' ], 'empty signer' => [ - ['users' => [[]]], + ['signers' => [[]]], true, // should throw 'No signers' ], 'signer not array' => [ - ['users' => ['not-an-array']], + ['signers' => ['not-an-array']], true, // should throw 'No signers' ], 'signer without identify methods' => [ - ['users' => [['someKey' => 'value']]], + ['signers' => [['someKey' => 'value']]], true, // should throw 'No identify methods for signer' ], 'signer with empty identify' => [ - ['users' => [['identify' => []]]], + ['signers' => [['identify' => []]]], true, // should throw 'No identify methods for signer' ], 'signer with empty identifyMethods' => [ - ['users' => [['identifyMethods' => []]]], + ['signers' => [['identifyMethods' => []]]], true, // should throw 'No identify methods for signer' ], 'invalid identifyMethods structure - missing method' => [ [ - 'users' => [ + 'signers' => [ [ 'identifyMethods' => [ ['value' => 'user@example.com'] // missing 'method' @@ -1148,7 +1148,7 @@ public static function providerValidateIdentifySigners(): array { ], 'invalid identifyMethods structure - missing value' => [ [ - 'users' => [ + 'signers' => [ [ 'identifyMethods' => [ ['method' => 'email'] // missing 'value' @@ -1161,7 +1161,7 @@ public static function providerValidateIdentifySigners(): array { ], 'valid displayName within 64 characters' => [ [ - 'users' => [ + 'signers' => [ [ 'displayName' => 'Valid Display Name', 'identify' => [ @@ -1174,7 +1174,7 @@ public static function providerValidateIdentifySigners(): array { ], 'displayName exactly 64 characters' => [ [ - 'users' => [ + 'signers' => [ [ 'displayName' => str_repeat('A', 64), 'identify' => [ @@ -1187,7 +1187,7 @@ public static function providerValidateIdentifySigners(): array { ], 'displayName too long - 65 characters' => [ [ - 'users' => [ + 'signers' => [ [ 'displayName' => str_repeat('A', 65), 'identify' => [ @@ -1201,7 +1201,7 @@ public static function providerValidateIdentifySigners(): array { ], 'displayName too long - 100 characters' => [ [ - 'users' => [ + 'signers' => [ [ 'displayName' => str_repeat('B', 100), 'identify' => [ diff --git a/tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php b/tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php deleted file mode 100644 index 59eee545a3..0000000000 --- a/tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php +++ /dev/null @@ -1,184 +0,0 @@ -identifyService = $this->createMock(IdentifyService::class); - $this->appConfig = $this->createMock(IAppConfig::class); - $this->sessionService = $this->createMock(SessionService::class); - $this->signRequestMapper = $this->createMock(SignRequestMapper::class); - $this->timeFactory = $this->createMock(ITimeFactory::class); - $l10n = $this->createMock(IL10N::class); - - $l10n->method('t') - ->willReturnCallback(static fn (string $text): string => $text); - $this->identifyService->method('getL10n')->willReturn($l10n); - $this->identifyService->method('getAppConfig')->willReturn($this->appConfig); - $this->identifyService->method('getSessionService')->willReturn($this->sessionService); - $this->identifyService->method('getSignRequestMapper')->willReturn($this->signRequestMapper); - $this->identifyService->method('getTimeFactory')->willReturn($this->timeFactory); - } - - #[DataProvider('providerRuntimeConfigReadPaths')] - public function testRuntimeConfigIsReadWithCacheRefresh(string $path): void { - $cacheCleared = false; - $this->appConfig->expects($this->once()) - ->method('clearCache') - ->with(true) - ->willReturnCallback(function () use (&$cacheCleared): void { - $cacheCleared = true; - }); - $this->appConfig->expects($this->once()) - ->method('getValueInt') - ->with(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL) - ->willReturnCallback(function () use (&$cacheCleared): int { - $this->assertTrue($cacheCleared); - return 10; - }); - - $identifyMethod = $this->newIdentifyMethodEntity( - signRequestId: 10, - identifierValue: 'signer@domain.test', - lastAttemptDate: '2026-02-16T10:00:01+00:00', - identifiedAtDate: null, - ); - - if ($path === 'renewSession') { - $this->sessionService->expects($this->once()) - ->method('setIdentifyMethodId') - ->with(99); - $this->sessionService->expects($this->once()) - ->method('resetDurationOfSignPage'); - $this->newMethodWithEntity($identifyMethod)->runRenewSession(); - return; - } - - $this->sessionService->method('getSignStartTime')->willReturn(0); - $this->signRequestMapper->method('getById')->with(10)->willReturn($this->newSignRequest( - createdAt: '2026-02-16T10:00:09+00:00', - uuid: '9f95dc38-c2f8-43e5-a91d-8e191ca9520d', - )); - $this->timeFactory->method('getDateTime')->willReturn(new \DateTime('2026-02-16T10:00:10+00:00')); - - $this->newMethodWithEntity($identifyMethod)->runThrowIfRenewalIntervalExpired(); - } - - public static function providerRuntimeConfigReadPaths(): array { - return [ - 'renewSession path' => ['renewSession'], - 'throwIfRenewalIntervalExpired path' => ['throwIfRenewalIntervalExpired'], - ]; - } - - #[DataProvider('providerRenewalWindowByLastAction')] - public function testRenewalWindowUsesIdentifiedAtAsLastAction(?string $identifiedAtDate, bool $mustExpire): void { - $this->appConfig->method('clearCache'); - $this->appConfig->method('getValueInt') - ->with(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL) - ->willReturn(10); - - $this->sessionService->method('getSignStartTime')->willReturn(0); - $this->signRequestMapper->method('getById')->with(10)->willReturn($this->newSignRequest( - createdAt: '2026-02-16T10:00:00+00:00', - uuid: '903c8fa8-f140-4213-a2fd-f435eea3492d', - )); - $this->timeFactory->method('getDateTime')->willReturn(new \DateTime('2026-02-16T10:00:12+00:00')); - - $identifyMethod = $this->newIdentifyMethodEntity( - signRequestId: 10, - identifierValue: 'signer@domain.test', - lastAttemptDate: '2026-02-16T10:00:01+00:00', - identifiedAtDate: $identifiedAtDate, - ); - - $method = $this->newMethodWithEntity($identifyMethod); - $method->forceName('email'); - - if ($mustExpire) { - $this->expectException(LibresignException::class); - $this->expectExceptionMessageMatches('/.*Link expired.*/'); - $method->runThrowIfRenewalIntervalExpired(); - return; - } - - $method->runThrowIfRenewalIntervalExpired(); - $this->assertSame(10, $method->getEntity()->getSignRequestId()); - } - - public static function providerRenewalWindowByLastAction(): array { - return [ - 'without identifiedAt expires by older lastAttempt' => [null, true], - 'with recent identifiedAt keeps renewal valid' => ['2026-02-16T10:00:05+00:00', false], - ]; - } - - private function newMethodWithEntity(IdentifyMethod $entity): AbstractIdentifyMethodForTest { - $method = new AbstractIdentifyMethodForTest($this->identifyService); - $method->setEntity($entity); - return $method; - } - - private function newIdentifyMethodEntity( - int $signRequestId, - string $identifierValue, - ?string $lastAttemptDate, - ?string $identifiedAtDate, - ): IdentifyMethod { - $identifyMethod = new IdentifyMethod(); - $identifyMethod->setId(99); - $identifyMethod->setSignRequestId($signRequestId); - $identifyMethod->setIdentifierValue($identifierValue); - $identifyMethod->setLastAttemptDate($lastAttemptDate); - $identifyMethod->setIdentifiedAtDate($identifiedAtDate); - return $identifyMethod; - } - - private function newSignRequest(string $createdAt, string $uuid): SignRequest { - $signRequest = new SignRequest(); - $signRequest->setCreatedAt(new \DateTime($createdAt)); - $signRequest->setUuid($uuid); - return $signRequest; - } -} - -final class AbstractIdentifyMethodForTest extends AbstractIdentifyMethod { - public function runRenewSession(): void { - $this->renewSession(); - } - - public function runThrowIfRenewalIntervalExpired(): void { - $this->throwIfRenewalIntervalExpired(); - } - - public function forceName(string $name): void { - $this->name = $name; - } -} diff --git a/tests/php/Unit/Service/RequestSignatureServiceTest.php b/tests/php/Unit/Service/RequestSignatureServiceTest.php index 40ce661ba3..4f2fe1ac20 100644 --- a/tests/php/Unit/Service/RequestSignatureServiceTest.php +++ b/tests/php/Unit/Service/RequestSignatureServiceTest.php @@ -10,9 +10,11 @@ namespace OCA\Libresign\Tests\Unit\Service; +use OCA\Libresign\Db\FileElement; use OCA\Libresign\Db\FileElementMapper; use OCA\Libresign\Db\FileMapper; use OCA\Libresign\Db\IdentifyMethodMapper; +use OCA\Libresign\Db\SignRequest; use OCA\Libresign\Db\SignRequestMapper; use OCA\Libresign\Handler\DocMdpHandler; use OCA\Libresign\Helper\FileUploadHelper; @@ -239,7 +241,7 @@ public function testValidateNameIsMandatory():void { } public function testValidateEmptyUserCollection():void { - $this->expectExceptionMessage('Empty users list'); + $this->expectExceptionMessage('Empty signers list'); $response = $this->createMock(IResponse::class); $response @@ -261,7 +263,7 @@ public function testValidateEmptyUserCollection():void { } public function testValidateEmptyUsersCollection():void { - $this->expectExceptionMessage('Empty users list'); + $this->expectExceptionMessage('Empty signers list'); $this->getService()->validateNewRequestToFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], @@ -271,23 +273,23 @@ public function testValidateEmptyUsersCollection():void { } public function testValidateUserCollectionNotArray():void { - $this->expectExceptionMessage('User list needs to be an array'); + $this->expectExceptionMessage('Signers list needs to be an array'); $this->getService()->validateNewRequestToFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => 'asdfg', + 'signers' => 'asdfg', 'userManager' => $this->user ]); } public function testValidateUserEmptyCollection():void { - $this->expectExceptionMessage('Empty users list'); + $this->expectExceptionMessage('Empty signers list'); $this->getService()->validateNewRequestToFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => null, + 'signers' => null, 'userManager' => $this->user ]); } @@ -296,7 +298,7 @@ public function testValidateSuccess():void { $actual = $this->getService()->validateNewRequestToFile([ 'file' => ['base64' => base64_encode(file_get_contents(__DIR__ . '/../../fixtures/pdfs/small_valid.pdf'))], 'name' => 'test', - 'users' => [ + 'signers' => [ ['identify' => ['email' => 'jhondoe@test.coop']] ], 'userManager' => $this->user @@ -355,6 +357,87 @@ public static function dataSaveVisibleElements():array { ]; } + #[\PHPUnit\Framework\Attributes\DataProvider('providerSaveVisibleElementsOfEnvelope')] + public function testSaveVisibleElementsOfEnvelopeResolvesSignRequestId( + int $inputSignRequestId, + int $resolvedSignRequestFileId, + int $expectedPersistedSignRequestId, + array $childrenSignRequests, + bool $shouldTranslate, + ): void { + $libreSignFile = new \OCA\Libresign\Db\File(); + $libreSignFile->setId(544); + $libreSignFile->setNodeType('envelope'); + + $resolvedSignRequest = new SignRequest(); + $resolvedSignRequest->setId($inputSignRequestId); + $resolvedSignRequest->setFileId($resolvedSignRequestFileId); + + $this->signRequestMapper + ->expects($this->once()) + ->method('getById') + ->with($inputSignRequestId) + ->willReturn($resolvedSignRequest); + + if ($shouldTranslate) { + $this->signRequestMapper + ->expects($this->once()) + ->method('getByEnvelopeChildrenAndIdentifyMethod') + ->with(544, $inputSignRequestId) + ->willReturn($childrenSignRequests); + } else { + $this->signRequestMapper + ->expects($this->never()) + ->method('getByEnvelopeChildrenAndIdentifyMethod'); + } + + $this->fileElementService + ->expects($this->once()) + ->method('saveVisibleElement') + ->with($this->callback(function (array $element) use ($expectedPersistedSignRequestId): bool { + return $element['fileId'] === 545 + && $element['signRequestId'] === $expectedPersistedSignRequestId; + })) + ->willReturn(new FileElement()); + + $actual = self::invokePrivate($this->getService(), 'saveVisibleElements', [[ + 'visibleElements' => [[ + 'fileId' => 545, + 'signRequestId' => $inputSignRequestId, + 'coordinates' => ['page' => 1, 'left' => 100, 'top' => 20, 'width' => 300, 'height' => 100], + ]], + ], $libreSignFile]); + + $this->assertCount(1, $actual); + } + + public static function providerSaveVisibleElementsOfEnvelope(): array { + $childSignRequest603 = new SignRequest(); + $childSignRequest603->setId(603); + $childSignRequest603->setFileId(545); + + $otherChildSignRequest = new SignRequest(); + $otherChildSignRequest->setId(999); + $otherChildSignRequest->setFileId(999); + + return [ + 'keeps child signRequestId when already points to file child' => [ + 'inputSignRequestId' => 603, + 'resolvedSignRequestFileId' => 545, + 'expectedPersistedSignRequestId' => 603, + 'childrenSignRequests' => [], + 'shouldTranslate' => false, + ], + 'translates envelope signRequestId to matching child signRequestId' => [ + 'inputSignRequestId' => 602, + 'resolvedSignRequestFileId' => 544, + 'expectedPersistedSignRequestId' => 603, + 'childrenSignRequests' => [$otherChildSignRequest, $childSignRequest603], + 'shouldTranslate' => true, + ], + ]; + } + public function testParallelFlowIgnoresSignerDraftStatusWhenFileIsAbleToSign(): void { $sequentialSigningService = $this->createMock(SequentialSigningService::class); $sequentialSigningService diff --git a/tests/php/Unit/Service/SessionServiceTest.php b/tests/php/Unit/Service/SessionServiceTest.php deleted file mode 100644 index 8b00725fe3..0000000000 --- a/tests/php/Unit/Service/SessionServiceTest.php +++ /dev/null @@ -1,58 +0,0 @@ -session = $this->createMock(ISession::class); - $this->appConfig = $this->createMock(IAppConfig::class); - } - - private function getService(): SessionService { - return new SessionService( - $this->session, - $this->appConfig, - ); - } - - #[DataProvider('providerGetSessionId')] - public function testGetSessionIdResolvesByContext(?string $userId, mixed $uuid, string $expected): void { - $this->session->method('get') - ->willReturnCallback(function (string $key) use ($userId, $uuid) { - return match ($key) { - 'user_id' => $userId, - 'libresign-uuid' => $uuid, - default => null, - }; - }); - $this->session->method('getId') - ->willReturn('session-raw-id'); - - $this->assertSame($expected, $this->getService()->getSessionId()); - } - - public static function providerGetSessionId(): array { - return [ - 'authenticated keeps raw session id' => ['admin', 'public-uuid', 'session-raw-id'], - 'anonymous uses public uuid when available' => [null, 'public-uuid', 'public-uuid'], - 'anonymous ignores non-string public uuid' => [null, 123, 'session-raw-id'], - 'anonymous falls back to raw session id' => [null, null, 'session-raw-id'], - ]; - } -} diff --git a/tests/php/Unit/Service/SignFileServiceTest.php b/tests/php/Unit/Service/SignFileServiceTest.php index 772bc7c55a..53ba844aad 100644 --- a/tests/php/Unit/Service/SignFileServiceTest.php +++ b/tests/php/Unit/Service/SignFileServiceTest.php @@ -523,7 +523,7 @@ public function testCanDeleteRequestSignatureWhenNoSignatureWasRequested():void $this->expectExceptionMessage('No signature was requested to %'); $this->getService()->canDeleteRequestSignature([ 'uuid' => 'valid', - 'users' => [ + 'signers' => [ [ 'email' => 'test@test.coop' ]