From 6a3d4dd514408330d74926fc5928a61aa654ca73 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:33:20 -0300 Subject: [PATCH 01/10] fix: aggregate visible elements by files Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/components/Request/VisibleElements.vue | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/Request/VisibleElements.vue b/src/components/Request/VisibleElements.vue index ba3e202e89..8a4d3dd180 100644 --- a/src/components/Request/VisibleElements.vue +++ b/src/components/Request/VisibleElements.vue @@ -213,6 +213,25 @@ export default { }) const childFiles = response?.data?.ocs?.data?.data || [] this.document.files = Array.isArray(childFiles) ? childFiles : [] + + const allVisibleElements = this.aggregateVisibleElementsByFiles(this.document.files) + if (allVisibleElements.length > 0) { + this.document.visibleElements = allVisibleElements + } + }, + 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 }, buildFilePagesMap() { this.filePagesMap = {} From 52110318e0b883026e73360be3a477d550ae48fc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:33:31 -0300 Subject: [PATCH 02/10] feat: refactor tests to cover new rule Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../Request/VisibleElements.spec.js | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/tests/components/Request/VisibleElements.spec.js b/src/tests/components/Request/VisibleElements.spec.js index 57b744fbc4..a7f80db528 100644 --- a/src/tests/components/Request/VisibleElements.spec.js +++ b/src/tests/components/Request/VisibleElements.spec.js @@ -602,4 +602,101 @@ describe('VisibleElements Component - Business Rules', () => { expect(showError).toHaveBeenCalledWith('save failed') }) }) + + describe('RULE: aggregateVisibleElementsByFiles', () => { + it.each([ + { + label: 'undefined input', + input: undefined, + expected: [], + }, + { + label: 'null input', + input: null, + expected: [], + }, + { + label: 'empty array', + input: [], + expected: [], + }, + { + label: 'mixed files with invalid entries', + input: [ + { id: 545, visibleElements: [{ elementId: 185, fileId: 545 }] }, + { id: 999, visibleElements: null }, + { id: 546, visibleElements: [{ elementId: 186, fileId: 546 }] }, + ], + expected: [ + { elementId: 185, fileId: 545 }, + { elementId: 186, fileId: 546 }, + ], + }, + { + label: 'preserves order when a file has multiple elements', + input: [ + { + id: 100, + visibleElements: [ + { elementId: 1, fileId: 100 }, + { elementId: 2, fileId: 100 }, + ], + }, + { id: 200, visibleElements: [{ elementId: 3, fileId: 200 }] }, + ], + expected: [ + { elementId: 1, fileId: 100 }, + { elementId: 2, fileId: 100 }, + { elementId: 3, fileId: 200 }, + ], + }, + ])('handles $label', ({ input, expected }) => { + expect(wrapper.vm.aggregateVisibleElementsByFiles(input)).toEqual(expected) + }) + }) + + describe('RULE: fetchFiles updates document files and visible elements', () => { + it.each([ + { + label: 'applies aggregated visible elements when available', + childFiles: [ + { id: 545, name: 'file1.pdf', visibleElements: [{ elementId: 185, fileId: 545 }] }, + { id: 546, name: 'file2.pdf', visibleElements: [{ elementId: 186, fileId: 546 }] }, + ], + initialVisibleElements: [{ elementId: 999, fileId: 1 }], + expectedVisibleElements: [ + { elementId: 185, fileId: 545 }, + { elementId: 186, fileId: 546 }, + ], + }, + { + label: 'keeps existing visibleElements when aggregated result is empty', + childFiles: [ + { id: 545, name: 'file1.pdf', visibleElements: [] }, + { id: 546, name: 'file2.pdf' }, + ], + initialVisibleElements: [{ elementId: 999, fileId: 1 }], + expectedVisibleElements: [{ elementId: 999, fileId: 1 }], + }, + ])('$label', async ({ childFiles, initialVisibleElements, expectedVisibleElements }) => { + filesStore.files[1].id = 544 + filesStore.files[1].files = [] + filesStore.files[1].visibleElements = initialVisibleElements + + axios.get.mockResolvedValue({ + data: { + ocs: { + data: { + data: childFiles, + }, + }, + }, + }) + + await wrapper.vm.fetchFiles() + + expect(wrapper.vm.document.files).toEqual(childFiles) + expect(wrapper.vm.document.visibleElements).toEqual(expectedVisibleElements) + }) + }) }) From bdafc335a34b1749b6437138b8442a19a719ea1f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:50:16 -0300 Subject: [PATCH 03/10] fix: stabilize anonymous signature elements session key Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/SessionService.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Service/SessionService.php b/lib/Service/SessionService.php index ffe3dc85ca..b893360a99 100644 --- a/lib/Service/SessionService.php +++ b/lib/Service/SessionService.php @@ -27,6 +27,10 @@ public function getSignStartTime(): int { } public function getSessionId(): string { + $uuid = $this->session->get('libresign-uuid'); + if (is_string($uuid) && $uuid !== '') { + return $uuid; + } return $this->session->getId(); } From 26f30a4a7e2a69ad5788638e9a7c49ac3dfd3dd8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:50:18 -0300 Subject: [PATCH 04/10] test: add session service data provider coverage Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/SessionServiceTest.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/php/Unit/Service/SessionServiceTest.php diff --git a/tests/php/Unit/Service/SessionServiceTest.php b/tests/php/Unit/Service/SessionServiceTest.php new file mode 100644 index 0000000000..420ec27548 --- /dev/null +++ b/tests/php/Unit/Service/SessionServiceTest.php @@ -0,0 +1,53 @@ +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 testGetSessionIdUsesUuidWhenAvailable(mixed $uuidInSession, string $sessionId, string $expected): void { + $this->session->method('get') + ->with('libresign-uuid') + ->willReturn($uuidInSession); + $this->session->method('getId') + ->willReturn($sessionId); + + $this->assertSame($expected, $this->getService()->getSessionId()); + } + + public static function providerGetSessionId(): array { + return [ + 'uuid string present' => ['54afbd0a-a065-4eaf-b611-48ec381b116a', 'session-123', '54afbd0a-a065-4eaf-b611-48ec381b116a'], + 'uuid empty string' => ['', 'session-123', 'session-123'], + 'uuid null' => [null, 'session-123', 'session-123'], + 'uuid non-string' => [123, 'session-123', 'session-123'], + ]; + } +} From 6ee7cfbf3bf6d722311dd7b727ce0cb242c92193 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:40:29 -0300 Subject: [PATCH 05/10] fix: refresh renewal interval config at runtime Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../IdentifyMethod/AbstractIdentifyMethod.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/Service/IdentifyMethod/AbstractIdentifyMethod.php b/lib/Service/IdentifyMethod/AbstractIdentifyMethod.php index 9a255ce903..465ac7643e 100644 --- a/lib/Service/IdentifyMethod/AbstractIdentifyMethod.php +++ b/lib/Service/IdentifyMethod/AbstractIdentifyMethod.php @@ -9,7 +9,6 @@ namespace OCA\Libresign\Service\IdentifyMethod; use DateTime; -use DateTimeInterface; use InvalidArgumentException; use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Db\IdentifyMethod; @@ -219,7 +218,7 @@ protected function throwIfInvalidToken(): void { protected function renewSession(): void { $this->identifyService->getSessionService()->setIdentifyMethodId($this->getEntity()->getId()); - $renewalInterval = (int)$this->identifyService->getAppConfig()->getValueInt(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL); + $renewalInterval = $this->getRuntimeConfigInt('renewal_interval', SessionService::NO_RENEWAL_INTERVAL); if ($renewalInterval <= 0) { return; } @@ -237,7 +236,7 @@ protected function updateIdentifiedAt(): void { } protected function throwIfRenewalIntervalExpired(): void { - $renewalInterval = (int)$this->identifyService->getAppConfig()->getValueInt(Application::APP_ID, 'renewal_interval', SessionService::NO_RENEWAL_INTERVAL); + $renewalInterval = $this->getRuntimeConfigInt('renewal_interval', SessionService::NO_RENEWAL_INTERVAL); if ($renewalInterval <= 0) { return; } @@ -250,24 +249,17 @@ protected function throwIfRenewalIntervalExpired(): void { } $createdAt = $signRequest->getCreatedAt(); $lastAttempt = $this->getEntity()->getLastAttemptDate(); + $identifiedAt = $this->getEntity()->getIdentifiedAtDate(); $lastActionDate = max( $startTime, $createdAt, $lastAttempt, + $identifiedAt, ); $now = $this->identifyService->getTimeFactory()->getDateTime(); - $this->identifyService->getLogger()->debug('AbstractIdentifyMethod::throwIfRenewalIntervalExpired Times', [ - 'renewalInterval' => $renewalInterval, - 'startTime' => $startTime, - 'createdAt' => $createdAt, - 'lastAttempt' => $lastAttempt, - 'lastActionDate' => $lastActionDate, - 'now' => $now->format(DateTimeInterface::ATOM), - ]); $endRenewal = (clone $lastActionDate) ->add(new \DateInterval('PT' . $renewalInterval . 'S')); if ($endRenewal < $now) { - $this->identifyService->getLogger()->debug('AbstractIdentifyMethod::throwIfRenewalIntervalExpired Exception'); if ($this->getName() === 'email') { $blur = new Blur($this->getEntity()->getIdentifierValue()); throw new LibresignException(json_encode([ @@ -296,6 +288,12 @@ private function getRenewAction(): int { }; } + private function getRuntimeConfigInt(string $key, int $default): int { + $appConfig = $this->identifyService->getAppConfig(); + $appConfig->clearCache(true); + return (int)$appConfig->getValueInt(Application::APP_ID, $key, $default); + } + protected function throwIfAlreadySigned(): void { $signRequest = $this->identifyService->getSignRequestMapper()->getById($this->getEntity()->getSignRequestId()); $fileEntity = $this->identifyService->getFileMapper()->getById($signRequest->getFileId()); From cc731c53764da2e8be0359e389e334082c4fbe9f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:40:42 -0300 Subject: [PATCH 06/10] fix: resolve session id by authentication context Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/SessionService.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Service/SessionService.php b/lib/Service/SessionService.php index b893360a99..3e62b9a747 100644 --- a/lib/Service/SessionService.php +++ b/lib/Service/SessionService.php @@ -27,6 +27,9 @@ public function getSignStartTime(): int { } public function getSessionId(): string { + if ($this->isAuthenticated()) { + return $this->session->getId(); + } $uuid = $this->session->get('libresign-uuid'); if (is_string($uuid) && $uuid !== '') { return $uuid; From 2550096e2332832d9fb52002c2431909a637117a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:40:57 -0300 Subject: [PATCH 07/10] test: cover session id resolution contexts Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/SessionServiceTest.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/php/Unit/Service/SessionServiceTest.php b/tests/php/Unit/Service/SessionServiceTest.php index 420ec27548..54b4c5336c 100644 --- a/tests/php/Unit/Service/SessionServiceTest.php +++ b/tests/php/Unit/Service/SessionServiceTest.php @@ -32,22 +32,26 @@ private function getService(): SessionService { } #[DataProvider('providerGetSessionId')] - public function testGetSessionIdUsesUuidWhenAvailable(mixed $uuidInSession, string $sessionId, string $expected): void { + public function testGetSessionIdResolvesByContext(?string $userId, mixed $uuid, string $expected): void { $this->session->method('get') - ->with('libresign-uuid') - ->willReturn($uuidInSession); + ->willReturnCallback(function (string $key) use ($userId, $uuid) { + return match ($key) { + 'user_id' => $userId, + 'libresign-uuid' => $uuid, + default => null, + }; + }); $this->session->method('getId') - ->willReturn($sessionId); + ->willReturn('session-raw-id'); $this->assertSame($expected, $this->getService()->getSessionId()); } public static function providerGetSessionId(): array { return [ - 'uuid string present' => ['54afbd0a-a065-4eaf-b611-48ec381b116a', 'session-123', '54afbd0a-a065-4eaf-b611-48ec381b116a'], - 'uuid empty string' => ['', 'session-123', 'session-123'], - 'uuid null' => [null, 'session-123', 'session-123'], - 'uuid non-string' => [123, 'session-123', 'session-123'], + 'authenticated keeps raw session id' => ['admin', 'public-uuid', 'session-raw-id'], + 'anonymous uses public uuid when available' => [null, 'public-uuid', 'public-uuid'], + 'anonymous falls back to raw session id' => [null, null, 'session-raw-id'], ]; } } From e9e117e359f050141592739c782a8ebb6cdd9593 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:41:18 -0300 Subject: [PATCH 08/10] test: add authenticated signature element scenario Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../features/account/signature.feature | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 023b6a0121..7e3555ae27 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -238,6 +238,22 @@ Feature: account/signature When sending "delete" to ocs "/apps/libresign/api/v1/signature/elements/" Then the response should have a status code 200 + Scenario: CRUD of signature element authenticated with public sign header + Given user "signer1" exists + And as user "signer1" + And set the custom http header "libresign-sign-request-uuid" with "11111111-1111-1111-1111-111111111111" as value to next request + When sending "post" to ocs "/apps/libresign/api/v1/signature/elements" + | elements | [{"type":"signature","file":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}}] | + Then the response should have a status code 200 + When sending "get" to ocs "/apps/libresign/api/v1/signature/elements" + Then the response should be a JSON array with the following mandatory values + | key | value | + | (jq).ocs.data.elements\|length | 1 | + | (jq).ocs.data.elements[0].type | signature | + And fetch field "(NODE_ID)ocs.data.elements.0.file.nodeId" from previous JSON response + When sending "delete" to ocs "/apps/libresign/api/v1/signature/elements/" + Then the response should have a status code 200 + Scenario: CRUD of signature element to signer by email without account Given run the command "config:app:set guests whitelist --value=libresign" with result code 0 And run the command "libresign:configure:openssl --cn test" with result code 0 From 9fbd6f63b549b38e9aad390a680feec7d6da6d2c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:49:20 -0300 Subject: [PATCH 09/10] test: extend session id provider coverage Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/php/Unit/Service/SessionServiceTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/php/Unit/Service/SessionServiceTest.php b/tests/php/Unit/Service/SessionServiceTest.php index 54b4c5336c..8b00725fe3 100644 --- a/tests/php/Unit/Service/SessionServiceTest.php +++ b/tests/php/Unit/Service/SessionServiceTest.php @@ -51,6 +51,7 @@ 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'], ]; } From 6ce76cc61b6b3fc7ba601671058dd1dcdd83bbf4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:49:37 -0300 Subject: [PATCH 10/10] test: cover abstract identify renewal rules Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../AbstractIdentifyMethodTest.php | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php diff --git a/tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php b/tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php new file mode 100644 index 0000000000..59eee545a3 --- /dev/null +++ b/tests/php/Unit/Service/IdentifyMethod/AbstractIdentifyMethodTest.php @@ -0,0 +1,184 @@ +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; + } +}