Skip to content

Commit e34ebde

Browse files
committed
test(cfssl): cover binary checks and process integration
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 48a426f commit e34ebde

1 file changed

Lines changed: 209 additions & 0 deletions

File tree

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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\Tests\Unit\Handler\CertificateEngine;
10+
11+
use OCA\Libresign\Db\CrlMapper;
12+
use OCA\Libresign\Handler\CertificateEngine\CfsslHandler;
13+
use OCA\Libresign\Handler\CfsslServerHandler;
14+
use OCA\Libresign\Service\CaIdentifierService;
15+
use OCA\Libresign\Service\CertificatePolicyService;
16+
use OCA\Libresign\Service\Crl\CrlRevocationChecker;
17+
use OCA\Libresign\Service\Install\InstallService;
18+
use OCA\Libresign\Service\Process\ProcessManager;
19+
use OCA\Libresign\Tests\Unit\TestCase;
20+
use OCA\Libresign\Vendor\Symfony\Component\Process\Process;
21+
use OCP\Files\AppData\IAppDataFactory;
22+
use OCP\IAppConfig;
23+
use OCP\IConfig;
24+
use OCP\IDateTimeFormatter;
25+
use OCP\ITempManager;
26+
use OCP\IURLGenerator;
27+
use PHPUnit\Framework\MockObject\MockObject;
28+
use Psr\Log\LoggerInterface;
29+
30+
class CfsslHandlerTest extends TestCase {
31+
private const PROCESS_SOURCE = 'cfssl';
32+
33+
private ProcessManager&MockObject $processManager;
34+
35+
public function setUp(): void {
36+
parent::setUp();
37+
$this->processManager = $this->createMock(ProcessManager::class);
38+
}
39+
40+
public function testGetServerPidReadsFromRegistry(): void {
41+
$handler = $this->createHandler();
42+
43+
$this->processManager->expects($this->once())
44+
->method('findRunningPid')
45+
->with(self::PROCESS_SOURCE, $this->callback('is_callable'))
46+
->willReturnCallback(function (string $_source, callable $filter): int {
47+
return $filter([
48+
'pid' => 302,
49+
'context' => ['uri' => CfsslHandler::CFSSL_URI],
50+
'createdAt' => 123,
51+
]) ? 302 : 0;
52+
});
53+
54+
$actual = self::invokePrivate($handler, 'getServerPid');
55+
56+
$this->assertSame(302, $actual);
57+
}
58+
59+
public function testScopedProcessClassIsAvailable(): void {
60+
$this->assertTrue(class_exists(Process::class));
61+
}
62+
63+
public function testCheckBinariesReturnsErrorWhenProcessFails(): void {
64+
$binary = tempnam(sys_get_temp_dir(), 'cfssl-bin-');
65+
$this->assertNotFalse($binary);
66+
67+
$process = $this->createMock(Process::class);
68+
$process->expects($this->once())
69+
->method('run');
70+
$process->expects($this->once())
71+
->method('getOutput')
72+
->willReturn('');
73+
$process->expects($this->once())
74+
->method('isSuccessful')
75+
->willReturn(false);
76+
77+
$handler = $this->createHandler($process, (string)$binary);
78+
$result = self::invokePrivate($handler, 'checkBinaries');
79+
80+
$this->assertSame('error', $result[0]->jsonSerialize()['status']);
81+
$this->assertStringContainsString('Failed to run the command', $result[0]->jsonSerialize()['message']);
82+
83+
@unlink((string)$binary);
84+
}
85+
86+
public function testCheckBinariesReturnsSuccessWhenProcessOutputIsValid(): void {
87+
$binary = tempnam(sys_get_temp_dir(), 'cfssl-bin-');
88+
$this->assertNotFalse($binary);
89+
90+
$process = $this->createMock(Process::class);
91+
$process->expects($this->once())
92+
->method('run');
93+
$process->expects($this->once())
94+
->method('isSuccessful')
95+
->willReturn(true);
96+
$process->expects($this->once())
97+
->method('getOutput')
98+
->willReturn('Version: ' . InstallService::CFSSL_VERSION . "\nRuntime: go1.22\n");
99+
100+
$handler = $this->createHandler($process, (string)$binary);
101+
$result = self::invokePrivate($handler, 'checkBinaries');
102+
103+
$this->assertCount(3, $result);
104+
$this->assertSame('success', $result[0]->jsonSerialize()['status']);
105+
$this->assertSame('success', $result[1]->jsonSerialize()['status']);
106+
$this->assertSame('success', $result[2]->jsonSerialize()['status']);
107+
108+
@unlink((string)$binary);
109+
}
110+
111+
public function testCheckBinariesReturnsErrorWhenVersionOutputFormatIsInvalid(): void {
112+
$binary = tempnam(sys_get_temp_dir(), 'cfssl-bin-');
113+
$this->assertNotFalse($binary);
114+
115+
$process = $this->createMock(Process::class);
116+
$process->expects($this->once())
117+
->method('run');
118+
$process->expects($this->once())
119+
->method('isSuccessful')
120+
->willReturn(true);
121+
$process->expects($this->once())
122+
->method('getOutput')
123+
->willReturn('cfssl version output without expected separators');
124+
125+
$handler = $this->createHandler($process, (string)$binary);
126+
$result = self::invokePrivate($handler, 'checkBinaries');
127+
128+
$this->assertSame('error', $result[0]->jsonSerialize()['status']);
129+
$this->assertStringContainsString('Failed to identify cfssl version', $result[0]->jsonSerialize()['message']);
130+
131+
@unlink((string)$binary);
132+
}
133+
134+
public function testCheckBinariesReturnsErrorWhenCfsslVersionDoesNotMatchExpected(): void {
135+
$binary = tempnam(sys_get_temp_dir(), 'cfssl-bin-');
136+
$this->assertNotFalse($binary);
137+
138+
$process = $this->createMock(Process::class);
139+
$process->expects($this->once())
140+
->method('run');
141+
$process->expects($this->once())
142+
->method('isSuccessful')
143+
->willReturn(true);
144+
$process->expects($this->once())
145+
->method('getOutput')
146+
->willReturn("Version: 0.0.1\nRuntime: go1.22\n");
147+
148+
$handler = $this->createHandler($process, (string)$binary);
149+
$result = self::invokePrivate($handler, 'checkBinaries');
150+
151+
$this->assertSame('error', $result[0]->jsonSerialize()['status']);
152+
$this->assertStringContainsString('Invalid version. Expected:', $result[0]->jsonSerialize()['message']);
153+
154+
@unlink((string)$binary);
155+
}
156+
157+
private function createHandler(?Process $process = null, ?string $binary = null): CfsslHandler {
158+
$config = \OCP\Server::get(IConfig::class);
159+
$appConfig = $this->getMockAppConfigWithReset();
160+
if ($binary !== null) {
161+
$appConfigMock = $this->createMock(IAppConfig::class);
162+
$appConfigMock->method('getValueString')
163+
->willReturnCallback(function (string $appId, string $key, string $default = '') use ($binary): string {
164+
if ($appId === 'libresign' && $key === 'cfssl_bin') {
165+
return $binary;
166+
}
167+
return $default;
168+
});
169+
$appConfig = $appConfigMock;
170+
}
171+
$appDataFactory = \OCP\Server::get(IAppDataFactory::class);
172+
$dateTimeFormatter = \OCP\Server::get(IDateTimeFormatter::class);
173+
$tempManager = \OCP\Server::get(ITempManager::class);
174+
$certificatePolicyService = \OCP\Server::get(CertificatePolicyService::class);
175+
$urlGenerator = \OCP\Server::get(IURLGenerator::class);
176+
$caIdentifierService = \OCP\Server::get(CaIdentifierService::class);
177+
$crlMapper = \OCP\Server::get(CrlMapper::class);
178+
$logger = \OCP\Server::get(LoggerInterface::class);
179+
$crlRevocationChecker = $this->createMock(CrlRevocationChecker::class);
180+
$cfsslServerHandler = $this->createMock(CfsslServerHandler::class);
181+
$cfsslServerHandler->expects($this->once())
182+
->method('configCallback');
183+
$process ??= $this->createMock(Process::class);
184+
185+
$handler = $this->getMockBuilder(CfsslHandler::class)
186+
->setConstructorArgs([
187+
$config,
188+
$appConfig,
189+
$appDataFactory,
190+
$dateTimeFormatter,
191+
$tempManager,
192+
$cfsslServerHandler,
193+
$certificatePolicyService,
194+
$urlGenerator,
195+
$caIdentifierService,
196+
$crlMapper,
197+
$logger,
198+
$crlRevocationChecker,
199+
$this->processManager,
200+
])
201+
->onlyMethods(['createProcess'])
202+
->getMock();
203+
204+
$handler->method('createProcess')
205+
->willReturn($process);
206+
207+
return $handler;
208+
}
209+
}

0 commit comments

Comments
 (0)