Some checks failed
Deploy Application / deploy (push) Has been cancelled
154 lines
5.2 KiB
PHP
154 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Framework\DI;
|
|
|
|
use App\Framework\Core\ValueObjects\ClassName;
|
|
use App\Framework\Core\ValueObjects\MethodName;
|
|
use App\Framework\DI\Exceptions\InitializerCycleException;
|
|
use App\Framework\DI\InitializerDependencyAnalyzer;
|
|
use App\Framework\DI\InitializerDependencyGraph;
|
|
use App\Framework\Reflection\ReflectionService;
|
|
use App\Framework\Reflection\SimpleReflectionService;
|
|
|
|
describe('InitializerDependencyGraph', function () {
|
|
it('can add initializers and build execution order', function () {
|
|
$reflectionService = new SimpleReflectionService();
|
|
$graph = new InitializerDependencyGraph($reflectionService);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceA',
|
|
ClassName::create('App\Test\ServiceAInitializer'),
|
|
MethodName::create('__invoke')
|
|
);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceB',
|
|
ClassName::create('App\Test\ServiceBInitializer'),
|
|
MethodName::create('__invoke')
|
|
);
|
|
|
|
expect($graph->hasNode('ServiceA'))->toBeTrue();
|
|
expect($graph->hasNode('ServiceB'))->toBeTrue();
|
|
expect($graph->hasNode('ServiceC'))->toBeFalse();
|
|
});
|
|
|
|
it('uses explicit dependencies when provided', function () {
|
|
$reflectionService = new SimpleReflectionService();
|
|
$graph = new InitializerDependencyGraph($reflectionService);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceA',
|
|
ClassName::create('App\Test\ServiceAInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceB', 'ServiceC'], // Explicit dependencies
|
|
100 // Priority
|
|
);
|
|
|
|
$node = $graph->getNode('ServiceA');
|
|
expect($node)->not->toBeNull();
|
|
expect($node->dependencies)->toBe(['ServiceB', 'ServiceC']);
|
|
});
|
|
|
|
it('detects circular dependencies', function () {
|
|
$reflectionService = new SimpleReflectionService();
|
|
$graph = new InitializerDependencyGraph($reflectionService);
|
|
|
|
// Create a cycle: A -> B -> A
|
|
$graph->addInitializer(
|
|
'ServiceA',
|
|
ClassName::create('App\Test\ServiceAInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceB'] // A depends on B
|
|
);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceB',
|
|
ClassName::create('App\Test\ServiceBInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceA'] // B depends on A (cycle!)
|
|
);
|
|
|
|
expect(fn() => $graph->getExecutionOrder())->toThrow(InitializerCycleException::class);
|
|
});
|
|
|
|
it('provides cycle information with dependency paths', function () {
|
|
$reflectionService = new SimpleReflectionService();
|
|
$graph = new InitializerDependencyGraph($reflectionService);
|
|
|
|
// Create a cycle: A -> B -> A
|
|
$graph->addInitializer(
|
|
'ServiceA',
|
|
ClassName::create('App\Test\ServiceAInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceB']
|
|
);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceB',
|
|
ClassName::create('App\Test\ServiceBInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceA']
|
|
);
|
|
|
|
try {
|
|
$graph->getExecutionOrder();
|
|
expect(false)->toBeTrue(); // Should not reach here
|
|
} catch (InitializerCycleException $e) {
|
|
$cycles = $e->getCycles();
|
|
expect($cycles)->not->toBeEmpty();
|
|
|
|
$paths = $e->getDependencyPaths();
|
|
expect($paths)->not->toBeEmpty();
|
|
}
|
|
});
|
|
|
|
it('calculates correct execution order for linear dependencies', function () {
|
|
$reflectionService = new SimpleReflectionService();
|
|
$graph = new InitializerDependencyGraph($reflectionService);
|
|
|
|
// C depends on B, B depends on A
|
|
$graph->addInitializer(
|
|
'ServiceA',
|
|
ClassName::create('App\Test\ServiceAInitializer'),
|
|
MethodName::create('__invoke'),
|
|
[] // No dependencies
|
|
);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceB',
|
|
ClassName::create('App\Test\ServiceBInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceA'] // B depends on A
|
|
);
|
|
|
|
$graph->addInitializer(
|
|
'ServiceC',
|
|
ClassName::create('App\Test\ServiceCInitializer'),
|
|
MethodName::create('__invoke'),
|
|
['ServiceB'] // C depends on B
|
|
);
|
|
|
|
$order = $graph->getExecutionOrder();
|
|
|
|
// A should come before B, B should come before C
|
|
$indexA = array_search('ServiceA', $order, true);
|
|
$indexB = array_search('ServiceB', $order, true);
|
|
$indexC = array_search('ServiceC', $order, true);
|
|
|
|
expect($indexA)->toBeLessThan($indexB);
|
|
expect($indexB)->toBeLessThan($indexC);
|
|
});
|
|
|
|
it('integrates with InitializerDependencyAnalyzer when provided', function () {
|
|
$reflectionService = new SimpleReflectionService();
|
|
$analyzer = new InitializerDependencyAnalyzer(null);
|
|
$graph = new InitializerDependencyGraph($reflectionService, $analyzer);
|
|
|
|
// Graph should accept analyzer without errors
|
|
expect($graph)->toBeInstanceOf(InitializerDependencyGraph::class);
|
|
});
|
|
});
|
|
|