• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 21268273482

22 Jan 2026 11:13PM UTC coverage: 45.185%. First build
21268273482

Pull #6526

github

web-flow
Merge 16c32ce29 into 068f2ad0f
Pull Request #6526: fix: progress cache factory

84 of 183 new or added lines in 6 files covered. (45.9%)

7348 of 16262 relevant lines covered (45.19%)

4.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.29
/lib/Service/SignRequest/ProgressService.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service\SignRequest;
10

11
use OCA\Libresign\Db\File as FileEntity;
12
use OCA\Libresign\Db\FileMapper;
13
use OCA\Libresign\Db\SignRequest as SignRequestEntity;
14
use OCA\Libresign\Enum\FileStatus;
15
use OCA\Libresign\Enum\SignRequestStatus;
16
use OCP\ICache;
17
use OCP\ICacheFactory;
18

19
/**
20
 * Service for calculating and managing sign request progress
21
 *
22
 * This service encapsulates the business logic for:
23
 * - Calculating progress for specific sign requests
24
 * - Handling different file types (simple files, envelopes)
25
 * - Polling for status changes
26
 * - Building status responses
27
 *
28
 * Testable unit that can be tested independently of HTTP concerns
29
 */
30
class ProgressService {
31
        private ICache $cache;
32

33
        public function __construct(
34
                private FileMapper $fileMapper,
35
                ICacheFactory $cacheFactory,
36
        ) {
37
                $this->cache = $cacheFactory->createDistributed('libresign_progress');
9✔
38
        }
39

40
        /**
41
         * Poll for status change of a sign request
42
         *
43
         * Waits up to the specified timeout for the status to change by checking cache
44
         *
45
         * @return int The current status (changed or original if timeout reached)
46
         */
47
        public function pollForStatusChange(string $uuid, int $initialStatus, int $timeout = 30, int $intervalSeconds = 1): int {
48
                $cacheKey = 'status_' . $uuid;
2✔
49
                $cachedStatus = $this->cache->get($cacheKey);
2✔
50
                $interval = max(1, $intervalSeconds);
2✔
51

52
                for ($elapsed = 0; $elapsed < $timeout; $elapsed += $interval) {
2✔
53
                        $newCachedStatus = $this->cache->get($cacheKey);
2✔
54

55
                        if ($newCachedStatus !== $cachedStatus && $newCachedStatus !== false) {
2✔
56
                                return (int)$newCachedStatus;
1✔
57
                        }
58

59
                        if ($intervalSeconds > 0) {
2✔
NEW
60
                                sleep($intervalSeconds);
×
61
                        }
62
                }
63

64
                return $initialStatus;
1✔
65
        }
66

67
        /**
68
         * Get progress for a specific sign request
69
         *
70
         * Returns progress data tailored to the specific sign request,
71
         * not the global file status
72
         *
73
         * @return array<string, mixed> Progress data with structure: {total, signed, pending, files?, signers?}
74
         */
75
        public function getSignRequestProgress(FileEntity $file, SignRequestEntity $signRequest): array {
76
                return match (true) {
77
                        $file->getNodeType() === 'envelope' => $this->getEnvelopeProgressForSignRequest($file, $signRequest),
1✔
78
                        !$file->getParentFileId() => $this->getSingleFileProgressForSignRequest($file, $signRequest),
1✔
79
                        default => $this->getFileProgressForSignRequest($file, $signRequest),
1✔
80
                };
81
        }
82

83
        public function getStatusCodeForSignRequest(FileEntity $file, SignRequestEntity $signRequest): int {
NEW
84
                return $this->getSignRequestStatusCode($file, $signRequest);
×
85
        }
86

87
        public function isProgressComplete(array $progress): bool {
NEW
88
                $total = (int)($progress['total'] ?? 0);
×
NEW
89
                if ($total <= 0) {
×
NEW
90
                        return false;
×
91
                }
NEW
92
                $signed = (int)($progress['signed'] ?? 0);
×
NEW
93
                $pending = (int)($progress['pending'] ?? 0);
×
NEW
94
                $inProgress = (int)($progress['inProgress'] ?? 0);
×
NEW
95
                return $signed >= $total && $pending <= 0 && $inProgress <= 0;
×
96
        }
97

98
        /**
99
         * Get progress for a sign request on a single file
100
         *
101
         * Returns counts relative to the sign request status
102
         *
103
         * @return array<string, mixed>
104
         */
105
        public function getSingleFileProgressForSignRequest(FileEntity $file, SignRequestEntity $signRequest): array {
106
                $statusCode = $this->getSignRequestStatusCode($file, $signRequest);
4✔
107
                $isSigned = $statusCode === FileStatus::SIGNED->value;
4✔
108
                $isInProgress = $statusCode === FileStatus::SIGNING_IN_PROGRESS->value;
4✔
109

110
                return [
4✔
111
                        'total' => 1,
4✔
112
                        'signed' => $isSigned ? 1 : 0,
4✔
113
                        'inProgress' => $isInProgress ? 1 : 0,
4✔
114
                        'pending' => $isSigned || $isInProgress ? 0 : 1,
4✔
115
                        'files' => [
4✔
116
                                [
4✔
117
                                        'id' => $file->getId(),
4✔
118
                                        'name' => $file->getName(),
4✔
119
                                        'status' => $statusCode,
4✔
120
                                        'statusText' => $this->fileMapper->getTextOfStatus($statusCode),
4✔
121
                                ]
4✔
122
                        ],
4✔
123
                ];
4✔
124
        }
125

126
        /**
127
         * Get progress for a sign request on an envelope
128
         *
129
         * Returns progress for all files in the envelope relative to this signer
130
         *
131
         * @return array<string, mixed>
132
         */
133
        public function getEnvelopeProgressForSignRequest(FileEntity $envelope, SignRequestEntity $signRequest): array {
134
                $children = $this->fileMapper->getChildrenFiles($envelope->getId());
2✔
135
                if (empty($children)) {
2✔
NEW
136
                        $children = [$envelope];
×
137
                }
138

139
                $files = array_map(
2✔
140
                        fn ($child) => $this->mapSignRequestFileProgress($child, $signRequest),
2✔
141
                        $children
2✔
142
                );
2✔
143
                $total = count($files);
2✔
144
                $signed = count(array_filter($files, fn (array $file) => $file['status'] === FileStatus::SIGNED->value));
2✔
145
                $inProgress = count(array_filter($files, fn (array $file) => $file['status'] === FileStatus::SIGNING_IN_PROGRESS->value));
2✔
146
                $pending = max(0, $total - $signed - $inProgress);
2✔
147

148
                return [
2✔
149
                        'total' => $total,
2✔
150
                        'signed' => $signed,
2✔
151
                        'inProgress' => $inProgress,
2✔
152
                        'pending' => $pending,
2✔
153
                        'files' => $files,
2✔
154
                ];
2✔
155
        }
156

157
        /**
158
         * Get progress for a sign request on a child file in an envelope
159
         *
160
         * @return array<string, mixed>
161
         */
162
        public function getFileProgressForSignRequest(FileEntity $file, SignRequestEntity $signRequest): array {
163
                $statusCode = $this->getSignRequestStatusCode($file, $signRequest);
1✔
164
                $isSigned = $statusCode === FileStatus::SIGNED->value;
1✔
165
                $isInProgress = $statusCode === FileStatus::SIGNING_IN_PROGRESS->value;
1✔
166

167
                return [
1✔
168
                        'total' => 1,
1✔
169
                        'signed' => $isSigned ? 1 : 0,
1✔
170
                        'inProgress' => $isInProgress ? 1 : 0,
1✔
171
                        'pending' => $isSigned || $isInProgress ? 0 : 1,
1✔
172
                        'signers' => [
1✔
173
                                [
1✔
174
                                        'id' => $signRequest->getId(),
1✔
175
                                        'displayName' => $signRequest->getDisplayName(),
1✔
176
                                        'signed' => $signRequest->getSigned()?->format('c'),
1✔
177
                                        'status' => $statusCode,
1✔
178
                                ]
1✔
179
                        ],
1✔
180
                ];
1✔
181
        }
182

183
        /**
184
         * Map file progress data
185
         *
186
         * @return array<string, mixed>
187
         */
188
        private function mapFileProgress(FileEntity $file): array {
NEW
189
                return [
×
NEW
190
                        'id' => $file->getId(),
×
NEW
191
                        'name' => $file->getName(),
×
NEW
192
                        'status' => $file->getStatus(),
×
NEW
193
                        'statusText' => $this->fileMapper->getTextOfStatus($file->getStatus()),
×
NEW
194
                ];
×
195
        }
196

197
        private function mapSignRequestFileProgress(FileEntity $file, SignRequestEntity $signRequest): array {
198
                $statusCode = $this->getSignRequestStatusCode($file, $signRequest);
2✔
199
                return [
2✔
200
                        'id' => $file->getId(),
2✔
201
                        'name' => $file->getName(),
2✔
202
                        'status' => $statusCode,
2✔
203
                        'statusText' => $this->fileMapper->getTextOfStatus($statusCode),
2✔
204
                ];
2✔
205
        }
206

207
        private function getSignRequestStatusCode(FileEntity $file, SignRequestEntity $signRequest): int {
208
                if ($file->getStatus() === FileStatus::SIGNING_IN_PROGRESS->value) {
7✔
209
                        return FileStatus::SIGNING_IN_PROGRESS->value;
1✔
210
                }
211

212
                if ($signRequest->getSigned() !== null) {
6✔
213
                        return FileStatus::SIGNED->value;
2✔
214
                }
215

216
                return match ($signRequest->getStatusEnum()) {
4✔
217
                        SignRequestStatus::DRAFT => FileStatus::DRAFT->value,
4✔
NEW
218
                        SignRequestStatus::ABLE_TO_SIGN => FileStatus::ABLE_TO_SIGN->value,
×
219
                        default => $file->getStatus(),
4✔
220
                };
4✔
221
        }
222
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc