Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
281
tests/Framework/DI/ContainerCompilerTest.php
Normal file
281
tests/Framework/DI/ContainerCompilerTest.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\DI\ContainerCompiler;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\DI\DependencyResolver;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->tempDir = sys_get_temp_dir() . '/container-compiler-test-' . uniqid();
|
||||
mkdir($this->tempDir, 0755, true);
|
||||
|
||||
$this->container = new DefaultContainer();
|
||||
$this->reflectionProvider = new CachedReflectionProvider();
|
||||
$this->dependencyResolver = new DependencyResolver($this->reflectionProvider, $this->container);
|
||||
$this->compiler = new ContainerCompiler($this->reflectionProvider, $this->dependencyResolver);
|
||||
|
||||
$this->compiledPath = $this->tempDir . '/compiled-container.php';
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
// Clean up test directory
|
||||
if (is_dir($this->tempDir)) {
|
||||
array_map('unlink', glob($this->tempDir . '/*'));
|
||||
rmdir($this->tempDir);
|
||||
}
|
||||
});
|
||||
|
||||
// Test classes
|
||||
class ContainerCompilerTestSimpleService
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'simple';
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerCompilerTestServiceWithDependency
|
||||
{
|
||||
public function __construct(private ContainerCompilerTestSimpleService $service)
|
||||
{
|
||||
}
|
||||
|
||||
public function getServiceName(): string
|
||||
{
|
||||
return $this->service->getName();
|
||||
}
|
||||
}
|
||||
|
||||
interface ContainerCompilerTestServiceInterface
|
||||
{
|
||||
public function getValue(): string;
|
||||
}
|
||||
|
||||
class ContainerCompilerTestConcreteService implements ContainerCompilerTestServiceInterface
|
||||
{
|
||||
public function getValue(): string
|
||||
{
|
||||
return 'concrete';
|
||||
}
|
||||
}
|
||||
|
||||
test('compiles container with simple binding', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
|
||||
// Act
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Assert
|
||||
expect(file_exists($this->compiledPath))->toBeTrue();
|
||||
|
||||
$content = file_get_contents($this->compiledPath);
|
||||
expect($content)->toContain('class CompiledContainer implements Container');
|
||||
expect($content)->toContain('createContainerCompilerTestSimpleService()');
|
||||
});
|
||||
|
||||
test('compiles container with dependency injection', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
|
||||
|
||||
// Act
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Assert
|
||||
expect(file_exists($this->compiledPath))->toBeTrue();
|
||||
|
||||
$content = file_get_contents($this->compiledPath);
|
||||
expect($content)->toContain('$this->get(\'ContainerCompilerTestSimpleService\')');
|
||||
});
|
||||
|
||||
test('compiles container with singletons', function () {
|
||||
// Arrange
|
||||
$this->container->singleton(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
|
||||
// Act
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Assert
|
||||
expect(file_exists($this->compiledPath))->toBeTrue();
|
||||
|
||||
$content = file_get_contents($this->compiledPath);
|
||||
expect($content)->toContain('$this->singletons[\'ContainerCompilerTestSimpleService\'] = true');
|
||||
});
|
||||
|
||||
test('loads compiled container successfully', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Act
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
|
||||
// Assert
|
||||
expect($compiledContainer)->toBeInstanceOf(\App\Framework\DI\Container::class);
|
||||
expect($compiledContainer->has(ContainerCompilerTestSimpleService::class))->toBeTrue();
|
||||
|
||||
$instance = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
||||
expect($instance)->toBeInstanceOf(ContainerCompilerTestSimpleService::class);
|
||||
expect($instance->getName())->toBe('simple');
|
||||
});
|
||||
|
||||
test('compiled container resolves dependencies correctly', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Act
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
$instance = $compiledContainer->get(ContainerCompilerTestServiceWithDependency::class);
|
||||
|
||||
// Assert
|
||||
expect($instance)->toBeInstanceOf(ContainerCompilerTestServiceWithDependency::class);
|
||||
expect($instance->getServiceName())->toBe('simple');
|
||||
});
|
||||
|
||||
test('compiled container handles singletons correctly', function () {
|
||||
// Arrange
|
||||
$this->container->singleton(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Act
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
$instance1 = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
||||
$instance2 = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
||||
|
||||
// Assert
|
||||
expect($instance1)->toBe($instance2);
|
||||
});
|
||||
|
||||
test('validates compiled container hash correctly', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Act & Assert - should be valid initially
|
||||
expect($this->compiler->isCompiledContainerValid($this->container, $this->compiledPath))->toBeTrue();
|
||||
|
||||
// Add new binding to change container state
|
||||
$this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
|
||||
|
||||
// Should now be invalid due to hash mismatch
|
||||
expect($this->compiler->isCompiledContainerValid($this->container, $this->compiledPath))->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns false for non-existent compiled container', function () {
|
||||
// Arrange
|
||||
$nonExistentPath = $this->tempDir . '/non-existent.php';
|
||||
|
||||
// Act & Assert
|
||||
expect($this->compiler->isCompiledContainerValid($this->container, $nonExistentPath))->toBeFalse();
|
||||
});
|
||||
|
||||
test('creates directory if it does not exist', function () {
|
||||
// Arrange
|
||||
$nestedPath = $this->tempDir . '/nested/deep/compiled-container.php';
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
|
||||
// Act
|
||||
$this->compiler->compile($this->container, $nestedPath);
|
||||
|
||||
// Assert
|
||||
expect(file_exists($nestedPath))->toBeTrue();
|
||||
expect(is_dir(dirname($nestedPath)))->toBeTrue();
|
||||
});
|
||||
|
||||
test('throws exception when loading non-existent compiled container', function () {
|
||||
// Arrange
|
||||
$nonExistentPath = $this->tempDir . '/non-existent.php';
|
||||
|
||||
// Act & Assert
|
||||
expect(fn () => ContainerCompiler::load($nonExistentPath))
|
||||
->toThrow(RuntimeException::class, 'Compiled container not found');
|
||||
});
|
||||
|
||||
test('compiled container throws exception for runtime binding', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
|
||||
// Act & Assert
|
||||
expect(fn () => $compiledContainer->bind('NewClass', 'AnotherClass'))
|
||||
->toThrow(RuntimeException::class, 'Cannot bind to compiled container');
|
||||
});
|
||||
|
||||
test('compiled container throws exception for runtime singleton registration', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
|
||||
// Act & Assert
|
||||
expect(fn () => $compiledContainer->singleton('NewClass', 'AnotherClass'))
|
||||
->toThrow(RuntimeException::class, 'Cannot add singletons to compiled container');
|
||||
});
|
||||
|
||||
test('compiled container allows runtime instance registration', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
$customInstance = new ContainerCompilerTestSimpleService();
|
||||
|
||||
// Act
|
||||
$compiledContainer->instance(ContainerCompilerTestSimpleService::class, $customInstance);
|
||||
$retrievedInstance = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
||||
|
||||
// Assert
|
||||
expect($retrievedInstance)->toBe($customInstance);
|
||||
});
|
||||
|
||||
test('gets default compiled container path', function () {
|
||||
// Act
|
||||
$path = ContainerCompiler::getCompiledContainerPath();
|
||||
|
||||
// Assert
|
||||
expect($path)->toContain('compiled-container.php');
|
||||
expect(is_dir(dirname($path)))->toBeTrue();
|
||||
});
|
||||
|
||||
test('gets custom compiled container path', function () {
|
||||
// Arrange
|
||||
$customCacheDir = $this->tempDir . '/custom-cache';
|
||||
|
||||
// Act
|
||||
$path = ContainerCompiler::getCompiledContainerPath($customCacheDir);
|
||||
|
||||
// Assert
|
||||
expect($path)->toBe($customCacheDir . '/compiled-container.php');
|
||||
expect(is_dir($customCacheDir))->toBeTrue();
|
||||
});
|
||||
|
||||
test('compiled container handles unknown class gracefully', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
||||
|
||||
// Act & Assert
|
||||
expect(fn () => $compiledContainer->get('UnknownClass'))
|
||||
->toThrow(InvalidArgumentException::class, 'Class UnknownClass is not bound in the container');
|
||||
});
|
||||
|
||||
test('generated code contains proper metadata', function () {
|
||||
// Arrange
|
||||
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
||||
|
||||
// Act
|
||||
$this->compiler->compile($this->container, $this->compiledPath);
|
||||
|
||||
// Assert
|
||||
$content = file_get_contents($this->compiledPath);
|
||||
expect($content)->toContain('Generated:');
|
||||
expect($content)->toContain('Hash:');
|
||||
expect($content)->toContain('WARNING: This file is auto-generated');
|
||||
expect($content)->toMatch('/Hash: [a-f0-9]{64}/');
|
||||
});
|
||||
Reference in New Issue
Block a user