fix(Console): add void as valid return type for command methods
All checks were successful
Test Runner / test-php (push) Successful in 31s
Deploy Application / deploy (push) Successful in 1m42s
Test Runner / test-basic (push) Successful in 7s

The MethodSignatureAnalyzer was rejecting command methods with void return
type, causing the schedule:run command to fail validation.
This commit is contained in:
2025-11-26 06:16:09 +01:00
parent 386baff65f
commit c93d3f07a2
73 changed files with 1674 additions and 163 deletions

View File

@@ -0,0 +1,237 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\FrameworkModule;
use App\Framework\Core\ValueObjects\FrameworkModuleRegistry;
use App\Framework\Core\ValueObjects\PhpNamespace;
use App\Framework\Filesystem\ValueObjects\FilePath;
describe('FrameworkModuleRegistry', function () {
it('creates registry with variadic modules', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath),
FrameworkModule::create('Database', $basePath),
FrameworkModule::create('Cache', $basePath)
);
expect($registry->count())->toBe(3);
expect($registry->hasModule('Http'))->toBeTrue();
expect($registry->hasModule('Database'))->toBeTrue();
expect($registry->hasModule('Cache'))->toBeTrue();
});
it('creates empty registry', function () {
$registry = new FrameworkModuleRegistry();
expect($registry->count())->toBe(0);
expect($registry->getAllModules())->toBe([]);
});
describe('discover', function () {
it('discovers modules from Framework directory', function () {
// Use actual project path (tests run outside Docker)
$projectRoot = dirname(__DIR__, 5);
$frameworkPath = FilePath::create($projectRoot . '/src/Framework');
$registry = FrameworkModuleRegistry::discover($frameworkPath);
expect($registry->count())->toBeGreaterThan(50);
expect($registry->hasModule('Http'))->toBeTrue();
expect($registry->hasModule('Database'))->toBeTrue();
expect($registry->hasModule('Core'))->toBeTrue();
expect($registry->hasModule('Cache'))->toBeTrue();
});
});
describe('getModuleForNamespace', function () {
beforeEach(function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$this->registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath),
FrameworkModule::create('Database', $basePath),
FrameworkModule::create('Cache', $basePath)
);
});
it('finds module for namespace', function () {
$namespace = PhpNamespace::fromString('App\\Framework\\Http\\Middlewares\\Auth');
$module = $this->registry->getModuleForNamespace($namespace);
expect($module)->not->toBeNull();
expect($module->name)->toBe('Http');
});
it('finds module for root module namespace', function () {
$namespace = PhpNamespace::fromString('App\\Framework\\Database');
$module = $this->registry->getModuleForNamespace($namespace);
expect($module)->not->toBeNull();
expect($module->name)->toBe('Database');
});
it('returns null for non-framework namespace', function () {
$namespace = PhpNamespace::fromString('App\\Domain\\User\\Services');
$module = $this->registry->getModuleForNamespace($namespace);
expect($module)->toBeNull();
});
it('returns null for unknown module', function () {
$namespace = PhpNamespace::fromString('App\\Framework\\UnknownModule\\Something');
$module = $this->registry->getModuleForNamespace($namespace);
expect($module)->toBeNull();
});
it('returns null for bare Framework namespace', function () {
$namespace = PhpNamespace::fromString('App\\Framework');
$module = $this->registry->getModuleForNamespace($namespace);
expect($module)->toBeNull();
});
});
describe('getModuleForClass', function () {
beforeEach(function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$this->registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath),
FrameworkModule::create('Core', $basePath)
);
});
it('finds module for class', function () {
$className = ClassName::create('App\\Framework\\Http\\Request');
$module = $this->registry->getModuleForClass($className);
expect($module)->not->toBeNull();
expect($module->name)->toBe('Http');
});
it('finds module for deeply nested class', function () {
$className = ClassName::create('App\\Framework\\Core\\ValueObjects\\PhpNamespace');
$module = $this->registry->getModuleForClass($className);
expect($module)->not->toBeNull();
expect($module->name)->toBe('Core');
});
});
describe('inSameModule', function () {
beforeEach(function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$this->registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath),
FrameworkModule::create('Database', $basePath)
);
});
it('returns true for namespaces in same module', function () {
$a = PhpNamespace::fromString('App\\Framework\\Http\\Request');
$b = PhpNamespace::fromString('App\\Framework\\Http\\Response');
expect($this->registry->inSameModule($a, $b))->toBeTrue();
});
it('returns true for deeply nested namespaces in same module', function () {
$a = PhpNamespace::fromString('App\\Framework\\Http\\Middlewares\\Auth');
$b = PhpNamespace::fromString('App\\Framework\\Http\\ValueObjects\\StatusCode');
expect($this->registry->inSameModule($a, $b))->toBeTrue();
});
it('returns false for namespaces in different modules', function () {
$a = PhpNamespace::fromString('App\\Framework\\Http\\Request');
$b = PhpNamespace::fromString('App\\Framework\\Database\\Connection');
expect($this->registry->inSameModule($a, $b))->toBeFalse();
});
it('returns false when one namespace is not in framework', function () {
$a = PhpNamespace::fromString('App\\Framework\\Http\\Request');
$b = PhpNamespace::fromString('App\\Domain\\User\\UserService');
expect($this->registry->inSameModule($a, $b))->toBeFalse();
});
it('returns false when both namespaces are not in framework', function () {
$a = PhpNamespace::fromString('App\\Domain\\User');
$b = PhpNamespace::fromString('App\\Domain\\Order');
expect($this->registry->inSameModule($a, $b))->toBeFalse();
});
});
describe('classesInSameModule', function () {
beforeEach(function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$this->registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath),
FrameworkModule::create('Cache', $basePath)
);
});
it('returns true for classes in same module', function () {
$a = ClassName::create('App\\Framework\\Http\\Request');
$b = ClassName::create('App\\Framework\\Http\\Response');
expect($this->registry->classesInSameModule($a, $b))->toBeTrue();
});
it('returns false for classes in different modules', function () {
$a = ClassName::create('App\\Framework\\Http\\Request');
$b = ClassName::create('App\\Framework\\Cache\\CacheItem');
expect($this->registry->classesInSameModule($a, $b))->toBeFalse();
});
});
describe('getModule', function () {
it('returns module by name', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath)
);
$module = $registry->getModule('Http');
expect($module)->not->toBeNull();
expect($module->name)->toBe('Http');
});
it('returns null for unknown module', function () {
$registry = new FrameworkModuleRegistry();
expect($registry->getModule('Unknown'))->toBeNull();
});
});
describe('getModuleNames', function () {
it('returns all module names', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$registry = new FrameworkModuleRegistry(
FrameworkModule::create('Http', $basePath),
FrameworkModule::create('Database', $basePath),
FrameworkModule::create('Cache', $basePath)
);
$names = $registry->getModuleNames();
expect($names)->toContain('Http');
expect($names)->toContain('Database');
expect($names)->toContain('Cache');
expect($names)->toHaveCount(3);
});
});
});

View File

@@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\FrameworkModule;
use App\Framework\Core\ValueObjects\PhpNamespace;
use App\Framework\Filesystem\ValueObjects\FilePath;
describe('FrameworkModule', function () {
it('creates module with valid name', function () {
$path = FilePath::create('/var/www/html/src/Framework/Http');
$module = new FrameworkModule('Http', $path);
expect($module->name)->toBe('Http');
expect($module->path->toString())->toBe('/var/www/html/src/Framework/Http');
expect($module->namespace->toString())->toBe('App\\Framework\\Http');
});
it('creates module via factory method', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Database', $basePath);
expect($module->name)->toBe('Database');
expect($module->path->toString())->toBe('/var/www/html/src/Framework/Database');
});
it('rejects empty module name', function () {
$path = FilePath::create('/var/www/html/src/Framework/Empty');
new FrameworkModule('', $path);
})->throws(InvalidArgumentException::class, 'Module name cannot be empty');
it('rejects lowercase module name', function () {
$path = FilePath::create('/var/www/html/src/Framework/http');
new FrameworkModule('http', $path);
})->throws(InvalidArgumentException::class, 'Must be PascalCase');
it('rejects module name with invalid characters', function () {
$path = FilePath::create('/var/www/html/src/Framework/Http-Client');
new FrameworkModule('Http-Client', $path);
})->throws(InvalidArgumentException::class, 'Must be PascalCase');
describe('containsNamespace', function () {
it('returns true for namespace within module', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Framework\\Http\\Middlewares');
expect($module->containsNamespace($namespace))->toBeTrue();
});
it('returns true for module root namespace', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Framework\\Http');
expect($module->containsNamespace($namespace))->toBeTrue();
});
it('returns false for namespace in different module', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Framework\\Database\\Query');
expect($module->containsNamespace($namespace))->toBeFalse();
});
it('returns false for non-framework namespace', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Domain\\User');
expect($module->containsNamespace($namespace))->toBeFalse();
});
});
describe('containsClass', function () {
it('returns true for class within module', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$className = ClassName::create('App\\Framework\\Http\\Request');
expect($module->containsClass($className))->toBeTrue();
});
it('returns false for class in different module', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$className = ClassName::create('App\\Framework\\Cache\\CacheItem');
expect($module->containsClass($className))->toBeFalse();
});
});
describe('getRelativeNamespace', function () {
it('returns relative namespace within module', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Framework\\Http\\Middlewares\\Auth');
$relative = $module->getRelativeNamespace($namespace);
expect($relative)->not->toBeNull();
expect($relative->toString())->toBe('Middlewares\\Auth');
});
it('returns global namespace for module root', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Framework\\Http');
$relative = $module->getRelativeNamespace($namespace);
expect($relative)->not->toBeNull();
expect($relative->isGlobal())->toBeTrue();
});
it('returns null for namespace not in module', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
$namespace = PhpNamespace::fromString('App\\Framework\\Database');
$relative = $module->getRelativeNamespace($namespace);
expect($relative)->toBeNull();
});
});
describe('equals', function () {
it('returns true for modules with same name', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module1 = FrameworkModule::create('Http', $basePath);
$module2 = FrameworkModule::create('Http', $basePath);
expect($module1->equals($module2))->toBeTrue();
});
it('returns false for modules with different names', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module1 = FrameworkModule::create('Http', $basePath);
$module2 = FrameworkModule::create('Cache', $basePath);
expect($module1->equals($module2))->toBeFalse();
});
});
it('converts to string', function () {
$basePath = FilePath::create('/var/www/html/src/Framework');
$module = FrameworkModule::create('Http', $basePath);
expect((string) $module)->toBe('Http');
expect($module->toString())->toBe('Http');
});
});