Files
michaelschiemer/tests/Unit/Framework/DI/ParameterResolverTest.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

257 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Framework\DI;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\DI\Container;
use App\Framework\DI\ParameterResolver;
use App\Framework\Logging\Attributes\LogChannel as LogChannelAttribute;
use App\Framework\Logging\DefaultLogger;
use App\Framework\Logging\HasChannel;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\Logger;
use App\Framework\Logging\SupportsChannels;
use App\Framework\Reflection\CachedReflectionProvider;
// Test classes for parameter resolution
final class ServiceWithLogChannelAttribute
{
public function __construct(
#[LogChannelAttribute(LogChannel::CACHE)]
public Logger $logger
) {
}
}
final class ServiceWithMultipleLogChannels
{
public function testMethod(
#[LogChannelAttribute(LogChannel::SECURITY)]
Logger $securityLogger,
#[LogChannelAttribute(LogChannel::DATABASE)]
Logger $dbLogger,
#[LogChannelAttribute(LogChannel::CACHE)]
Logger $cacheLogger
): void {
}
}
final class ServiceWithOverrideTest
{
public function __construct(
#[LogChannelAttribute(LogChannel::CACHE)]
public Logger $logger
) {
}
}
final class ServiceWithTypedDependency
{
public function __construct(public Logger $logger)
{
}
}
final class ServiceWithDefaultValue
{
public function __construct(public string $name = 'default-value')
{
}
}
final class ServiceWithNullable
{
public function __construct(public ?string $optionalParam)
{
}
}
final class ServiceWithRequiredParam
{
public function __construct(public string $requiredParam)
{
}
}
final class ServiceWithMixedParams
{
public function complexMethod(
#[LogChannelAttribute(LogChannel::FRAMEWORK)]
Logger $attributeLogger,
Logger $overriddenParam,
Logger $containerParam,
string $defaultParam = 'default',
?string $nullableParam = null
): void {
}
}
beforeEach(function () {
$this->container = $this->createMock(Container::class);
$this->reflectionProvider = new CachedReflectionProvider();
$this->resolver = new ParameterResolver($this->container, $this->reflectionProvider);
});
describe('ParameterResolver', function () {
it('resolves parameters with LogChannel attribute', function () {
// Mock main logger - use real DefaultLogger for simplicity
$mainLogger = new DefaultLogger();
$this->container->expects($this->once())
->method('get')
->with(SupportsChannels::class)
->willReturn($mainLogger);
$className = ClassName::create(ServiceWithLogChannelAttribute::class);
$params = $this->resolver->resolveMethodParameters($className, '__construct');
expect($params)->toHaveCount(1);
expect($params[0])->toBeInstanceOf(Logger::class);
expect($params[0])->toBeInstanceOf(HasChannel::class);
expect($params[0]->channel)->toBe(LogChannel::CACHE);
});
it('resolves different channel loggers via attribute', function () {
$mainLogger = new DefaultLogger();
$this->container->expects($this->exactly(3))
->method('get')
->with(SupportsChannels::class)
->willReturn($mainLogger);
$className = ClassName::create(ServiceWithMultipleLogChannels::class);
$params = $this->resolver->resolveMethodParameters($className, 'testMethod');
expect($params)->toHaveCount(3);
// Verify each logger has correct channel
expect($params[0])->toBeInstanceOf(Logger::class);
expect($params[0])->toBeInstanceOf(HasChannel::class);
expect($params[0]->channel)->toBe(LogChannel::SECURITY);
expect($params[1])->toBeInstanceOf(Logger::class);
expect($params[1])->toBeInstanceOf(HasChannel::class);
expect($params[1]->channel)->toBe(LogChannel::DATABASE);
expect($params[2])->toBeInstanceOf(Logger::class);
expect($params[2])->toBeInstanceOf(HasChannel::class);
expect($params[2]->channel)->toBe(LogChannel::CACHE);
});
it('resolves override values before attributes', function () {
// Container should NOT be called because override takes priority
$this->container->expects($this->never())
->method('get');
$overrideLogger = $this->createMock(Logger::class);
$className = ClassName::create(ServiceWithOverrideTest::class);
$params = $this->resolver->resolveMethodParameters(
$className,
'__construct',
['logger' => $overrideLogger]
);
expect($params)->toHaveCount(1);
expect($params[0])->toBe($overrideLogger);
});
it('resolves type-based injection when no attribute present', function () {
$logger = $this->createMock(Logger::class);
$this->container->expects($this->once())
->method('has')
->with(Logger::class)
->willReturn(true);
$this->container->expects($this->once())
->method('get')
->with(Logger::class)
->willReturn($logger);
$className = ClassName::create(ServiceWithTypedDependency::class);
$params = $this->resolver->resolveMethodParameters($className, '__construct');
expect($params)->toHaveCount(1);
expect($params[0])->toBe($logger);
});
it('resolves default values when no injection possible', function () {
$this->container->expects($this->never())
->method('get');
$className = ClassName::create(ServiceWithDefaultValue::class);
$params = $this->resolver->resolveMethodParameters($className, '__construct');
expect($params)->toHaveCount(1);
expect($params[0])->toBe('default-value');
});
it('resolves nullable parameters to null', function () {
$this->container->expects($this->never())
->method('get');
$className = ClassName::create(ServiceWithNullable::class);
$params = $this->resolver->resolveMethodParameters($className, '__construct');
expect($params)->toHaveCount(1);
expect($params[0])->toBeNull();
});
it('throws exception when parameter cannot be resolved', function () {
$this->container->method('has')->willReturn(false);
$className = ClassName::create(ServiceWithRequiredParam::class);
expect(fn () => $this->resolver->resolveMethodParameters($className, '__construct'))
->toThrow(\InvalidArgumentException::class, 'Cannot resolve parameter');
});
it('follows correct resolution priority order', function () {
$mainLogger = new DefaultLogger();
$overrideLogger = $this->createMock(Logger::class);
$containerLogger = $this->createMock(Logger::class);
$this->container->method('get')
->willReturnCallback(function ($type) use ($mainLogger, $containerLogger) {
return match ($type) {
SupportsChannels::class => $mainLogger,
Logger::class => $containerLogger,
default => throw new \RuntimeException("Unexpected type: {$type}")
};
});
$this->container->method('has')
->with(Logger::class)
->willReturn(true);
$className = ClassName::create(ServiceWithMixedParams::class);
$params = $this->resolver->resolveMethodParameters(
$className,
'complexMethod',
['overriddenParam' => $overrideLogger]
);
expect($params)->toHaveCount(5);
// Attribute injection
expect($params[0])->toBeInstanceOf(Logger::class);
expect($params[0])->toBeInstanceOf(HasChannel::class);
expect($params[0]->channel)->toBe(LogChannel::FRAMEWORK);
// Override
expect($params[1])->toBe($overrideLogger);
// Container
expect($params[2])->toBe($containerLogger);
// Default value
expect($params[3])->toBe('default');
// Nullable
expect($params[4])->toBeNull();
});
});