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

@@ -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