- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
311 lines
8.2 KiB
PHP
311 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
|
|
{
|
|
}
|
|
};
|
|
}
|
|
}
|