Add comprehensive test coverage and update notes
This commit is contained in:
225
tests/Unit/SystemStatusControllerUnitTest.php
Normal file
225
tests/Unit/SystemStatusControllerUnitTest.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?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([]);
|
||||
});
|
||||
Reference in New Issue
Block a user