ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
ImplementationOfAgentFinderTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21namespace ILIAS\Tests\Setup;
22
23use ILIAS\Setup;
29use ILIAS\Refinery\Factory as Refinery;
30use ILIAS\Data\Factory as DataFactory;
32use PHPUnit\Framework\TestCase;
33
34class ImplementationOfAgentFinderTest extends TestCase
35{
36 private string $plugin_directory;
37
38 protected function setUp(): void
39 {
40 $this->plugin_directory = sys_get_temp_dir() . "/ilias_agent_finder_" . bin2hex(random_bytes(4))
41 . "/Customizing/global/plugins";
42 }
43
44 protected function tearDown(): void
45 {
46 $root = dirname($this->plugin_directory, 3);
47 if (!is_dir($root)) {
48 return;
49 }
50 $files = new \RecursiveIteratorIterator(
51 new \RecursiveDirectoryIterator($root, \FilesystemIterator::SKIP_DOTS),
52 \RecursiveIteratorIterator::CHILD_FIRST
53 );
54 foreach ($files as $file) {
55 $file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
56 }
57 rmdir($root);
58 }
59
60 private function finder(
61 array $component_agents,
62 ?ImplementationOfInterfaceFinder $interface_finder = null
63 ): ImplementationOfAgentFinder {
64 $refinery = new Refinery(new DataFactory(), $this->createStub(Language::class));
65
66 return new ImplementationOfAgentFinder(
68 new DataFactory(),
69 $this->createStub(Language::class),
70 $interface_finder ?? $this->createStub(ImplementationOfInterfaceFinder::class),
71 $component_agents,
72 $this->plugin_directory
73 );
74 }
75
76 private function createPlugin(string $path, string ...$agent_classes): void
77 {
78 mkdir($this->plugin_directory . "/" . $path . "/classes", 0777, true);
79 foreach ($agent_classes as $class) {
80 $file = $this->plugin_directory . "/" . $path . "/classes/class.$class.php";
81 file_put_contents($file, sprintf(<<<'PHP'
82 <?php
83 class %1$s extends \ilPluginDefaultAgent
84 {
85 public function __construct(
86 \ILIAS\Refinery\Factory $refinery,
87 \ILIAS\Data\Factory $data_factory,
88 \ILIAS\Language\Language $lng
89 ) {
90 parent::__construct('%1$s');
91 }
92 }
93 PHP, $class));
94 require_once $file;
95 }
96 }
97
103 private function ensureTestObjectiveClass(): void
104 {
105 if (class_exists(\TestPluginObjective::class, false)) {
106 return;
107 }
108 $file = sys_get_temp_dir() . "/ilias_agent_finder_test_objective.php";
109 file_put_contents(
110 $file,
111 "<?php\n\nclass TestPluginObjective extends \\ILIAS\\Setup\\Objective\\NullObjective\n{\n}\n"
112 );
113 require_once $file;
114 }
115
121 private function writePluginAgent(string $plugin, string $template): string
122 {
123 $class = "il" . $plugin . "Agent" . bin2hex(random_bytes(4));
124 $dir = $this->plugin_directory . "/Services/Repository/RepositoryObject/" . $plugin . "/classes";
125 if (!is_dir($dir)) {
126 mkdir($dir, 0777, true);
127 }
128 $file = $dir . "/class.$class.php";
129 file_put_contents($file, "<?php\n\n" . sprintf($template, $class));
130 require_once $file;
131 return $class;
132 }
133
135 private function defaultAgentTemplate(): string
136 {
137 return <<<'PHP'
138 class %1$s extends \ilPluginDefaultAgent
139 {
140 public function __construct(
141 \ILIAS\Refinery\Factory $refinery,
142 \ILIAS\Data\Factory $data_factory,
143 \ILIAS\Language\Language $lng
144 ) {
145 parent::__construct('%1$s');
146 }
147 }
148 PHP;
149 }
150
152 private function implementingAgentTemplate(): string
153 {
154 return <<<'PHP'
155 class %1$s implements \ILIAS\Setup\Agent
156 {
157 public function __construct(
158 \ILIAS\Refinery\Factory $refinery,
159 \ILIAS\Data\Factory $data_factory,
160 \ILIAS\Language\Language $lng
161 ) {
162 }
163
164 public function hasConfig(): bool
165 {
166 return true;
167 }
168
169 public function getArrayToConfigTransformation(): \ILIAS\Refinery\Transformation
170 {
171 throw new \LogicException('not needed');
172 }
173
174 public function getInstallObjective(?\ILIAS\Setup\Config $config = null): \ILIAS\Setup\Objective
175 {
176 return new \TestPluginObjective();
177 }
178
179 public function getUpdateObjective(?\ILIAS\Setup\Config $config = null): \ILIAS\Setup\Objective
180 {
181 return new \TestPluginObjective();
182 }
183
184 public function getBuildObjective(): \ILIAS\Setup\Objective
185 {
186 return new \ILIAS\Setup\Objective\NullObjective();
187 }
188
189 public function getStatusObjective(\ILIAS\Setup\Metrics\Storage $storage): \ILIAS\Setup\Objective
190 {
191 return new \ILIAS\Setup\Objective\NullObjective();
192 }
193
194 public function getMigrations(): array
195 {
196 return ['test_migration' => new class () implements \ILIAS\Setup\Migration {
197 public function getLabel(): string
198 {
199 return 'test migration';
200 }
201
202 public function getDefaultAmountOfStepsPerRun(): int
203 {
204 return 1;
205 }
206
207 public function getPreconditions(\ILIAS\Setup\Environment $environment): array
208 {
209 return [];
210 }
211
212 public function prepare(\ILIAS\Setup\Environment $environment): void
213 {
214 }
215
216 public function step(\ILIAS\Setup\Environment $environment): void
217 {
218 }
219
220 public function getRemainingAmountOfSteps(): int
221 {
222 return 0;
223 }
224 }];
225 }
226
227 public function getNamedObjectives(?\ILIAS\Setup\Config $config = null): array
228 {
229 return [
230 'test_objective' => new \ILIAS\Setup\ObjectiveConstructor(
231 'test objective',
232 static fn(): \ILIAS\Setup\Objective => new \ILIAS\Setup\Objective\NullObjective()
233 )
234 ];
235 }
236 }
237 PHP;
238 }
239
240 private function pluginFinder(array $agent_classes): ImplementationOfAgentFinder
241 {
242 $interface_finder = $this->createStub(ImplementationOfInterfaceFinder::class);
243 $interface_finder
244 ->method('getMatchingClassNames')
245 ->willReturn(new \ArrayIterator($agent_classes));
246
247 return $this->finder([], $interface_finder);
248 }
249
253 protected function flatten(Setup\Objective $objective): array
254 {
255 if (!$objective instanceof Setup\ObjectiveCollection) {
256 return [get_class($objective)];
257 }
258
259 return array_merge(...array_map(
260 fn(Setup\Objective $o): array => $this->flatten($o),
261 $objective->getObjectives()
262 ));
263 }
264
265 public function testNamedAgentIsKeyedByItsDeclaredName(): void
266 {
267 $named = $this->createStub(NamedAgent::class);
268 $named->method('getAgentName')->willReturn('content_isolation');
269
270 $collection = $this->finder([$named])->getComponentAgents();
271
272 $this->assertSame($named, $collection->getAgent('content_isolation'));
273 }
274
275 public function testPlainAgentFallsBackToClassNameKeying(): void
276 {
277 $plain = $this->createStub(Agent::class);
278
279 $collection = $this->finder([$plain])->getComponentAgents();
280
281 // no semantic name -> not reachable under one, but reachable by class name
282 $this->assertNull($collection->getAgent('content_isolation'));
283 $this->assertSame($plain, $collection->getAgent($plain::class));
284 }
285
286 public function testPluginsAreFoundFourLevelsBelowThePluginDirectory(): void
287 {
288 $this->createPlugin("Services/Repository/RepositoryObject/Alpha");
289 $this->createPlugin("Modules/Course/CourseHook/Beta");
290 mkdir($this->plugin_directory . "/Services/Repository/RepositoryObject/.hidden", 0777, true);
291 touch($this->plugin_directory . "/Services/Repository/RepositoryObject/README.md");
292
293 $agents = $this->finder([])->getAgents();
294
295 $this->assertEqualsCanonicalizing(['Alpha', 'Beta'], array_keys($agents->getAgents()));
296 $this->assertInstanceOf(\ilPluginDefaultAgent::class, $agents->getAgent('Alpha'));
297 }
298
299 public function testAgentsOfAllPluginsAreSearchedInOnePass(): void
300 {
301 $suffix = bin2hex(random_bytes(4));
302 $gamma = "ilGammaSetupAgent$suffix";
303 $delta_1 = "ilDeltaOneSetupAgent$suffix";
304 $delta_2 = "ilDeltaTwoSetupAgent$suffix";
305 $this->createPlugin("Services/Repository/RepositoryObject/Gamma", $gamma);
306 $this->createPlugin("Services/Repository/RepositoryObject/Delta", $delta_1, $delta_2);
307 $this->createPlugin("Services/Repository/RepositoryObject/Epsilon");
308
309 $interface_finder = $this->createMock(ImplementationOfInterfaceFinder::class);
310 $interface_finder
311 ->expects($this->once())
312 ->method('getMatchingClassNames')
313 ->willReturn(new \ArrayIterator([$gamma, $delta_1, $delta_2]));
314
315 $agents = $this->finder([], $interface_finder)->getAgents();
316
317 $this->assertInstanceOf($gamma, $agents->getAgent('Gamma'));
318 $this->assertInstanceOf(AgentCollection::class, $agents->getAgent('Delta'));
319 $this->assertCount(2, $agents->getAgent('Delta')->getAgents());
320 $this->assertInstanceOf(\ilPluginDefaultAgent::class, $agents->getAgent('Epsilon'));
321 }
322
323 public function testPluginAgentIgnoresClassesOfOtherPlugins(): void
324 {
325 $suffix = bin2hex(random_bytes(4));
326 $zeta = "ilZetaSetupAgent$suffix";
327 // a plugin that happens to contain a directory named like another plugin
328 $this->createPlugin("Services/Repository/RepositoryObject/Eta/lib/Zeta", $zeta);
329
330 $interface_finder = $this->createStub(ImplementationOfInterfaceFinder::class);
331 $interface_finder->method('getMatchingClassNames')->willReturn(new \ArrayIterator([$zeta]));
332
333 $agent = $this->finder([], $interface_finder)->getPluginAgent('Zeta');
334
335 $this->assertNotInstanceOf($zeta, $agent);
336 $this->assertInstanceOf(\ilPluginDefaultAgent::class, $agent);
337 }
338
339 public function testPluginWithoutAgentGetsTheDefaultAgent(): void
340 {
341 $agent = $this->pluginFinder([])->getPluginAgent('TestPlugin');
342
343 $this->assertInstanceOf(\ilPluginDefaultAgent::class, $agent);
344 $this->assertNotInstanceOf(\ilPluginAgentWithDefaultObjectives::class, $agent);
345 }
346
347 public function testAgentExtendingTheDefaultAgentIsUsedDirectly(): void
348 {
349 $class = $this->writePluginAgent('TestPlugin', $this->defaultAgentTemplate());
350
351 $agent = $this->pluginFinder([$class])->getPluginAgent('TestPlugin');
352
353 $this->assertInstanceOf($class, $agent);
354 }
355
356 public function testAgentImplementingAgentGetsTheDefaultObjectives(): void
357 {
358 $this->ensureTestObjectiveClass();
359 $class = $this->writePluginAgent('TestPlugin', $this->implementingAgentTemplate());
360
361 $agent = $this->pluginFinder([$class])->getPluginAgent('TestPlugin');
362
363 $this->assertInstanceOf(\ilPluginAgentWithDefaultObjectives::class, $agent);
364
365 $install_objectives = $this->flatten($agent->getInstallObjective());
366 $this->assertContains(\ilComponentInstallPluginObjective::class, $install_objectives);
367 $this->assertContains(\ilComponentActivatePluginsObjective::class, $install_objectives);
368 $this->assertContains(\TestPluginObjective::class, $install_objectives);
369
370 $update_objectives = $this->flatten($agent->getUpdateObjective());
371 $this->assertContains(\ilComponentUpdatePluginObjective::class, $update_objectives);
372 $this->assertNotContains(\ilComponentActivatePluginsObjective::class, $update_objectives);
373 $this->assertContains(\TestPluginObjective::class, $update_objectives);
374 }
375
376 public function testAgentImplementingAgentKeepsItsPaths(): void
377 {
378 $this->ensureTestObjectiveClass();
379 $class = $this->writePluginAgent('TestPlugin', $this->implementingAgentTemplate());
380
381 $agent = $this->pluginFinder([$class])->getPluginAgent('TestPlugin');
382
383 $this->assertSame(['test_objective'], array_keys($agent->getNamedObjectives()));
384 $this->assertSame(['test_migration'], array_keys($agent->getMigrations()));
385 $this->assertTrue($agent->hasConfig());
386 }
387
388 public function testSeveralAgentsAreCollectedUnderTheirShortClassNames(): void
389 {
390 $default_agent = $this->writePluginAgent('TestPlugin', $this->defaultAgentTemplate());
391 $implementing_agent = $this->writePluginAgent('TestPlugin', $this->implementingAgentTemplate());
392
393 $agent = $this->pluginFinder([$default_agent, $implementing_agent])->getPluginAgent('TestPlugin');
394
395 $this->assertInstanceOf(AgentCollection::class, $agent);
396 $this->assertInstanceOf($default_agent, $agent->getAgent($default_agent));
397 $this->assertInstanceOf($implementing_agent, $agent->getAgent($implementing_agent));
398 }
399}
Builds data types.
Definition: Factory.php:36
An agent that is just a collection of some other agents.
__construct()
Constructor setup ILIAS global object @access public.
Definition: class.ilias.php:76
An agent that declares its own name in the agent collection.
Definition: NamedAgent.php:33
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
global $lng
Definition: privfeed.php:26