2026-09-25 00:32:16 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
|
use XcVm\Cli\Commands\ServerDiagnoseCommand;
|
|
|
|
|
use XcVm\Core\Config\OpensslExtra;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* server:diagnose — compares the OPENSSL_EXTRA fingerprint a node publishes in
|
|
|
|
|
* server_hardware with the MAIN's. A mismatch means every token MAIN mints for
|
|
|
|
|
* a redirect is rejected by that node. A node that publishes none runs an
|
|
|
|
|
* older build; proxies publish none and are not checked.
|
2026-09-25 00:56:05 +00:00
|
|
|
*
|
|
|
|
|
* The section is called through reflection. That Mode A (diagnoseFromMain) and
|
|
|
|
|
* Mode B (diagnoseLocal) still call it is pinned from their source only: running
|
|
|
|
|
* either mode probes the network and the host.
|
2026-09-25 00:32:16 +00:00
|
|
|
*/
|
|
|
|
|
final class ServerDiagnoseOpensslExtraTest extends TestCase {
|
|
|
|
|
|
|
|
|
|
private function row(int $rID, ?string $rValue, array $rOverride = []): array {
|
|
|
|
|
$rHardware = ['cores' => 4];
|
|
|
|
|
if ($rValue !== null) {
|
|
|
|
|
$rHardware[OpensslExtra::HARDWARE_KEY] = OpensslExtra::fingerprint($rValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array_merge(['id' => $rID, 'is_main' => (int) ($rID === 1), 'server_type' => 0, 'server_hardware' => json_encode($rHardware)], $rOverride);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @return array{0:string,1:string[]} [output, problems] */
|
|
|
|
|
private function check(array $rServer, ?array $rMain): array {
|
|
|
|
|
$rMethod = new ReflectionMethod(ServerDiagnoseCommand::class, 'opensslExtraSection');
|
|
|
|
|
$rMethod->setAccessible(true);
|
|
|
|
|
$rProblems = [];
|
|
|
|
|
ob_start();
|
2026-09-25 00:56:05 +00:00
|
|
|
try {
|
|
|
|
|
$rMethod->invokeArgs(new ServerDiagnoseCommand(), [$rServer, $rMain, &$rProblems]);
|
|
|
|
|
} finally {
|
|
|
|
|
$rOutput = (string) ob_get_clean();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [$rOutput, $rProblems];
|
|
|
|
|
}
|
2026-09-25 00:32:16 +00:00
|
|
|
|
2026-09-25 00:56:05 +00:00
|
|
|
/** The source of one of the command's methods, as reflection locates it. */
|
|
|
|
|
private function body(string $rName): string {
|
|
|
|
|
$rMethod = new ReflectionMethod(ServerDiagnoseCommand::class, $rName);
|
|
|
|
|
$rLines = file((string) $rMethod->getFileName());
|
|
|
|
|
|
|
|
|
|
return implode('', array_slice($rLines, $rMethod->getStartLine() - 1, $rMethod->getEndLine() - $rMethod->getStartLine() + 1));
|
2026-09-25 00:32:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testAMismatchIsReportedAsAProblem(): void {
|
|
|
|
|
[$rOutput, $rProblems] = $this->check($this->row(2, 'fNiu3XD448xTDa27xoY4'), $this->row(1, 'random-main-extra'));
|
|
|
|
|
|
|
|
|
|
$this->assertStringContainsString('[WARN]', $rOutput);
|
|
|
|
|
$this->assertCount(1, $rProblems);
|
|
|
|
|
$this->assertStringStartsWith('OPENSSL_EXTRA mismatch: tokens minted on MAIN are rejected by this node', $rProblems[0]);
|
2026-09-25 00:34:12 +00:00
|
|
|
$this->assertStringContainsString('server:sync-openssl-extra 2', $rProblems[0]);
|
2026-09-25 00:32:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testMatchingValuesPass(): void {
|
|
|
|
|
[$rOutput, $rProblems] = $this->check($this->row(2, 'same-extra'), $this->row(1, 'same-extra'));
|
|
|
|
|
|
|
|
|
|
$this->assertStringContainsString('[OK]', $rOutput);
|
|
|
|
|
$this->assertSame([], $rProblems);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testAMissingFingerprintReadsAsUnknown(): void {
|
|
|
|
|
[$rOutput, $rProblems] = $this->check($this->row(2, null), $this->row(1, 'main-extra'));
|
|
|
|
|
$this->assertStringContainsString('unknown (node not updated)', $rOutput);
|
|
|
|
|
$this->assertSame([], $rProblems);
|
|
|
|
|
|
|
|
|
|
[$rOutput, $rProblems] = $this->check($this->row(2, 'lb-extra'), $this->row(1, null));
|
|
|
|
|
$this->assertStringContainsString('unknown (main not updated)', $rOutput);
|
|
|
|
|
$this->assertSame([], $rProblems);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 00:56:05 +00:00
|
|
|
/** Both modes still report the check (see the class comment). */
|
|
|
|
|
public function testBothModesRunTheCheck(): void {
|
|
|
|
|
$this->assertStringContainsString('$this->opensslExtraSection($rServer, $rMain, $rProblems);', $this->body('diagnoseFromMain'));
|
|
|
|
|
$this->assertStringContainsString('$this->opensslExtraSection($rMe, $rMain, $rProblems);', $this->body('diagnoseLocal'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 00:32:16 +00:00
|
|
|
public function testProxiesAndAMissingMainAreSkipped(): void {
|
|
|
|
|
$this->assertSame(['', []], $this->check($this->row(3, null, ['server_type' => 1]), $this->row(1, 'main-extra')));
|
|
|
|
|
$this->assertSame(['', []], $this->check($this->row(2, 'lb-extra'), null));
|
|
|
|
|
}
|
|
|
|
|
}
|