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 { } }; } }