226 lines
7.2 KiB
PHP
226 lines
7.2 KiB
PHP
<?php
|
|
|
|
use App\Http\Controllers\SystemStatusController;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Str;
|
|
|
|
function withFakeBinForStatus(array $scripts, callable $callback): void
|
|
{
|
|
$dir = storage_path('app/test-bin-' . Str::random(6));
|
|
if (!is_dir($dir)) {
|
|
mkdir($dir, 0777, true);
|
|
}
|
|
|
|
foreach ($scripts as $name => $body) {
|
|
$path = $dir . DIRECTORY_SEPARATOR . $name;
|
|
file_put_contents($path, $body);
|
|
chmod($path, 0755);
|
|
}
|
|
|
|
$originalPath = getenv('PATH') ?: '';
|
|
putenv("PATH={$dir}");
|
|
$_ENV['PATH'] = $dir;
|
|
$_SERVER['PATH'] = $dir;
|
|
|
|
try {
|
|
$callback($dir);
|
|
} finally {
|
|
putenv("PATH={$originalPath}");
|
|
$_ENV['PATH'] = $originalPath;
|
|
$_SERVER['PATH'] = $originalPath;
|
|
if (is_dir($dir)) {
|
|
$items = scandir($dir);
|
|
if (is_array($items)) {
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..') {
|
|
continue;
|
|
}
|
|
$path = $dir . DIRECTORY_SEPARATOR . $item;
|
|
if (is_file($path)) {
|
|
unlink($path);
|
|
}
|
|
}
|
|
}
|
|
rmdir($dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
it('returns system status for admins', function (): void {
|
|
withFakeBinForStatus([
|
|
'php' => "#!/bin/sh\nif [ \"$1\" = \"-r\" ]; then echo \"8.4.0\"; exit 0; fi\necho \"php\"\n",
|
|
'composer' => "#!/bin/sh\necho \"composer 2.0.0\"\n",
|
|
'node' => "#!/bin/sh\necho \"v20.0.0\"\n",
|
|
'npm' => "#!/bin/sh\necho \"9.0.0\"\n",
|
|
'tar' => "#!/bin/sh\necho \"tar 1.2.3\"\n",
|
|
'rsync' => "#!/bin/sh\necho \"rsync 3.2.0\"\n",
|
|
], function (string $dir): void {
|
|
$admin = User::factory()->create();
|
|
$role = Role::firstOrCreate(['name' => 'ROLE_ADMIN'], ['color' => '#111111']);
|
|
$admin->roles()->attach($role);
|
|
|
|
$request = Request::create('/api/system/status', 'GET');
|
|
$request->setUserResolver(fn () => $admin);
|
|
|
|
$controller = new SystemStatusController();
|
|
$response = $controller->__invoke($request);
|
|
|
|
expect($response->getStatusCode())->toBe(200);
|
|
$payload = $response->getData(true);
|
|
|
|
expect($payload)->toHaveKeys([
|
|
'php',
|
|
'php_default',
|
|
'composer',
|
|
'composer_version',
|
|
'node',
|
|
'node_version',
|
|
'npm',
|
|
'npm_version',
|
|
'tar',
|
|
'tar_version',
|
|
'rsync',
|
|
'rsync_version',
|
|
'proc_functions',
|
|
'storage_writable',
|
|
'updates_writable',
|
|
]);
|
|
});
|
|
});
|
|
|
|
it('covers binary resolution edge cases', function (): void {
|
|
withFakeBinForStatus([
|
|
'sh' => "#!/bin/sh\nexit 0\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refBinary = new ReflectionMethod($controller, 'resolveBinary');
|
|
$refBinary->setAccessible(true);
|
|
|
|
expect($refBinary->invoke($controller, 'php'))->toBeNull();
|
|
});
|
|
});
|
|
|
|
it('returns php version when available', function (): void {
|
|
withFakeBinForStatus([
|
|
'phpfake' => "#!/bin/sh\nif [ \"$1\" = \"-r\" ]; then echo \"8.4.1\"; exit 0; fi\nexit 0\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refPhp = new ReflectionMethod($controller, 'resolvePhpVersion');
|
|
$refPhp->setAccessible(true);
|
|
|
|
$path = $dir . '/phpfake';
|
|
expect($refPhp->invoke($controller, $path))->toBe('8.4.1');
|
|
});
|
|
});
|
|
|
|
it('returns null php version when command fails', function (): void {
|
|
withFakeBinForStatus([
|
|
'phpfail' => "#!/bin/sh\nexit 1\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refPhp = new ReflectionMethod($controller, 'resolvePhpVersion');
|
|
$refPhp->setAccessible(true);
|
|
|
|
$path = $dir . '/phpfail';
|
|
expect($refPhp->invoke($controller, $path))->toBeNull();
|
|
});
|
|
});
|
|
|
|
it('returns binary version when regex matches', function (): void {
|
|
withFakeBinForStatus([
|
|
'tool' => "#!/bin/sh\necho \"tool v1.2.3\"\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refVer = new ReflectionMethod($controller, 'resolveBinaryVersion');
|
|
$refVer->setAccessible(true);
|
|
|
|
$path = $dir . '/tool';
|
|
expect($refVer->invoke($controller, $path, ['--version']))->toBe('1.2.3');
|
|
});
|
|
});
|
|
|
|
it('returns null when binary version output is empty', function (): void {
|
|
withFakeBinForStatus([
|
|
'empty' => "#!/bin/sh\nexit 0\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refVer = new ReflectionMethod($controller, 'resolveBinaryVersion');
|
|
$refVer->setAccessible(true);
|
|
|
|
$path = $dir . '/empty';
|
|
expect($refVer->invoke($controller, $path, ['--version']))->toBeNull();
|
|
});
|
|
});
|
|
|
|
it('returns null when binary version output has no version', function (): void {
|
|
withFakeBinForStatus([
|
|
'noversion' => "#!/bin/sh\necho \"tool version unknown\"\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refVer = new ReflectionMethod($controller, 'resolveBinaryVersion');
|
|
$refVer->setAccessible(true);
|
|
|
|
$path = $dir . '/noversion';
|
|
expect($refVer->invoke($controller, $path, ['--version']))->toBeNull();
|
|
});
|
|
});
|
|
|
|
it('returns null when binary version command fails', function (): void {
|
|
withFakeBinForStatus([
|
|
'fail' => "#!/bin/sh\nexit 1\n",
|
|
], function (string $dir): void {
|
|
$controller = new SystemStatusController();
|
|
$refVer = new ReflectionMethod($controller, 'resolveBinaryVersion');
|
|
$refVer->setAccessible(true);
|
|
|
|
$path = $dir . '/fail';
|
|
expect($refVer->invoke($controller, $path, ['--version']))->toBeNull();
|
|
});
|
|
});
|
|
|
|
it('returns empty array when readJson cannot read file', function (): void {
|
|
$controller = new SystemStatusController();
|
|
$ref = new ReflectionMethod($controller, 'readJson');
|
|
$ref->setAccessible(true);
|
|
|
|
$path = sys_get_temp_dir() . '/missing.json';
|
|
$result = $ref->invoke($controller, $path);
|
|
|
|
expect($result)->toBe([]);
|
|
});
|
|
|
|
it('returns empty array when readJson invalid', function (): void {
|
|
$controller = new SystemStatusController();
|
|
$ref = new ReflectionMethod($controller, 'readJson');
|
|
$ref->setAccessible(true);
|
|
|
|
$path = sys_get_temp_dir() . '/invalid.json';
|
|
file_put_contents($path, 'not-json');
|
|
|
|
$result = $ref->invoke($controller, $path);
|
|
unlink($path);
|
|
|
|
expect($result)->toBe([]);
|
|
});
|
|
|
|
it('returns empty array when readJson cannot read contents', function (): void {
|
|
$controller = new SystemStatusController();
|
|
$ref = new ReflectionMethod($controller, 'readJson');
|
|
$ref->setAccessible(true);
|
|
|
|
$path = storage_path('app/unreadable.json');
|
|
file_put_contents($path, '{"a":1}');
|
|
chmod($path, 0000);
|
|
|
|
$prev = set_error_handler(static fn () => true);
|
|
$result = $ref->invoke($controller, $path);
|
|
restore_error_handler();
|
|
|
|
chmod($path, 0644);
|
|
unlink($path);
|
|
|
|
expect($result)->toBe([]);
|
|
});
|