diff --git a/lib/Controller/RequestSignatureController.php b/lib/Controller/RequestSignatureController.php index 07fac53703..e436cfdbee 100644 --- a/lib/Controller/RequestSignatureController.php +++ b/lib/Controller/RequestSignatureController.php @@ -56,6 +56,7 @@ public function __construct( * @param string $name The name of file to sign * @param string|null $callback URL that will receive a POST after the document is signed * @param integer|null $status Numeric code of status * 0 - no signers * 1 - signed * 2 - pending + * @param string|null $signatureFlow Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration * @return DataResponse|DataResponse}, array{}> * * 200: OK @@ -65,7 +66,14 @@ public function __construct( #[NoCSRFRequired] #[RequireManager] #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])] - public function request(array $file, array $users, string $name, ?string $callback = null, ?int $status = 1): DataResponse { + public function request( + array $file, + array $users, + string $name, + ?string $callback = null, + ?int $status = 1, + ?string $signatureFlow = null, + ): DataResponse { $user = $this->userSession->getUser(); $data = [ 'file' => $file, @@ -73,7 +81,8 @@ public function request(array $file, array $users, string $name, ?string $callba 'users' => $users, 'status' => $status, 'callback' => $callback, - 'userManager' => $user + 'userManager' => $user, + 'signatureFlow' => $signatureFlow, ]; try { $this->requestSignatureService->validateNewRequestToFile($data); diff --git a/lib/Db/File.php b/lib/Db/File.php index ab92b067e9..856df3dc3e 100644 --- a/lib/Db/File.php +++ b/lib/Db/File.php @@ -8,6 +8,7 @@ namespace OCA\Libresign\Db; +use OCA\Libresign\Enum\SignatureFlow; use OCP\AppFramework\Db\Entity; use OCP\DB\Types; @@ -38,6 +39,8 @@ * @method ?array getMetadata() * @method void setModificationStatus(int $modificationStatus) * @method int getModificationStatus() + * @method void setSignatureFlow(int $signatureFlow) + * @method int getSignatureFlow() */ class File extends Entity { protected int $nodeId = 0; @@ -52,6 +55,7 @@ class File extends Entity { protected ?string $callback = null; protected ?array $metadata = null; protected int $modificationStatus = 0; + protected int $signatureFlow = SignatureFlow::NUMERIC_PARALLEL; public const STATUS_NOT_LIBRESIGN_FILE = -1; public const STATUS_DRAFT = 0; public const STATUS_ABLE_TO_SIGN = 1; @@ -78,6 +82,7 @@ public function __construct() { $this->addType('status', Types::INTEGER); $this->addType('metadata', Types::JSON); $this->addType('modificationStatus', Types::SMALLINT); + $this->addType('signatureFlow', Types::SMALLINT); } public function isDeletedAccount(): bool { @@ -89,4 +94,12 @@ public function getUserId(): string { $metadata = $this->getMetadata(); return $metadata['deleted_account']['account'] ?? $this->userId ?? ''; } + + public function getSignatureFlowEnum(): \OCA\Libresign\Enum\SignatureFlow { + return \OCA\Libresign\Enum\SignatureFlow::fromNumeric($this->signatureFlow); + } + + public function setSignatureFlowEnum(\OCA\Libresign\Enum\SignatureFlow $flow): void { + $this->setSignatureFlow($flow->toNumeric()); + } } diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index 0c8ad3ffdc..e1da090b47 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -9,6 +9,7 @@ namespace OCA\Libresign\Db; use DateTimeInterface; +use OCA\Libresign\Enum\SignatureFlow; use OCA\Libresign\Enum\SignRequestStatus; use OCA\Libresign\Helper\Pagination; use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod; @@ -500,6 +501,7 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array 'f.status', 'f.metadata', 'f.created_at', + 'f.signature_flow', ) ->groupBy( 'f.id', @@ -510,6 +512,7 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array 'f.name', 'f.status', 'f.created_at', + 'f.signature_flow', ); // metadata is a json column, the right way is to use f.metadata::text // when the database is PostgreSQL. The problem is that the command @@ -620,11 +623,13 @@ private function formatListRow(array $row): array { $row['nodeId'] = (int)$row['node_id']; $row['name'] = $this->removeExtensionFromName($row['name'], $row['metadata']); + $row['signatureFlow'] = SignatureFlow::fromNumeric((int)($row['signature_flow']))->value; unset( $row['user_id'], $row['node_id'], $row['signed_node_id'], + $row['signature_flow'], ); return $row; } diff --git a/lib/Enum/SignatureFlow.php b/lib/Enum/SignatureFlow.php index ba4d99383c..71d31ba538 100644 --- a/lib/Enum/SignatureFlow.php +++ b/lib/Enum/SignatureFlow.php @@ -15,4 +15,22 @@ enum SignatureFlow: string { case PARALLEL = 'parallel'; case ORDERED_NUMERIC = 'ordered_numeric'; + + public const NUMERIC_PARALLEL = 1; + public const NUMERIC_ORDERED_NUMERIC = 2; + + public function toNumeric(): int { + return match($this) { + self::PARALLEL => self::NUMERIC_PARALLEL, + self::ORDERED_NUMERIC => self::NUMERIC_ORDERED_NUMERIC, + }; + } + + public static function fromNumeric(int $value): self { + return match($value) { + self::NUMERIC_PARALLEL => self::PARALLEL, + self::NUMERIC_ORDERED_NUMERIC => self::ORDERED_NUMERIC, + default => throw new \ValueError("Invalid numeric value for SignatureFlow: $value"), + }; + } } diff --git a/lib/Migration/Version15000Date20251209000000.php b/lib/Migration/Version15000Date20251209000000.php index d48d8d4346..d68f3947e4 100644 --- a/lib/Migration/Version15000Date20251209000000.php +++ b/lib/Migration/Version15000Date20251209000000.php @@ -9,6 +9,7 @@ namespace OCA\Libresign\Migration; use Closure; +use OCA\Libresign\Enum\SignatureFlow; use OCP\DB\ISchemaWrapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\Types; @@ -56,6 +57,17 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt } } + if ($schema->hasTable('libresign_file')) { + $tableFile = $schema->getTable('libresign_file'); + if (!$tableFile->hasColumn('signature_flow')) { + $tableFile->addColumn('signature_flow', Types::SMALLINT, [ + 'notnull' => true, + 'default' => SignatureFlow::NUMERIC_PARALLEL, + 'comment' => 'Signature flow mode: 1=parallel, 2=ordered_numeric', + ]); + } + } + return $schema; } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index b712ad0417..2b40542a06 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -186,6 +186,7 @@ * status: 0|1|2|3|4, * statusText: string, * nodeId: non-negative-int, + * signatureFlow: int, * totalPages: non-negative-int, * size: non-negative-int, * pdfVersion: string, diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 1aafd9ff89..e75cb9f845 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -711,6 +711,7 @@ private function loadLibreSignData(): void { $this->fileData->created_at = $this->file->getCreatedAt()->format(DateTimeInterface::ATOM); $this->fileData->statusText = $this->fileMapper->getTextOfStatus($this->file->getStatus()); $this->fileData->nodeId = $this->file->getNodeId(); + $this->fileData->signatureFlow = $this->file->getSignatureFlow(); $this->fileData->requested_by = [ 'userId' => $this->file->getUserId(), diff --git a/lib/Service/RequestSignatureService.php b/lib/Service/RequestSignatureService.php index 2da4aa1514..d65d900c83 100644 --- a/lib/Service/RequestSignatureService.php +++ b/lib/Service/RequestSignatureService.php @@ -8,12 +8,14 @@ namespace OCA\Libresign\Service; +use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Db\File as FileEntity; use OCA\Libresign\Db\FileElementMapper; use OCA\Libresign\Db\FileMapper; use OCA\Libresign\Db\IdentifyMethodMapper; use OCA\Libresign\Db\SignRequest as SignRequestEntity; use OCA\Libresign\Db\SignRequestMapper; +use OCA\Libresign\Enum\SignatureFlow; use OCA\Libresign\Handler\DocMdpHandler; use OCA\Libresign\Helper\ValidateHelper; use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod; @@ -21,6 +23,7 @@ use OCP\Files\IMimeTypeDetector; use OCP\Files\Node; use OCP\Http\Client\IClientService; +use OCP\IAppConfig; use OCP\IL10N; use OCP\IUser; use OCP\IUserManager; @@ -47,6 +50,7 @@ public function __construct( protected DocMdpHandler $docMdpHandler, protected LoggerInterface $logger, protected SequentialSigningService $sequentialSigningService, + protected IAppConfig $appConfig, ) { } @@ -56,6 +60,7 @@ public function save(array $data): FileEntity { if (!isset($data['status'])) { $data['status'] = $file->getStatus(); } + $this->sequentialSigningService->setFile($file); $this->associateToSigners($data, $file->getId()); return $file; } @@ -106,10 +111,28 @@ public function saveFile(array $data): FileEntity { } else { $file->setStatus(FileEntity::STATUS_ABLE_TO_SIGN); } + + if (isset($data['signatureFlow']) && is_string($data['signatureFlow'])) { + try { + $signatureFlow = \OCA\Libresign\Enum\SignatureFlow::from($data['signatureFlow']); + $file->setSignatureFlowEnum($signatureFlow); + } catch (\ValueError) { + $this->setSignatureFlowFromGlobalConfig($file); + } + } else { + $this->setSignatureFlowFromGlobalConfig($file); + } + $this->fileMapper->insert($file); return $file; } + private function setSignatureFlowFromGlobalConfig(FileEntity $file): void { + $globalFlowValue = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::PARALLEL->value); + $globalFlow = SignatureFlow::from($globalFlowValue); + $file->setSignatureFlowEnum($globalFlow); + } + private function updateStatus(FileEntity $file, int $status): FileEntity { if ($status > $file->getStatus()) { $file->setStatus($status); diff --git a/lib/Service/SequentialSigningService.php b/lib/Service/SequentialSigningService.php index a639cd00cc..43109d189b 100644 --- a/lib/Service/SequentialSigningService.php +++ b/lib/Service/SequentialSigningService.php @@ -8,7 +8,7 @@ namespace OCA\Libresign\Service; -use OCA\Libresign\AppInfo\Application; +use OCA\Libresign\Db\File as FileEntity; use OCA\Libresign\Db\SignRequestMapper; use OCA\Libresign\Enum\SignatureFlow; use OCA\Libresign\Enum\SignRequestStatus; @@ -16,6 +16,7 @@ class SequentialSigningService { private int $currentOrder = 1; + private ?FileEntity $file = null; public function __construct( private IAppConfig $appConfig, @@ -24,9 +25,11 @@ public function __construct( ) { } - /** - * Check if ordered numeric flow is enabled - */ + public function setFile(FileEntity $file): self { + $this->file = $file; + return $this; + } + public function isOrderedNumericFlow(): bool { return $this->getSignatureFlow() === SignatureFlow::ORDERED_NUMERIC; } @@ -51,8 +54,8 @@ public function determineSigningOrder(?int $userProvidedOrder): int { } if ($userProvidedOrder !== null) { - if ($userProvidedOrder > $this->currentOrder) { - $this->currentOrder = $userProvidedOrder; + if ($userProvidedOrder >= $this->currentOrder) { + $this->currentOrder = $userProvidedOrder + 1; } return $userProvidedOrder; } @@ -162,13 +165,10 @@ private function activateSignersForOrder(array $signRequests, int $order): void } private function getSignatureFlow(): SignatureFlow { - $value = $this->appConfig->getValueString( - Application::APP_ID, - 'signature_flow', - SignatureFlow::PARALLEL->value - ); - - return SignatureFlow::from($value); + if ($this->file === null) { + throw new \LogicException('File must be set before calling getSignatureFlow(). Call setFile() first.'); + } + return $this->file->getSignatureFlowEnum(); } /** diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index c67ff9f46d..5c167e7813 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -380,10 +380,12 @@ protected function updateSignRequest(string $hash): void { $this->signRequestMapper->update($this->signRequest); - $this->sequentialSigningService->releaseNextOrder( - $this->signRequest->getFileId(), - $this->signRequest->getSigningOrder() - ); + $this->sequentialSigningService + ->setFile($this->libreSignFile) + ->releaseNextOrder( + $this->signRequest->getFileId(), + $this->signRequest->getSigningOrder() + ); } protected function updateLibreSignFile(string $hash): void { diff --git a/openapi-full.json b/openapi-full.json index b4eb7cf6e2..3aaf844a4b 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -1012,6 +1012,7 @@ "status", "statusText", "nodeId", + "signatureFlow", "totalPages", "size", "pdfVersion", @@ -1045,6 +1046,10 @@ "format": "int64", "minimum": 0 }, + "signatureFlow": { + "type": "integer", + "format": "int64" + }, "totalPages": { "type": "integer", "format": "int64", @@ -6456,6 +6461,11 @@ "nullable": true, "default": 1, "description": "Numeric code of status * 0 - no signers * 1 - signed * 2 - pending" + }, + "signatureFlow": { + "type": "string", + "nullable": true, + "description": "Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration" } } } diff --git a/openapi.json b/openapi.json index 65d463c12c..ae39e7a0b4 100644 --- a/openapi.json +++ b/openapi.json @@ -862,6 +862,7 @@ "status", "statusText", "nodeId", + "signatureFlow", "totalPages", "size", "pdfVersion", @@ -895,6 +896,10 @@ "format": "int64", "minimum": 0 }, + "signatureFlow": { + "type": "integer", + "format": "int64" + }, "totalPages": { "type": "integer", "format": "int64", @@ -6306,6 +6311,11 @@ "nullable": true, "default": 1, "description": "Numeric code of status * 0 - no signers * 1 - signed * 2 - pending" + }, + "signatureFlow": { + "type": "string", + "nullable": true, + "description": "Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration" } } } diff --git a/src/Components/RightSidebar/RequestSignatureTab.vue b/src/Components/RightSidebar/RequestSignatureTab.vue index 0fdc4e6d3f..7e2b6d911d 100644 --- a/src/Components/RightSidebar/RequestSignatureTab.vue +++ b/src/Components/RightSidebar/RequestSignatureTab.vue @@ -241,10 +241,13 @@ export default { document: {}, hasInfo: false, methods: [], - signatureFlow: loadState('libresign', 'signature_flow', 'parallel'), } }, computed: { + signatureFlow() { + const file = this.filesStore.getFile() + return file?.signatureFlow ?? 'parallel' + }, isOrderedNumeric() { return this.signatureFlow === 'ordered_numeric' }, diff --git a/src/Components/Signers/Signer.vue b/src/Components/Signers/Signer.vue index d2320041b2..57d0de5809 100644 --- a/src/Components/Signers/Signer.vue +++ b/src/Components/Signers/Signer.vue @@ -89,10 +89,13 @@ export default { data() { return { canRequestSign: loadState('libresign', 'can_request_sign', false), - signatureFlow: loadState('libresign', 'signature_flow', 'parallel'), } }, computed: { + signatureFlow() { + const file = this.filesStore.getFile() + return file?.signatureFlow ?? 'parallel' + }, signer() { const file = this.filesStore.getFile() return file?.signers?.[this.signerIndex] diff --git a/src/Components/Signers/Signers.vue b/src/Components/Signers/Signers.vue index 746d18d92e..4e3b62eb34 100644 --- a/src/Components/Signers/Signers.vue +++ b/src/Components/Signers/Signers.vue @@ -82,7 +82,8 @@ export default { }, }, isOrderedNumeric() { - return loadState('libresign', 'signature_flow', 'parallel') === 'ordered_numeric' + const file = this.filesStore.getFile() + return file?.signatureFlow === 'ordered_numeric' }, canReorder() { return this.filesStore.canSave() && this.signers.length > 1 diff --git a/src/store/files.js b/src/store/files.js index 11d3ac23f6..0b210bc129 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -215,12 +215,9 @@ export const useFilesStore = function(...args) { break } } - if (!signer.signingOrder) { - const signatureFlow = loadState('libresign', 'signature_flow', 'parallel') - if (signatureFlow === 'ordered_numeric') { - const maxOrder = this.getFile().signers.reduce((max, s) => Math.max(max, s.signingOrder || 0), 0) - signer.signingOrder = maxOrder + 1 - } + if (!signer.signingOrder && this.getFile().signatureFlow === 'ordered_numeric') { + const maxOrder = this.getFile().signers.reduce((max, s) => Math.max(max, s.signingOrder || 0), 0) + signer.signingOrder = maxOrder + 1 } this.getFile().signers.push(signer) const selected = this.selectedNodeId @@ -236,15 +233,13 @@ export const useFilesStore = function(...args) { })) } - const signatureFlow = loadState('libresign', 'signature_flow', 'parallel') - set( this.files[this.selectedNodeId], 'signers', this.files[this.selectedNodeId].signers.filter((i) => i.identify !== signer.identify), ) - if (signatureFlow === 'ordered_numeric' && signer.signingOrder) { + if (this.getFile().signatureFlow === 'ordered_numeric' && signer.signingOrder) { this.files[this.selectedNodeId].signers.forEach((s) => { if (s.signingOrder && s.signingOrder > signer.signingOrder) { s.signingOrder -= 1 diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 716dc960be..62aa3199dd 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1757,6 +1757,8 @@ export type components = { /** Format: int64 */ nodeId: number; /** Format: int64 */ + signatureFlow: number; + /** Format: int64 */ totalPages: number; /** Format: int64 */ size: number; @@ -4016,6 +4018,8 @@ export interface operations { * @default 1 */ status?: number | null; + /** @description Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration */ + signatureFlow?: string | null; }; }; }; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 6fa82b0071..89d12c914f 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1279,6 +1279,8 @@ export type components = { /** Format: int64 */ nodeId: number; /** Format: int64 */ + signatureFlow: number; + /** Format: int64 */ totalPages: number; /** Format: int64 */ size: number; @@ -3538,6 +3540,8 @@ export interface operations { * @default 1 */ status?: number | null; + /** @description Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration */ + signatureFlow?: string | null; }; }; }; diff --git a/tests/php/Unit/Db/FileTest.php b/tests/php/Unit/Db/FileTest.php new file mode 100644 index 0000000000..05dd0f9751 --- /dev/null +++ b/tests/php/Unit/Db/FileTest.php @@ -0,0 +1,38 @@ +file = new File(); + } + + public function testGetSignatureFlowEnumConvertsFromInt(): void { + $this->file->setSignatureFlow(1); + $this->assertEquals(SignatureFlow::PARALLEL, $this->file->getSignatureFlowEnum()); + + $this->file->setSignatureFlow(2); + $this->assertEquals(SignatureFlow::ORDERED_NUMERIC, $this->file->getSignatureFlowEnum()); + } + + public function testSetSignatureFlowEnumConvertsToInt(): void { + $this->file->setSignatureFlowEnum(SignatureFlow::PARALLEL); + $this->assertEquals(1, $this->file->getSignatureFlow()); + + $this->file->setSignatureFlowEnum(SignatureFlow::ORDERED_NUMERIC); + $this->assertEquals(2, $this->file->getSignatureFlow()); + } +} diff --git a/tests/php/Unit/Enum/SignatureFlowTest.php b/tests/php/Unit/Enum/SignatureFlowTest.php new file mode 100644 index 0000000000..b3bc8ed998 --- /dev/null +++ b/tests/php/Unit/Enum/SignatureFlowTest.php @@ -0,0 +1,46 @@ + [SignatureFlow::PARALLEL, SignatureFlow::NUMERIC_PARALLEL, 'parallel'], + 'ordered_numeric' => [SignatureFlow::ORDERED_NUMERIC, SignatureFlow::NUMERIC_ORDERED_NUMERIC, 'ordered_numeric'], + ]; + } + + #[DataProvider('validFlowProvider')] + public function testBidirectionalConversion(SignatureFlow $flow, int $expectedNumeric, string $expectedString): void { + $this->assertEquals($expectedNumeric, $flow->toNumeric()); + $this->assertSame($flow, SignatureFlow::fromNumeric($expectedNumeric)); + $this->assertEquals($expectedString, $flow->value); + } + + public static function invalidNumericProvider(): array { + return [ + 'zero' => [0], + 'negative' => [-1], + 'three' => [3], + 'large' => [999], + 'max_int' => [PHP_INT_MAX], + ]; + } + + #[DataProvider('invalidNumericProvider')] + public function testFromNumericRejectsInvalidValues(int $invalidValue): void { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage("Invalid numeric value for SignatureFlow: $invalidValue"); + SignatureFlow::fromNumeric($invalidValue); + } +} diff --git a/tests/php/Unit/Service/RequestSignatureServiceTest.php b/tests/php/Unit/Service/RequestSignatureServiceTest.php index a3cf3fabad..a7b9d6c2f9 100644 --- a/tests/php/Unit/Service/RequestSignatureServiceTest.php +++ b/tests/php/Unit/Service/RequestSignatureServiceTest.php @@ -18,10 +18,12 @@ use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\PdfParserService; use OCA\Libresign\Service\RequestSignatureService; +use OCA\Libresign\Service\SequentialSigningService; use OCP\Files\IMimeTypeDetector; use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; +use OCP\IAppConfig; use OCP\IL10N; use OCP\IUser; use OCP\IUserManager; @@ -46,7 +48,8 @@ final class RequestSignatureServiceTest extends \OCA\Libresign\Tests\Unit\TestCa private IClientService&MockObject $client; private DocMdpHandler&MockObject $docMdpHandler; private LoggerInterface&MockObject $loggerInterface; - private \OCA\Libresign\Service\SequentialSigningService&MockObject $sequentialSigningService; + private SequentialSigningService&MockObject $sequentialSigningService; + private IAppConfig&MockObject $appConfig; public function setUp(): void { parent::setUp(); @@ -71,7 +74,8 @@ public function setUp(): void { $this->client = $this->createMock(IClientService::class); $this->docMdpHandler = $this->createMock(DocMdpHandler::class); $this->loggerInterface = $this->createMock(LoggerInterface::class); - $this->sequentialSigningService = $this->createMock(\OCA\Libresign\Service\SequentialSigningService::class); + $this->sequentialSigningService = $this->createMock(SequentialSigningService::class); + $this->appConfig = $this->createMock(IAppConfig::class); } private function getService(): RequestSignatureService { @@ -91,7 +95,8 @@ private function getService(): RequestSignatureService { $this->client, $this->docMdpHandler, $this->loggerInterface, - $this->sequentialSigningService + $this->sequentialSigningService, + $this->appConfig, ); } diff --git a/tests/php/Unit/Service/SequentialSigningServiceTest.php b/tests/php/Unit/Service/SequentialSigningServiceTest.php new file mode 100644 index 0000000000..5025f51589 --- /dev/null +++ b/tests/php/Unit/Service/SequentialSigningServiceTest.php @@ -0,0 +1,120 @@ +appConfig = $this->createMock(IAppConfig::class); + $this->signRequestMapper = $this->createMock(SignRequestMapper::class); + $this->identifyMethodService = $this->createMock(IdentifyMethodService::class); + + $this->service = new SequentialSigningService( + $this->appConfig, + $this->signRequestMapper, + $this->identifyMethodService + ); + } + + public function testIsOrderedNumericFlowThrowsExceptionWhenFileNotSet(): void { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('File must be set before calling getSignatureFlow(). Call setFile() first.'); + + $this->service->isOrderedNumericFlow(); + } + + public static function signatureFlowProvider(): array { + return [ + 'parallel flow' => [SignatureFlow::PARALLEL, false], + 'ordered numeric flow' => [SignatureFlow::ORDERED_NUMERIC, true], + ]; + } + + #[DataProvider('signatureFlowProvider')] + public function testIsOrderedNumericFlow(SignatureFlow $flow, bool $expected): void { + $file = $this->createMock(FileEntity::class); + $file->method('getSignatureFlowEnum') + ->willReturn($flow); + + $this->service->setFile($file); + + $this->assertSame($expected, $this->service->isOrderedNumericFlow()); + } + + public static function parallelOrderProvider(): array { + return [ + 'always returns 1' => [[null, null, 5], [1, 1, 1]], + ]; + } + + public static function orderedNumericOrderProvider(): array { + return [ + 'auto-increment' => [[null, null, null], [1, 2, 3]], + 'user-provided order jumps' => [[null, 5, null], [1, 5, 6]], + ]; + } + + #[DataProvider('parallelOrderProvider')] + public function testDetermineSigningOrderParallel(array $inputs, array $expected): void { + $file = $this->createMock(FileEntity::class); + $file->method('getSignatureFlowEnum') + ->willReturn(SignatureFlow::PARALLEL); + + $this->service->setFile($file); + $this->service->resetOrderCounter(); + + foreach ($inputs as $index => $input) { + $this->assertEquals($expected[$index], $this->service->determineSigningOrder($input)); + } + } + + #[DataProvider('orderedNumericOrderProvider')] + public function testDetermineSigningOrderOrderedNumeric(array $inputs, array $expected): void { + $file = $this->createMock(FileEntity::class); + $file->method('getSignatureFlowEnum') + ->willReturn(SignatureFlow::ORDERED_NUMERIC); + + $this->service->setFile($file); + $this->service->resetOrderCounter(); + + foreach ($inputs as $index => $input) { + $this->assertEquals($expected[$index], $this->service->determineSigningOrder($input)); + } + } + + public function testResetOrderCounter(): void { + $file = $this->createMock(FileEntity::class); + $file->method('getSignatureFlowEnum') + ->willReturn(SignatureFlow::ORDERED_NUMERIC); + + $this->service->setFile($file); + + $this->assertEquals(1, $this->service->determineSigningOrder(null)); + $this->assertEquals(2, $this->service->determineSigningOrder(null)); + + $this->service->resetOrderCounter(); + + $this->assertEquals(1, $this->service->determineSigningOrder(null)); + } +}