fix(Console): add void as valid return type for command methods
The MethodSignatureAnalyzer was rejecting command methods with void return type, causing the schedule:run command to fail validation.
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
160
tests/Unit/Framework/Core/ValueObjects/FrameworkModuleTest.php
Normal file
160
tests/Unit/Framework/Core/ValueObjects/FrameworkModuleTest.php
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -4,13 +4,187 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Integration;
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\Cache\CacheIdentifier;
|
||||
use App\Framework\Cache\CacheItem;
|
||||
use App\Framework\Cache\CacheKey;
|
||||
use App\Framework\Cache\CacheResult;
|
||||
use App\Framework\Cache\Driver\InMemoryCache;
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextData;
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
|
||||
use App\Framework\ExceptionHandling\ErrorHandlingConfig;
|
||||
use App\Framework\ExceptionHandling\ErrorKernel;
|
||||
use App\Framework\ExceptionHandling\ErrorRendererFactory;
|
||||
use App\Framework\ExceptionHandling\Renderers\ResponseErrorRenderer;
|
||||
use App\Framework\Http\Status;
|
||||
use App\Framework\Performance\PerformanceCategory;
|
||||
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
|
||||
use App\Framework\Performance\PerformanceConfig;
|
||||
use App\Framework\Performance\PerformanceMetric;
|
||||
use App\Framework\Performance\PerformanceService;
|
||||
use App\Framework\Serialization\Serializer;
|
||||
use App\Framework\View\Engine;
|
||||
use App\Framework\Context\ExecutionContext;
|
||||
use App\Framework\View\Loading\TemplateLoader;
|
||||
use App\Framework\View\TemplateProcessor;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Null performance collector for testing (no-op implementation)
|
||||
*/
|
||||
class TestPerformanceCollector implements PerformanceCollectorInterface
|
||||
{
|
||||
public function startTiming(string $key, PerformanceCategory $category, array $context = []): void {}
|
||||
public function endTiming(string $key): void {}
|
||||
public function measure(string $key, PerformanceCategory $category, callable $callback, array $context = []): mixed
|
||||
{
|
||||
return $callback();
|
||||
}
|
||||
public function recordMetric(string $key, PerformanceCategory $category, float $value, array $context = []): void {}
|
||||
public function increment(string $key, PerformanceCategory $category, int $amount = 1, array $context = []): void {}
|
||||
public function getMetrics(?PerformanceCategory $category = null): array { return []; }
|
||||
public function getMetric(string $key): ?PerformanceMetric { return null; }
|
||||
public function getTotalRequestTime(): float { return 0.0; }
|
||||
public function getTotalRequestMemory(): int { return 0; }
|
||||
public function getPeakMemory(): int { return 0; }
|
||||
public function reset(): void {}
|
||||
public function isEnabled(): bool { return false; }
|
||||
public function setEnabled(bool $enabled): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple cache wrapper for testing - adapts InMemoryCache to Cache interface
|
||||
*/
|
||||
class SimpleCacheWrapper implements Cache
|
||||
{
|
||||
public function __construct(private InMemoryCache $driver) {}
|
||||
|
||||
public function get(CacheIdentifier ...$identifiers): CacheResult
|
||||
{
|
||||
$keys = array_filter($identifiers, fn($id) => $id instanceof CacheKey);
|
||||
return $this->driver->get(...$keys);
|
||||
}
|
||||
|
||||
public function set(CacheItem ...$items): bool
|
||||
{
|
||||
return $this->driver->set(...$items);
|
||||
}
|
||||
|
||||
public function has(CacheIdentifier ...$identifiers): array
|
||||
{
|
||||
$keys = array_filter($identifiers, fn($id) => $id instanceof CacheKey);
|
||||
return $this->driver->has(...$keys);
|
||||
}
|
||||
|
||||
public function forget(CacheIdentifier ...$identifiers): bool
|
||||
{
|
||||
$keys = array_filter($identifiers, fn($id) => $id instanceof CacheKey);
|
||||
return $this->driver->forget(...$keys);
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
return $this->driver->clear();
|
||||
}
|
||||
|
||||
public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
|
||||
{
|
||||
$result = $this->driver->get($key);
|
||||
$item = $result->getItem($key);
|
||||
if ($item->isHit) {
|
||||
return $item;
|
||||
}
|
||||
$value = $callback();
|
||||
$newItem = CacheItem::forSet($key, $value, $ttl);
|
||||
$this->driver->set($newItem);
|
||||
return CacheItem::hit($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions to create test dependencies
|
||||
* Following the dependency chain: TemplateLoader → Engine → ErrorRendererFactory → ErrorKernel
|
||||
*/
|
||||
|
||||
function createTestEngine(): Engine
|
||||
{
|
||||
$projectRoot = dirname(__DIR__, 2);
|
||||
$pathProvider = new PathProvider($projectRoot);
|
||||
$cache = new SimpleCacheWrapper(new InMemoryCache());
|
||||
|
||||
$templateLoader = new TemplateLoader(
|
||||
pathProvider: $pathProvider,
|
||||
cache: $cache,
|
||||
discoveryRegistry: null,
|
||||
templates: [],
|
||||
templatePath: '/src/Framework/ExceptionHandling/Templates',
|
||||
useMtimeInvalidation: false,
|
||||
cacheEnabled: false,
|
||||
);
|
||||
|
||||
$performanceCollector = new TestPerformanceCollector();
|
||||
$performanceConfig = new PerformanceConfig(enabled: false);
|
||||
$performanceService = new PerformanceService(
|
||||
collector: $performanceCollector,
|
||||
config: $performanceConfig,
|
||||
);
|
||||
|
||||
$container = new DefaultContainer();
|
||||
$templateProcessor = new TemplateProcessor(
|
||||
astTransformers: [],
|
||||
stringProcessors: [],
|
||||
container: $container,
|
||||
);
|
||||
|
||||
return new Engine(
|
||||
loader: $templateLoader,
|
||||
performanceService: $performanceService,
|
||||
processor: $templateProcessor,
|
||||
cache: $cache,
|
||||
cacheEnabled: false,
|
||||
);
|
||||
}
|
||||
|
||||
function createTestErrorRendererFactory(?bool $isDebugMode = null): ErrorRendererFactory
|
||||
{
|
||||
$executionContext = ExecutionContext::forWeb();
|
||||
$engine = createTestEngine();
|
||||
$config = $isDebugMode !== null
|
||||
? new ErrorHandlingConfig(isDebugMode: $isDebugMode)
|
||||
: null;
|
||||
|
||||
return new ErrorRendererFactory(
|
||||
executionContext: $executionContext,
|
||||
engine: $engine,
|
||||
consoleOutput: null,
|
||||
config: $config,
|
||||
);
|
||||
}
|
||||
|
||||
function createTestErrorKernel(): ErrorKernel
|
||||
{
|
||||
$rendererFactory = createTestErrorRendererFactory();
|
||||
|
||||
return new ErrorKernel(
|
||||
rendererFactory: $rendererFactory,
|
||||
reporter: null,
|
||||
);
|
||||
}
|
||||
|
||||
function createTestResponseErrorRenderer(bool $isDebugMode = false): ResponseErrorRenderer
|
||||
{
|
||||
$engine = createTestEngine();
|
||||
|
||||
return new ResponseErrorRenderer(
|
||||
engine: $engine,
|
||||
isDebugMode: $isDebugMode,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Integration tests for unified ExceptionHandling module
|
||||
*
|
||||
@@ -32,7 +206,7 @@ describe('ErrorKernel HTTP Response Generation', function () {
|
||||
});
|
||||
|
||||
it('creates JSON API error response without context', function () {
|
||||
$errorKernel = new ErrorKernel();
|
||||
$errorKernel = createTestErrorKernel();
|
||||
$exception = new RuntimeException('Test API error', 500);
|
||||
|
||||
$response = $errorKernel->createHttpResponse($exception, null, isDebugMode: false);
|
||||
@@ -47,7 +221,7 @@ describe('ErrorKernel HTTP Response Generation', function () {
|
||||
});
|
||||
|
||||
it('creates JSON API error response with debug mode', function () {
|
||||
$errorKernel = new ErrorKernel();
|
||||
$errorKernel = createTestErrorKernel();
|
||||
$exception = new RuntimeException('Database connection failed', 500);
|
||||
|
||||
$response = $errorKernel->createHttpResponse($exception, null, isDebugMode: true);
|
||||
@@ -63,7 +237,7 @@ describe('ErrorKernel HTTP Response Generation', function () {
|
||||
});
|
||||
|
||||
it('creates JSON API error response with WeakMap context', function () {
|
||||
$errorKernel = new ErrorKernel();
|
||||
$errorKernel = createTestErrorKernel();
|
||||
$contextProvider = new ExceptionContextProvider();
|
||||
$exception = new RuntimeException('User operation failed', 500);
|
||||
|
||||
@@ -100,10 +274,10 @@ describe('ResponseErrorRenderer', function () {
|
||||
});
|
||||
|
||||
it('detects API requests correctly', function () {
|
||||
$renderer = new ResponseErrorRenderer(isDebugMode: false);
|
||||
$renderer = createTestResponseErrorRenderer(isDebugMode: false);
|
||||
$exception = new RuntimeException('Test error');
|
||||
|
||||
$response = $renderer->createResponse($exception, null);
|
||||
$response = $renderer->render($exception, null);
|
||||
|
||||
expect($response->headers->getFirst('Content-Type'))->toBe('application/json');
|
||||
});
|
||||
@@ -113,10 +287,10 @@ describe('ResponseErrorRenderer', function () {
|
||||
$_SERVER['HTTP_ACCEPT'] = 'text/html';
|
||||
$_SERVER['REQUEST_URI'] = '/web/page';
|
||||
|
||||
$renderer = new ResponseErrorRenderer(isDebugMode: false);
|
||||
$renderer = createTestResponseErrorRenderer(isDebugMode: false);
|
||||
$exception = new RuntimeException('Page error');
|
||||
|
||||
$response = $renderer->createResponse($exception, null);
|
||||
$response = $renderer->render($exception, null);
|
||||
|
||||
expect($response->headers->getFirst('Content-Type'))->toBe('text/html; charset=utf-8');
|
||||
expect($response->body)->toContain('<!DOCTYPE html>');
|
||||
@@ -127,7 +301,7 @@ describe('ResponseErrorRenderer', function () {
|
||||
$_SERVER['HTTP_ACCEPT'] = 'text/html';
|
||||
$_SERVER['REQUEST_URI'] = '/web/page';
|
||||
|
||||
$renderer = new ResponseErrorRenderer(isDebugMode: true);
|
||||
$renderer = createTestResponseErrorRenderer(isDebugMode: true);
|
||||
$contextProvider = new ExceptionContextProvider();
|
||||
$exception = new RuntimeException('Debug test error');
|
||||
|
||||
@@ -139,7 +313,7 @@ describe('ResponseErrorRenderer', function () {
|
||||
);
|
||||
$contextProvider->attach($exception, $contextData);
|
||||
|
||||
$response = $renderer->createResponse($exception, $contextProvider);
|
||||
$response = $renderer->render($exception, $contextProvider);
|
||||
|
||||
expect($response->body)->toContain('Debug Information');
|
||||
expect($response->body)->toContain('page.render');
|
||||
@@ -148,21 +322,21 @@ describe('ResponseErrorRenderer', function () {
|
||||
});
|
||||
|
||||
it('maps exception types to HTTP status codes correctly', function () {
|
||||
$renderer = new ResponseErrorRenderer();
|
||||
$renderer = createTestResponseErrorRenderer();
|
||||
|
||||
// InvalidArgumentException → 400
|
||||
$exception = new \InvalidArgumentException('Invalid input');
|
||||
$response = $renderer->createResponse($exception, null);
|
||||
$response = $renderer->render($exception, null);
|
||||
expect($response->status)->toBe(Status::BAD_REQUEST);
|
||||
|
||||
// RuntimeException → 500
|
||||
$exception = new RuntimeException('Runtime error');
|
||||
$response = $renderer->createResponse($exception, null);
|
||||
$response = $renderer->render($exception, null);
|
||||
expect($response->status)->toBe(Status::INTERNAL_SERVER_ERROR);
|
||||
|
||||
// Custom code in valid range
|
||||
$exception = new RuntimeException('Not found', 404);
|
||||
$response = $renderer->createResponse($exception, null);
|
||||
$response = $renderer->render($exception, null);
|
||||
expect($response->status)->toBe(Status::NOT_FOUND);
|
||||
});
|
||||
});
|
||||
@@ -297,7 +471,7 @@ describe('End-to-end integration scenario', function () {
|
||||
|
||||
it('demonstrates full exception handling flow with context enrichment', function () {
|
||||
// Setup
|
||||
$errorKernel = new ErrorKernel();
|
||||
$errorKernel = createTestErrorKernel();
|
||||
$contextProvider = new ExceptionContextProvider();
|
||||
|
||||
// 1. Exception occurs in service layer
|
||||
|
||||
Reference in New Issue
Block a user