Files
XC_VM/tests/Unit/ModuleCoreRequirementTest.php
T
Divarion_D be115bd053 feat(modules): enforce requires_core when loading, installing and updating
`requires_core` was only read and shown in the Modules table. A module
release that needs a newer core could be installed or updated anyway and
take the panel down, the update check offered such releases, and modules
left behind by a core rollback kept loading.

ModuleLoader::coreRequirementError() checks a manifest's constraint
(one or more `>= > <= < = == !=` conditions, a bare version meaning >=;
empty = any core; anything else unreadable and refused) against
XC_VM_VERSION, a nightly `X.Y.Z-dev.N` counting as X.Y.Z. It is applied:

- on load: the module is skipped with a log line, its dependents with it;
- on install (placeModuleFiles: zip, LB, standard set, platform) and on a
  git/url update: refused before the installed copy is touched; a refused
  copy the platform extracted into the modules directory is removed;
- in the update check: git offers the newest release whose module.json
  (repository root, read from raw.githubusercontent.com, up to 10 tags)
  this core can run, url honours requires_core in version.json;
- in the Modules table: the reason on the existing warning badge, now
  labelled "Issue(s)".

The checks sit in small helpers, so discoverModules() and
updateModuleFromSource() come out simpler than before.
2026-10-04 11:59:43 +03:00

47 lines
2.0 KiB
PHP

<?php
use XcVm\Core\Module\ModuleLoader;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
/**
* ModuleLoader::coreRequirementError(): which `requires_core` values let a
* given core run a module, and that an unreadable one rules it out.
*/
final class ModuleCoreRequirementTest extends TestCase {
/** @return array<string, array{string, string, bool}> requires_core, core version, compatible */
public static function constraints(): array {
return [
'minimum met' => ['>=2.0', '2.6.0', true],
'minimum not met' => ['>=2.7.0', '2.6.0', false],
'range, inside' => ['>=2.6 <3.0', '2.6.0', true],
'range, comma separated' => ['>=2.6,<3.0', '3.0.0', false],
'exact, single equals' => ['=2.6.0', '2.6.0', true],
'exact, double equals' => ['==2.6.0', '2.6.1', false],
'not equal' => ['!=2.6.0', '2.6.0', false],
'bare version means >=' => ['2.6.1', '2.6.0', false],
'empty means any core' => ['', '1.0.0', true],
'caret is unreadable' => ['^2.6', '2.6.0', false],
'nightly counts as release' => ['>=2.6.1', '2.6.1-dev.3', true],
'nightly is not the next' => ['>=2.6.2', '2.6.1-dev.3', false],
];
}
#[DataProvider('constraints')]
public function testConstraint(string $rRequires, string $rCore, bool $rCompatible): void {
$this->assertSame($rCompatible, ModuleLoader::coreRequirementError($rRequires, $rCore) === null);
}
public function testTheReasonNamesTheConstraintAndTheCore(): void {
$this->assertSame('needs core >=2.7.0; this panel runs 2.6.0', ModuleLoader::coreRequirementError('>=2.7.0', '2.6.0'));
$this->assertSame("has an unreadable requires_core '~2.6'", ModuleLoader::coreRequirementError('~2.6', '2.6.0'));
}
public function testDefaultsToTheRunningCore(): void {
class_exists(\XcVm\Core\Config\ConstantsInitializer::class);
$this->assertNull(ModuleLoader::coreRequirementError('>=' . preg_replace('/-dev\.\d+$/', '', XC_VM_VERSION)));
$this->assertNotNull(ModuleLoader::coreRequirementError('>=99.0'));
}
}