65c1b5fb75
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
177 lines
5.0 KiB
PHP
177 lines
5.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KTXT\Unit\Runtime;
|
|
|
|
use KTXC\Application\Execution\ExecutionContext;
|
|
use KTXC\Application\Execution\ExecutionDescriptor;
|
|
use KTXC\Application\Execution\ExecutionOutcome;
|
|
use KTXC\Application\Execution\ExecutionScope;
|
|
use KTXC\Application\Execution\TerminationReport;
|
|
use KTXC\Context\IdentityContext;
|
|
use KTXC\Context\TenantContext;
|
|
use KTXC\KernelInterface;
|
|
use KTXC\Module\ModuleCollection;
|
|
use KTXC\Module\ModuleManager;
|
|
use KTXC\Runtime\Console\ConsoleRuntime;
|
|
use KTXC\Service\TenantService;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\Attributes\TestDox;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Container\ContainerInterface;
|
|
use Symfony\Component\Console\Input\ArrayInput;
|
|
use Symfony\Component\Console\Output\BufferedOutput;
|
|
|
|
final class ConsoleRuntimeTest extends TestCase
|
|
{
|
|
#[Test]
|
|
#[TestDox('Successful commands preserve their exit code and terminate')]
|
|
public function succeeds(): void
|
|
{
|
|
$modules = $this->createStub(ModuleManager::class);
|
|
$modules->method('list')->willReturn(new ModuleCollection());
|
|
$kernel = $this->kernel(new ConsoleContainer([ModuleManager::class => $modules]));
|
|
|
|
$exitCode = (new ConsoleRuntime($kernel))->run(
|
|
new ArrayInput(['command' => 'list', '--raw' => true]),
|
|
new BufferedOutput(),
|
|
);
|
|
|
|
self::assertSame(0, $exitCode);
|
|
self::assertSame(1, $kernel->terminations);
|
|
self::assertTrue($kernel->outcome?->successful);
|
|
}
|
|
|
|
#[Test]
|
|
#[TestDox('Console failures terminate before being rethrown')]
|
|
public function fails(): void
|
|
{
|
|
$kernel = $this->kernel(new ConsoleContainer([], new \RuntimeException('Discovery failed.')));
|
|
|
|
try {
|
|
(new ConsoleRuntime($kernel))->run(
|
|
new ArrayInput(['command' => 'list']),
|
|
new BufferedOutput(),
|
|
);
|
|
self::fail('Expected console discovery to fail.');
|
|
} catch (\RuntimeException $error) {
|
|
self::assertSame('Discovery failed.', $error->getMessage());
|
|
}
|
|
|
|
self::assertSame(1, $kernel->terminations);
|
|
self::assertFalse($kernel->outcome?->successful);
|
|
}
|
|
|
|
#[Test]
|
|
#[TestDox('Deferred termination failures do not replace command exit codes')]
|
|
public function preservesExitCodes(): void
|
|
{
|
|
$modules = $this->createStub(ModuleManager::class);
|
|
$modules->method('list')->willReturn(new ModuleCollection());
|
|
$kernel = $this->kernel(new ConsoleContainer([ModuleManager::class => $modules]));
|
|
$kernel->terminationReport = new TerminationReport(
|
|
failures: [new \RuntimeException('Deferred listener failed.')],
|
|
);
|
|
|
|
$exitCode = (new ConsoleRuntime($kernel))->run(
|
|
new ArrayInput(['command' => 'list', '--raw' => true]),
|
|
new BufferedOutput(),
|
|
);
|
|
|
|
self::assertSame(0, $exitCode);
|
|
}
|
|
|
|
private function kernel(ContainerInterface $container): ConsoleKernel
|
|
{
|
|
return new ConsoleKernel(
|
|
$container,
|
|
new TenantContext($this->createStub(TenantService::class)),
|
|
new IdentityContext(),
|
|
);
|
|
}
|
|
}
|
|
|
|
final class ConsoleKernel implements KernelInterface
|
|
{
|
|
public int $terminations = 0;
|
|
public ?ExecutionOutcome $outcome = null;
|
|
public TerminationReport $terminationReport;
|
|
|
|
public function __construct(
|
|
private readonly ContainerInterface $services,
|
|
private readonly TenantContext $tenantContext,
|
|
private readonly IdentityContext $identityContext,
|
|
) {
|
|
$this->terminationReport = new TerminationReport();
|
|
}
|
|
|
|
public function boot(): void
|
|
{
|
|
}
|
|
|
|
public function beginExecution(ExecutionDescriptor $descriptor): ExecutionScope
|
|
{
|
|
return new ExecutionScope(
|
|
$descriptor,
|
|
ExecutionContext::fromDescriptor($descriptor),
|
|
$this->tenantContext,
|
|
$this->identityContext,
|
|
);
|
|
}
|
|
|
|
public function terminateExecution(
|
|
ExecutionScope $scope,
|
|
ExecutionOutcome $outcome,
|
|
): TerminationReport {
|
|
$this->terminations++;
|
|
$this->outcome = $outcome;
|
|
$scope->dispose();
|
|
$scope->markTerminated();
|
|
|
|
return $this->terminationReport;
|
|
}
|
|
|
|
public function shutdown(): void
|
|
{
|
|
}
|
|
|
|
public function container(): ContainerInterface
|
|
{
|
|
return $this->services;
|
|
}
|
|
|
|
public function environment(): string
|
|
{
|
|
return 'test';
|
|
}
|
|
|
|
public function debug(): bool
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
final class ConsoleContainer implements ContainerInterface
|
|
{
|
|
public function __construct(
|
|
private readonly array $services,
|
|
private readonly ?\Throwable $failure = null,
|
|
) {
|
|
}
|
|
|
|
public function get(string $id): mixed
|
|
{
|
|
if ($this->failure !== null) {
|
|
throw $this->failure;
|
|
}
|
|
|
|
return $this->services[$id];
|
|
}
|
|
|
|
public function has(string $id): bool
|
|
{
|
|
return isset($this->services[$id]);
|
|
}
|
|
}
|