Files
michaelschiemer/tests/Framework/CommandBus/DefaultCommandBusTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

312 lines
8.2 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\CommandBus\CommandHandlerDescriptor;
use App\Framework\CommandBus\CommandHandlersCollection;
use App\Framework\CommandBus\DefaultCommandBus;
use App\Framework\CommandBus\Exceptions\NoHandlerFound;
use App\Framework\CommandBus\ShouldQueue;
use App\Framework\Context\ContextType;
use App\Framework\Context\ExecutionContext;
use App\Framework\DI\DefaultContainer;
use App\Framework\Logging\ChannelLogger;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\Logger;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;
beforeEach(function () {
$this->container = new DefaultContainer();
$this->executionContext = createTestExecutionContext();
$this->queue = new TestQueue();
$this->logger = new TestLogger();
});
test('command handlers collection returns correct handler', function () {
$descriptor = new CommandHandlerDescriptor(TestCommandHandler::class, 'handle', TestCommand::class);
$collection = new CommandHandlersCollection($descriptor);
$retrieved = $collection->get(TestCommand::class);
expect($retrieved)->toBe($descriptor)
->and($retrieved->class)->toBe(TestCommandHandler::class)
->and($retrieved->method)->toBe('handle')
->and($retrieved->command)->toBe(TestCommand::class);
});
test('command handlers collection returns null for non-existent command', function () {
$collection = new CommandHandlersCollection();
$result = $collection->get('NonExistentCommand');
expect($result)->toBeNull();
});
test('command handler descriptor stores class and method', function () {
$descriptor = new CommandHandlerDescriptor('TestClass', 'testMethod', 'TestCommand');
expect($descriptor->class)->toBe('TestClass')
->and($descriptor->method)->toBe('testMethod')
->and($descriptor->command)->toBe('TestCommand');
});
test('no handler found exception contains command class', function () {
$commandClass = 'TestCommand';
$exception = NoHandlerFound::forCommand($commandClass);
expect($exception->getMessage())
->toContain($commandClass);
});
test('dispatch executes command handler directly', function () {
$command = new TestCommand('test-data');
$handler = new TestCommandHandler();
$handlerDescriptor = new CommandHandlerDescriptor(TestCommandHandler::class, 'handle', TestCommand::class);
$commandHandlers = new CommandHandlersCollection($handlerDescriptor);
$commandBus = new DefaultCommandBus(
$commandHandlers,
$this->container,
$this->executionContext,
$this->queue,
$this->logger,
[] // No middlewares for basic tests
);
$this->container->instance(TestCommandHandler::class, $handler);
$result = $commandBus->dispatch($command);
expect($result)->toBe('Handled: test-data');
});
test('dispatch throws exception when no handler found', function () {
$command = new TestCommand('test-data');
$commandHandlers = new CommandHandlersCollection(); // Empty collection
$commandBus = new DefaultCommandBus(
$commandHandlers,
$this->container,
$this->executionContext,
$this->queue,
$this->logger,
[] // No middlewares for basic tests
);
expect(fn () => $commandBus->dispatch($command))
->toThrow(NoHandlerFound::class);
});
test('should queue attribute is recognized', function () {
$command = new QueueableTestCommand('queue-data');
$commandHandlers = new CommandHandlersCollection(); // Empty collection to test queueing
$commandBus = new DefaultCommandBus(
$commandHandlers,
$this->container,
$this->executionContext,
$this->queue,
$this->logger,
[] // No middlewares for basic tests
);
// For queued commands, the result should be null
$result = $commandBus->dispatch($command);
expect($result)->toBeNull()
->and($this->queue->wasUsed())->toBeTrue();
});
// Test fixtures
class TestCommand
{
public function __construct(
public readonly string $data
) {
}
}
#[ShouldQueue]
class QueueableTestCommand
{
public function __construct(
public readonly string $data
) {
}
}
class TestCommandHandler
{
public function handle(TestCommand $command): string
{
return 'Handled: ' . $command->data;
}
}
// ExecutionContext is final, so we create a simple instance
function createTestExecutionContext(): ExecutionContext
{
return new ExecutionContext(ContextType::WEB);
}
class TestQueue implements Queue
{
private bool $used = false;
private array $jobs = [];
public function push(JobPayload $payload): void
{
$this->used = true;
$this->jobs[] = $payload;
}
public function pop(): ?JobPayload
{
return array_shift($this->jobs);
}
public function peek(): ?JobPayload
{
return $this->jobs[0] ?? null;
}
public function size(): int
{
return count($this->jobs);
}
public function clear(): int
{
$count = count($this->jobs);
$this->jobs = [];
return $count;
}
public function getStats(): array
{
return ['size' => count($this->jobs)];
}
public function wasUsed(): bool
{
return $this->used;
}
}
class TestLogger implements Logger
{
private ?ChannelLogger $mockChannelLogger = null;
public function emergency(string $message, ?LogContext $context = null): void
{
}
public function alert(string $message, ?LogContext $context = null): void
{
}
public function critical(string $message, ?LogContext $context = null): void
{
}
public function error(string $message, ?LogContext $context = null): void
{
}
public function warning(string $message, ?LogContext $context = null): void
{
}
public function notice(string $message, ?LogContext $context = null): void
{
}
public function info(string $message, ?LogContext $context = null): void
{
}
public function debug(string $message, ?LogContext $context = null): void
{
}
public function log(LogLevel $level, string $message, ?LogContext $context = null): void
{
}
public function logToChannel(LogChannel $channel, LogLevel $level, string $message, ?LogContext $context = null): void
{
}
public ChannelLogger $security {
get {
return $this->mockChannelLogger ??= $this->createMockChannelLogger();
}
}
public ChannelLogger $cache {
get {
return $this->mockChannelLogger ??= $this->createMockChannelLogger();
}
}
public ChannelLogger $database {
get {
return $this->mockChannelLogger ??= $this->createMockChannelLogger();
}
}
public ChannelLogger $framework {
get {
return $this->mockChannelLogger ??= $this->createMockChannelLogger();
}
}
public ChannelLogger $error {
get {
return $this->mockChannelLogger ??= $this->createMockChannelLogger();
}
}
private function createMockChannelLogger(): ChannelLogger
{
return new class () implements ChannelLogger {
public function emergency(string $message, ?LogContext $context = null): void
{
}
public function alert(string $message, ?LogContext $context = null): void
{
}
public function critical(string $message, ?LogContext $context = null): void
{
}
public function error(string $message, ?LogContext $context = null): void
{
}
public function warning(string $message, ?LogContext $context = null): void
{
}
public function notice(string $message, ?LogContext $context = null): void
{
}
public function info(string $message, ?LogContext $context = null): void
{
}
public function debug(string $message, ?LogContext $context = null): void
{
}
};
}
}