[BUGFIX] Use Hreflang for HTML tag
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Service / ExtensionServiceTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Extbase\Tests\Unit\Service;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17 use Doctrine\DBAL\Statement;
18 use Prophecy\Argument;
19 use Prophecy\Prophecy\ObjectProphecy;
20 use TYPO3\CMS\Core\Database\Connection;
21 use TYPO3\CMS\Core\Database\ConnectionPool;
22 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
23 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
24 use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
27 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
28 use TYPO3\CMS\Extbase\Exception;
29 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
30
31 /**
32 * Test case
33 */
34 class ExtensionServiceTest extends UnitTestCase
35 {
36 /**
37 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
38 */
39 protected $mockConfigurationManager;
40
41 /**
42 * @var \TYPO3\CMS\Extbase\Service\ExtensionService
43 */
44 protected $extensionService;
45
46 /**
47 * Due to nested PageRepository / FrontendRestriction Container issues, the Context object is set
48 * @var bool
49 */
50 protected $resetSingletonInstances = true;
51
52 protected function setUp(): void
53 {
54 parent::setUp();
55 $GLOBALS['TSFE'] = new \stdClass();
56 $this->extensionService = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Service\ExtensionService::class, ['dummy']);
57 $this->mockConfigurationManager = $this->createMock(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::class);
58 $this->extensionService->_set('configurationManager', $this->mockConfigurationManager);
59 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'] = [
60 'ExtensionName' => [
61 'plugins' => [
62 'SomePlugin' => [
63 'controllers' => [
64 'ControllerName' => [
65 'actions' => ['index', 'otherAction']
66 ]
67 ]
68 ],
69 'ThirdPlugin' => [
70 'controllers' => [
71 'ControllerName' => [
72 'actions' => ['otherAction', 'thirdAction']
73 ]
74 ]
75 ]
76 ]
77 ],
78 'SomeOtherExtensionName' => [
79 'plugins' => [
80 'SecondPlugin' => [
81 'controllers' => [
82 'ControllerName' => [
83 'actions' => ['index', 'otherAction']
84 ],
85 'SecondControllerName' => [
86 'actions' => ['someAction', 'someOtherAction'],
87 'nonCacheableActions' => ['someOtherAction']
88 ]
89 ]
90 ]
91 ]
92 ]
93 ];
94 }
95
96 /**
97 * Setup and return a mocked database connection that allows
98 * the QueryBuilder to work.
99 *
100 * @return ObjectProphecy
101 */
102 protected function getMockDatabaseConnection(): ObjectProphecy
103 {
104 $connection = $this->prophesize(Connection::class);
105 $connection->getDatabasePlatform()->willReturn(new MockPlatform());
106 $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
107 $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
108
109 $queryBuilder = new QueryBuilder(
110 $connection->reveal(),
111 null,
112 new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
113 );
114
115 $connectionPool = $this->prophesize(ConnectionPool::class);
116 $connectionPool->getQueryBuilderForTable('tt_content')->willReturn($queryBuilder);
117 GeneralUtility::addInstance(ConnectionPool::class, $connectionPool->reveal());
118
119 return $connection;
120 }
121
122 /**
123 * DataProvider for getPluginNamespaceByPluginSignatureTests()
124 *
125 * @return array
126 */
127 public function getPluginNamespaceDataProvider()
128 {
129 return [
130 [null, null, 'tx__'],
131 ['', '', 'tx__'],
132 ['SomeExtension', 'SomePlugin', 'tx_someextension_someplugin'],
133 ['NonExistingExtension', 'SomePlugin', 'tx_nonexistingextension_someplugin'],
134 ['Invalid', '', 'tx_invalid_']
135 ];
136 }
137
138 /**
139 * @test
140 * @dataProvider getPluginNamespaceDataProvider
141 * @param string $extensionName
142 * @param string $pluginName
143 * @param mixed $expectedResult
144 */
145 public function getPluginNamespaceTests($extensionName, $pluginName, $expectedResult)
146 {
147 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will($this->returnValue([]));
148 $actualResult = $this->extensionService->getPluginNamespace($extensionName, $pluginName);
149 $this->assertEquals($expectedResult, $actualResult, 'Failing for extension: "' . $extensionName . '", plugin: "' . $pluginName . '"');
150 }
151
152 /**
153 * @test
154 */
155 public function pluginNamespaceCanBeOverridden()
156 {
157 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->with(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, 'SomeExtension', 'SomePlugin')->will($this->returnValue(['view' => ['pluginNamespace' => 'overridden_plugin_namespace']]));
158 $expectedResult = 'overridden_plugin_namespace';
159 $actualResult = $this->extensionService->getPluginNamespace('SomeExtension', 'SomePlugin');
160 $this->assertEquals($expectedResult, $actualResult);
161 }
162
163 /**
164 * DataProvider for getPluginNameByActionTests()
165 *
166 * @return array
167 */
168 public function getPluginNameByActionDataProvider()
169 {
170 return [
171 ['ExtensionName', 'ControllerName', 'someNonExistingAction', null],
172 ['ExtensionName', 'ControllerName', 'index', 'SomePlugin'],
173 ['ExtensionName', 'ControllerName', 'thirdAction', 'ThirdPlugin'],
174 ['eXtEnSiOnNaMe', 'cOnTrOlLeRnAmE', 'thirdAction', null],
175 ['eXtEnSiOnNaMe', 'cOnTrOlLeRnAmE', 'ThIrDaCtIoN', null],
176 ['SomeOtherExtensionName', 'ControllerName', 'otherAction', 'SecondPlugin']
177 ];
178 }
179
180 /**
181 * @test
182 * @dataProvider getPluginNameByActionDataProvider
183 * @param string $extensionName
184 * @param string $controllerName
185 * @param string $actionName
186 * @param mixed $expectedResult
187 */
188 public function getPluginNameByActionTests($extensionName, $controllerName, $actionName, $expectedResult)
189 {
190 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->with(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK)->will($this->returnValue(['view' => ['pluginNamespace' => 'overridden_plugin_namespace']]));
191 $actualResult = $this->extensionService->getPluginNameByAction($extensionName, $controllerName, $actionName);
192 $this->assertEquals($expectedResult, $actualResult, 'Failing for $extensionName: "' . $extensionName . '", $controllerName: "' . $controllerName . '", $actionName: "' . $actionName . '" - ');
193 }
194
195 /**
196 * @test
197 */
198 public function getPluginNameByActionThrowsExceptionIfMoreThanOnePluginMatches()
199 {
200 $this->expectException(Exception::class);
201 $this->expectExceptionCode(1280825466);
202 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->with(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK)->will($this->returnValue(['view' => ['pluginNamespace' => 'overridden_plugin_namespace']]));
203 $this->extensionService->getPluginNameByAction('ExtensionName', 'ControllerName', 'otherAction');
204 }
205
206 /**
207 * @test
208 */
209 public function getPluginNameByActionReturnsCurrentIfItCanHandleTheActionEvenIfMoreThanOnePluginMatches()
210 {
211 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->with(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK)->will($this->returnValue(['extensionName' => 'CurrentExtension', 'pluginName' => 'CurrentPlugin', 'controllerConfiguration' => ['ControllerName' => ['actions' => ['otherAction']]]]));
212 $actualResult = $this->extensionService->getPluginNameByAction('CurrentExtension', 'ControllerName', 'otherAction');
213 $expectedResult = 'CurrentPlugin';
214 $this->assertEquals($expectedResult, $actualResult);
215 }
216
217 /**
218 * @test
219 */
220 public function isActionCacheableReturnsTrueByDefault()
221 {
222 $mockConfiguration = [];
223 $this->mockConfigurationManager->expects($this->any())->method('getConfiguration')->will($this->returnValue($mockConfiguration));
224 $actualResult = $this->extensionService->isActionCacheable('SomeExtension', 'SomePlugin', 'SomeController', 'someAction');
225 $this->assertTrue($actualResult);
226 }
227
228 /**
229 * @test
230 */
231 public function isActionCacheableReturnsFalseIfActionIsNotCacheable()
232 {
233 $mockConfiguration = [
234 'controllerConfiguration' => [
235 'SomeController' => [
236 'nonCacheableActions' => ['someAction']
237 ]
238 ]
239 ];
240 $this->mockConfigurationManager->expects($this->any())->method('getConfiguration')->will($this->returnValue($mockConfiguration));
241 $actualResult = $this->extensionService->isActionCacheable('SomeExtension', 'SomePlugin', 'SomeController', 'someAction');
242 $this->assertFalse($actualResult);
243 }
244
245 /**
246 * @test
247 */
248 public function getTargetPidByPluginSignatureReturnsNullIfConfigurationManagerIsNotInitialized()
249 {
250 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will($this->returnValue([]));
251 $this->assertNull($this->extensionService->getTargetPidByPlugin('ExtensionName', 'PluginName'));
252 }
253
254 /**
255 * @test
256 */
257 public function getTargetPidByPluginSignatureReturnsNullIfDefaultPidIsZero()
258 {
259 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will($this->returnValue(['view' => ['defaultPid' => 0]]));
260 $this->assertNull($this->extensionService->getTargetPidByPlugin('ExtensionName', 'PluginName'));
261 }
262
263 /**
264 * @test
265 */
266 public function getTargetPidByPluginSignatureReturnsTheConfiguredDefaultPid()
267 {
268 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will($this->returnValue(['view' => ['defaultPid' => 123]]));
269 $expectedResult = 123;
270 $actualResult = $this->extensionService->getTargetPidByPlugin('ExtensionName', 'SomePlugin');
271 $this->assertEquals($expectedResult, $actualResult);
272 }
273
274 /**
275 * @test
276 * @todo This should rather be a functional test since it needs a connection / querybuilder
277 */
278 public function getTargetPidByPluginSignatureDeterminesTheTargetPidIfDefaultPidIsAuto()
279 {
280 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will(
281 $this->returnValue(['view' => ['defaultPid' => 'auto']])
282 );
283 $expectedResult = 321;
284
285 $statement = $this->prophesize(Statement::class);
286 $statement->fetchAll()->shouldBeCalled()->willReturn([['pid' => (string)$expectedResult]]);
287
288 $connection = $this->getMockDatabaseConnection();
289 $connection->executeQuery(
290 'SELECT pid FROM tt_content WHERE (list_type = :dcValue1) AND (CType = :dcValue2) AND (sys_language_uid = :dcValue3) LIMIT 2',
291 ['dcValue1' => 'extensionname_someplugin', 'dcValue2' => 'list', 'dcValue3' => 0],
292 Argument::cetera()
293 )->shouldBeCalled()->willReturn($statement->reveal());
294
295 $actualResult = $this->extensionService->getTargetPidByPlugin('ExtensionName', 'SomePlugin');
296 $this->assertEquals($expectedResult, $actualResult);
297 }
298
299 /**
300 * @test
301 * @todo This should rather be a functional test since it needs a connection / querybuilder
302 */
303 public function getTargetPidByPluginSignatureReturnsNullIfTargetPidCouldNotBeDetermined()
304 {
305 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will(
306 $this->returnValue(['view' => ['defaultPid' => 'auto']])
307 );
308
309 $statement = $this->prophesize(Statement::class);
310 $statement->fetchAll()->shouldBeCalled()->willReturn([]);
311
312 $connection = $this->getMockDatabaseConnection();
313 $connection->executeQuery(
314 'SELECT pid FROM tt_content WHERE (list_type = :dcValue1) AND (CType = :dcValue2) AND (sys_language_uid = :dcValue3) LIMIT 2',
315 ['dcValue1' => 'extensionname_someplugin', 'dcValue2' => 'list', 'dcValue3' => 0],
316 Argument::cetera()
317 )->shouldBeCalled()->willReturn($statement->reveal());
318
319 $this->assertNull($this->extensionService->getTargetPidByPlugin('ExtensionName', 'SomePlugin'));
320 }
321
322 /**
323 * @test
324 * @todo This should rather be a functional test since it needs a connection / querybuilder
325 */
326 public function getTargetPidByPluginSignatureThrowsExceptionIfMoreThanOneTargetPidsWereFound()
327 {
328 $this->expectException(Exception::class);
329 $this->expectExceptionCode(1280773643);
330
331 $this->mockConfigurationManager->expects($this->once())->method('getConfiguration')->will(
332 $this->returnValue(['view' => ['defaultPid' => 'auto']])
333 );
334
335 $statement = $this->prophesize(Statement::class);
336 $statement->fetchAll()->shouldBeCalled()->willReturn([['pid' => 123], ['pid' => 124]]);
337
338 $connection = $this->getMockDatabaseConnection();
339 $connection->executeQuery(
340 'SELECT pid FROM tt_content WHERE (list_type = :dcValue1) AND (CType = :dcValue2) AND (sys_language_uid = :dcValue3) LIMIT 2',
341 ['dcValue1' => 'extensionname_someplugin', 'dcValue2' => 'list', 'dcValue3' => 0],
342 Argument::cetera()
343 )->shouldBeCalled()->willReturn($statement->reveal());
344
345 $this->expectException(\TYPO3\CMS\Extbase\Exception::class);
346 $this->expectExceptionCode(1280773643);
347
348 $this->extensionService->getTargetPidByPlugin('ExtensionName', 'SomePlugin');
349 }
350
351 /**
352 * @test
353 */
354 public function getDefaultControllerNameByPluginReturnsNullIfGivenExtensionCantBeFound()
355 {
356 $this->assertNull($this->extensionService->getDefaultControllerNameByPlugin('NonExistingExtensionName', 'SomePlugin'));
357 }
358
359 /**
360 * @test
361 */
362 public function getDefaultControllerNameByPluginReturnsNullIfGivenPluginCantBeFound()
363 {
364 $this->assertNull($this->extensionService->getDefaultControllerNameByPlugin('ExtensionName', 'NonExistingPlugin'));
365 }
366
367 /**
368 * @test
369 */
370 public function getDefaultControllerNameByPluginReturnsFirstControllerNameOfGivenPlugin()
371 {
372 $expectedResult = 'ControllerName';
373 $actualResult = $this->extensionService->getDefaultControllerNameByPlugin('ExtensionName', 'SomePlugin');
374 $this->assertEquals($expectedResult, $actualResult);
375 }
376
377 /**
378 * @test
379 */
380 public function getDefaultActionNameByPluginAndControllerReturnsNullIfGivenExtensionCantBeFound()
381 {
382 $this->assertNull($this->extensionService->getDefaultActionNameByPluginAndController('NonExistingExtensionName', 'SomePlugin', 'ControllerName'));
383 }
384
385 /**
386 * @test
387 */
388 public function getDefaultActionNameByPluginAndControllerReturnsNullIfGivenPluginCantBeFound()
389 {
390 $this->assertNull($this->extensionService->getDefaultActionNameByPluginAndController('ExtensionName', 'NonExistingPlugin', 'ControllerName'));
391 }
392
393 /**
394 * @test
395 */
396 public function getDefaultActionNameByPluginAndControllerReturnsNullIfGivenControllerCantBeFound()
397 {
398 $this->assertNull($this->extensionService->getDefaultActionNameByPluginAndController('ExtensionName', 'SomePlugin', 'NonExistingControllerName'));
399 }
400
401 /**
402 * @test
403 */
404 public function getDefaultActionNameByPluginAndControllerReturnsFirstActionNameOfGivenController()
405 {
406 $expectedResult = 'someAction';
407 $actualResult = $this->extensionService->getDefaultActionNameByPluginAndController('SomeOtherExtensionName', 'SecondPlugin', 'SecondControllerName');
408 $this->assertEquals($expectedResult, $actualResult);
409 }
410
411 /**
412 * @test
413 */
414 public function getTargetPageTypeByFormatReturnsZeroIfNoMappingIsSet(): void
415 {
416 $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
417 $configurationManagerProphecy->getConfiguration(
418 ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK,
419 'extension'
420 )->willReturn([]);
421 $this->extensionService->injectConfigurationManager($configurationManagerProphecy->reveal());
422
423 $result = $this->extensionService->getTargetPageTypeByFormat('extension', 'json');
424
425 $this->assertSame(0, $result);
426 }
427
428 /**
429 * @test
430 */
431 public function getTargetPageTypeByFormatReturnsMappedPageTypeFromConfiguration(): void
432 {
433 $configurationManagerProphecy = $this->prophesize(ConfigurationManager::class);
434 $configurationManagerProphecy->getConfiguration(
435 ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK,
436 'extension'
437 )->willReturn([
438 'view' => [
439 'formatToPageTypeMapping' => [
440 'json' => 111
441 ]
442 ]
443 ]);
444 $this->extensionService->injectConfigurationManager($configurationManagerProphecy->reveal());
445
446 $result = $this->extensionService->getTargetPageTypeByFormat('extension', 'json');
447
448 $this->assertSame(111, $result);
449 }
450 }