Skip to content

Commit 3287d3f

Browse files
authored
Merge pull request #6262 from LibreSign/fix/envelope-child-handling
fix: envelope child handling
2 parents 8a0afc9 + 04f5c0a commit 3287d3f

File tree

5 files changed

+218
-6
lines changed

5 files changed

+218
-6
lines changed

lib/Db/FileMapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ public function neutralizeDeletedUser(string $userId, string $displayName): void
281281
*/
282282
public function getChildrenFiles(int $parentId): array {
283283
$cached = array_filter($this->file, fn ($f) => $f->getParentFileId() === $parentId);
284-
if (!empty($cached)) {
284+
if (!empty($cached) && count($cached) > 1) {
285285
return array_values($cached);
286286
}
287287

lib/Service/IdentifyMethod/IdentifyService.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,25 @@ private function propagateIdentifiedDateToEnvelopeChildren(IdentifyMethod $ident
6767
$signRequest = $this->signRequestMapper->getById($identifyMethod->getSignRequestId());
6868
$fileEntity = $this->fileMapper->getById($signRequest->getFileId());
6969

70-
if (method_exists($fileEntity, 'getNodeType') && $fileEntity->getNodeType() !== 'envelope') {
70+
if ($fileEntity->getNodeType() === 'envelope') {
71+
$envelopeId = $fileEntity->getId();
72+
} elseif ($fileEntity->hasParent()) {
73+
$envelopeId = $fileEntity->getParentFileId();
74+
} else {
7175
return;
7276
}
7377

7478
$children = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod(
75-
$fileEntity->getId(),
79+
$envelopeId,
7680
$signRequest->getId(),
7781
);
7882

7983
foreach ($children as $childSignRequest) {
84+
// Skip the current sign request to avoid updating it twice
85+
if ($childSignRequest->getId() === $signRequest->getId()) {
86+
continue;
87+
}
88+
8089
$childMethods = $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestId($childSignRequest->getId());
8190

8291
foreach ($childMethods as $childEntity) {

lib/Service/SignFileService.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,21 +388,26 @@ public function sign(): void {
388388
* @return array Array of ['file' => FileEntity, 'signRequest' => SignRequestEntity]
389389
*/
390390
private function getSignRequestsToSign(): array {
391-
if (!$this->libreSignFile->isEnvelope()) {
391+
if (!$this->libreSignFile->isEnvelope()
392+
&& !$this->libreSignFile->hasParent()
393+
) {
392394
return [[
393395
'file' => $this->libreSignFile,
394396
'signRequest' => $this->signRequest,
395397
]];
396398
}
397399

398-
$childFiles = $this->fileMapper->getChildrenFiles($this->libreSignFile->getId());
400+
$envelopeId = $this->libreSignFile->isEnvelope()
401+
? $this->libreSignFile->getId()
402+
: $this->libreSignFile->getParentFileId();
399403

404+
$childFiles = $this->fileMapper->getChildrenFiles($envelopeId);
400405
if (empty($childFiles)) {
401406
throw new LibresignException('No files found in envelope');
402407
}
403408

404409
$childSignRequests = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod(
405-
$this->libreSignFile->getId(),
410+
$envelopeId,
406411
$this->signRequest->getId()
407412
);
408413

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Libresign\Tests\Unit\Service\IdentifyMethod;
10+
11+
use OCA\Libresign\Db\File;
12+
use OCA\Libresign\Db\FileMapper;
13+
use OCA\Libresign\Db\IdentifyMethod;
14+
use OCA\Libresign\Db\IdentifyMethodMapper;
15+
use OCA\Libresign\Db\SignRequest;
16+
use OCA\Libresign\Db\SignRequestMapper;
17+
use OCA\Libresign\Service\IdentifyMethod\IdentifyService;
18+
use OCA\Libresign\Service\SessionService;
19+
use OCA\Libresign\Tests\Unit\TestCase;
20+
use OCP\AppFramework\Utility\ITimeFactory;
21+
use OCP\EventDispatcher\IEventDispatcher;
22+
use OCP\Files\IRootFolder;
23+
use OCP\IAppConfig;
24+
use OCP\IL10N;
25+
use OCP\IURLGenerator;
26+
use OCP\IUserManager;
27+
use OCP\Security\IHasher;
28+
use PHPUnit\Framework\MockObject\MockObject;
29+
use Psr\Log\LoggerInterface;
30+
31+
final class IdentifyServiceTest extends TestCase {
32+
private IdentifyMethodMapper&MockObject $identifyMethodMapper;
33+
private SessionService&MockObject $sessionService;
34+
private ITimeFactory&MockObject $timeFactory;
35+
private IEventDispatcher&MockObject $eventDispatcher;
36+
private IRootFolder&MockObject $rootFolder;
37+
private IAppConfig&MockObject $appConfig;
38+
private SignRequestMapper&MockObject $signRequestMapper;
39+
private IL10N&MockObject $l10n;
40+
private FileMapper&MockObject $fileMapper;
41+
private IHasher&MockObject $hasher;
42+
private IUserManager&MockObject $userManager;
43+
private IURLGenerator&MockObject $urlGenerator;
44+
private LoggerInterface&MockObject $logger;
45+
private IdentifyService $service;
46+
47+
public function setUp(): void {
48+
parent::setUp();
49+
50+
$this->identifyMethodMapper = $this->createMock(IdentifyMethodMapper::class);
51+
$this->sessionService = $this->createMock(SessionService::class);
52+
$this->timeFactory = $this->createMock(ITimeFactory::class);
53+
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
54+
$this->rootFolder = $this->createMock(IRootFolder::class);
55+
$this->appConfig = $this->createMock(IAppConfig::class);
56+
$this->signRequestMapper = $this->createMock(SignRequestMapper::class);
57+
$this->l10n = $this->createMock(IL10N::class);
58+
$this->fileMapper = $this->createMock(FileMapper::class);
59+
$this->hasher = $this->createMock(IHasher::class);
60+
$this->userManager = $this->createMock(IUserManager::class);
61+
$this->urlGenerator = $this->createMock(IURLGenerator::class);
62+
$this->logger = $this->createMock(LoggerInterface::class);
63+
64+
$this->service = new IdentifyService(
65+
$this->identifyMethodMapper,
66+
$this->sessionService,
67+
$this->timeFactory,
68+
$this->eventDispatcher,
69+
$this->rootFolder,
70+
$this->appConfig,
71+
$this->signRequestMapper,
72+
$this->l10n,
73+
$this->fileMapper,
74+
$this->hasher,
75+
$this->userManager,
76+
$this->urlGenerator,
77+
$this->logger,
78+
);
79+
}
80+
81+
public function testPropagateIdentifiedDateSkipsCurrentRequestAndUpdatesSiblings(): void {
82+
$identifyMethod = new IdentifyMethod();
83+
$identifyMethod->setSignRequestId(1);
84+
$identifyMethod->setIdentifierKey('email');
85+
$identifyMethod->setIdentifierValue('[email protected]');
86+
$identifyMethod->setIdentifiedAtDate('2024-01-01T00:00:00Z');
87+
88+
$parentEnvelopeId = 99;
89+
90+
$currentFile = new File();
91+
$currentFile->setId(10);
92+
$currentFile->setParentFileId($parentEnvelopeId);
93+
94+
$currentSignRequest = new SignRequest();
95+
$currentSignRequest->setId(1);
96+
$currentSignRequest->setFileId($currentFile->getId());
97+
98+
$siblingFile = new File();
99+
$siblingFile->setId(11);
100+
$siblingFile->setParentFileId($parentEnvelopeId);
101+
102+
$siblingSignRequest = new SignRequest();
103+
$siblingSignRequest->setId(2);
104+
$siblingSignRequest->setFileId($siblingFile->getId());
105+
106+
$this->signRequestMapper
107+
->expects($this->once())
108+
->method('getById')
109+
->with($identifyMethod->getSignRequestId())
110+
->willReturn($currentSignRequest);
111+
112+
$this->fileMapper
113+
->expects($this->once())
114+
->method('getById')
115+
->with($currentFile->getId())
116+
->willReturn($currentFile);
117+
118+
$this->signRequestMapper
119+
->expects($this->once())
120+
->method('getByEnvelopeChildrenAndIdentifyMethod')
121+
->with($parentEnvelopeId, $currentSignRequest->getId())
122+
->willReturn([$currentSignRequest, $siblingSignRequest]);
123+
124+
$siblingIdentifyMethod = new IdentifyMethod();
125+
$siblingIdentifyMethod->setSignRequestId($siblingSignRequest->getId());
126+
$siblingIdentifyMethod->setIdentifierKey($identifyMethod->getIdentifierKey());
127+
$siblingIdentifyMethod->setIdentifierValue($identifyMethod->getIdentifierValue());
128+
129+
$this->identifyMethodMapper
130+
->expects($this->exactly(2))
131+
->method('getIdentifyMethodsFromSignRequestId')
132+
->willReturnMap([
133+
[$identifyMethod->getSignRequestId(), []],
134+
[$siblingSignRequest->getId(), [$siblingIdentifyMethod]],
135+
]);
136+
137+
$this->identifyMethodMapper
138+
->expects($this->once())
139+
->method('insertOrUpdate')
140+
->with($identifyMethod);
141+
142+
$this->identifyMethodMapper
143+
->expects($this->once())
144+
->method('update')
145+
->with($this->callback(function (IdentifyMethod $updated) use ($siblingIdentifyMethod, $identifyMethod) {
146+
return $updated->getSignRequestId() === $siblingIdentifyMethod->getSignRequestId()
147+
&& $updated->getIdentifiedAtDate() == $identifyMethod->getIdentifiedAtDate();
148+
}));
149+
150+
$this->service->save($identifyMethod);
151+
}
152+
}

tests/php/Unit/Service/SignFileServiceTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,52 @@ public static function providerGetOrGeneratePfxContent(): array {
625625
];
626626
}
627627

628+
public function testGetSignRequestsToSignWhenFileHasParentEnvelope(): void {
629+
$service = $this->getService();
630+
631+
$envelopeId = 99;
632+
$childFile = new File();
633+
$childFile->setId(10);
634+
$childFile->setParentFileId($envelopeId);
635+
636+
$siblingFile = new File();
637+
$siblingFile->setId(11);
638+
$siblingFile->setParentFileId($envelopeId);
639+
640+
$signRequest = new SignRequest();
641+
$signRequest->setId(200);
642+
$signRequest->setFileId($childFile->getId());
643+
644+
$siblingSignRequest = new SignRequest();
645+
$siblingSignRequest->setId(201);
646+
$siblingSignRequest->setFileId($siblingFile->getId());
647+
648+
$this->fileMapper
649+
->expects($this->once())
650+
->method('getChildrenFiles')
651+
->with($envelopeId)
652+
->willReturn([$childFile, $siblingFile]);
653+
654+
$this->signRequestMapper
655+
->expects($this->once())
656+
->method('getByEnvelopeChildrenAndIdentifyMethod')
657+
->with($envelopeId, $signRequest->getId())
658+
->willReturn([$signRequest, $siblingSignRequest]);
659+
660+
$result = self::invokePrivate(
661+
$service
662+
->setLibreSignFile($childFile)
663+
->setSignRequest($signRequest),
664+
'getSignRequestsToSign'
665+
);
666+
667+
$this->assertCount(2, $result);
668+
$this->assertSame($childFile, $result[0]['file']);
669+
$this->assertSame($signRequest, $result[0]['signRequest']);
670+
$this->assertSame($siblingFile, $result[1]['file']);
671+
$this->assertSame($siblingSignRequest, $result[1]['signRequest']);
672+
}
673+
628674
#[DataProvider('providerStoreUserMetadata')]
629675
public function testStoreUserMetadata(bool $collectMetadata, ?array $previous, array $new, ?array $expected): void {
630676
$signRequest = new \OCA\Libresign\Db\SignRequest();

0 commit comments

Comments
 (0)