3 * @copyright Copyright (C) 2010-2021, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 /// @todo this is in the same namespace as Install for mocking 'function_exists'
23 namespace Friendica\Core;
26 use Friendica\Core\Config\Cache;
28 use Friendica\Network\CurlResult;
29 use Friendica\Network\IHTTPRequest;
30 use Friendica\Test\MockedTest;
31 use Friendica\Test\Util\VFSTrait;
33 use Mockery\MockInterface;
35 class InstallerTest extends MockedTest
40 * @var L10n|MockInterface
44 * @var Dice|MockInterface
48 protected function setUp(): void
54 $this->l10nMock = Mockery::mock(L10n::class);
56 /** @var Dice|MockInterface $dice */
57 $this->dice = Mockery::mock(Dice::class)->makePartial();
58 $this->dice = $this->dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php');
60 $this->dice->shouldReceive('create')
62 ->andReturn($this->l10nMock);
64 DI::init($this->dice);
67 public static function tearDownAfterClass(): void
73 parent::tearDownAfterClass();
76 private function mockL10nT(string $text, $times = null)
78 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
82 * Mocking the DI::l10n()->t() calls for the function checks
84 private function mockFunctionL10TCalls()
86 $this->mockL10nT('Apache mod_rewrite module', 1);
87 $this->mockL10nT('PDO or MySQLi PHP module', 1);
88 $this->mockL10nT('libCurl PHP module', 1);
89 $this->mockL10nT('Error: libCURL PHP module required but not installed.', 1);
90 $this->mockL10nT('XML PHP module', 1);
91 $this->mockL10nT('GD graphics PHP module', 1);
92 $this->mockL10nT('Error: GD graphics PHP module with JPEG support required but not installed.', 1);
93 $this->mockL10nT('OpenSSL PHP module', 1);
94 $this->mockL10nT('Error: openssl PHP module required but not installed.', 1);
95 $this->mockL10nT('mb_string PHP module', 1);
96 $this->mockL10nT('Error: mb_string PHP module required but not installed.', 1);
97 $this->mockL10nT('iconv PHP module', 1);
98 $this->mockL10nT('Error: iconv PHP module required but not installed.', 1);
99 $this->mockL10nT('POSIX PHP module', 1);
100 $this->mockL10nT('Error: POSIX PHP module required but not installed.', 1);
101 $this->mockL10nT('JSON PHP module', 1);
102 $this->mockL10nT('Error: JSON PHP module required but not installed.', 1);
103 $this->mockL10nT('File Information PHP module', 1);
104 $this->mockL10nT('Error: File Information PHP module required but not installed.', 1);
105 $this->mockL10nT('Program execution functions', 1);
106 $this->mockL10nT('Error: Program execution functions required but not enabled.', 1);
109 private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
111 $subSet = [$position => [
114 'required' => $required,
119 self::assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
123 * Replaces function_exists results with given mocks
125 * @param array $functions a list from function names and their result
127 private function setFunctions(array $functions)
130 $phpMock['function_exists'] = function($function) use ($functions) {
131 foreach ($functions as $name => $value) {
132 if ($function == $name) {
136 return '__phpunit_continue__';
141 * Replaces class_exist results with given mocks
143 * @param array $classes a list from class names and their results
145 private function setClasses(array $classes)
148 $phpMock['class_exists'] = function($class) use ($classes) {
149 foreach ($classes as $name => $value) {
150 if ($class == $name) {
154 return '__phpunit_continue__';
161 public function testCheckKeys()
163 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
165 $this->setFunctions(['openssl_pkey_new' => false]);
166 $install = new Installer();
167 self::assertFalse($install->checkKeys());
169 $this->setFunctions(['openssl_pkey_new' => true]);
170 $install = new Installer();
171 self::assertTrue($install->checkKeys());
177 public function testCheckFunctions()
179 $this->mockFunctionL10TCalls();
180 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
181 $install = new Installer();
182 self::assertFalse($install->checkFunctions());
183 self::assertCheckExist(3,
184 'libCurl PHP module',
185 'Error: libCURL PHP module required but not installed.',
188 $install->getChecks());
190 $this->mockFunctionL10TCalls();
191 $this->setFunctions(['imagecreatefromjpeg' => false]);
192 $install = new Installer();
193 self::assertFalse($install->checkFunctions());
194 self::assertCheckExist(4,
195 'GD graphics PHP module',
196 'Error: GD graphics PHP module with JPEG support required but not installed.',
199 $install->getChecks());
201 $this->mockFunctionL10TCalls();
202 $this->setFunctions(['openssl_public_encrypt' => false]);
203 $install = new Installer();
204 self::assertFalse($install->checkFunctions());
205 self::assertCheckExist(5,
206 'OpenSSL PHP module',
207 'Error: openssl PHP module required but not installed.',
210 $install->getChecks());
212 $this->mockFunctionL10TCalls();
213 $this->setFunctions(['mb_strlen' => false]);
214 $install = new Installer();
215 self::assertFalse($install->checkFunctions());
216 self::assertCheckExist(6,
217 'mb_string PHP module',
218 'Error: mb_string PHP module required but not installed.',
221 $install->getChecks());
223 $this->mockFunctionL10TCalls();
224 $this->setFunctions(['iconv_strlen' => false]);
225 $install = new Installer();
226 self::assertFalse($install->checkFunctions());
227 self::assertCheckExist(7,
229 'Error: iconv PHP module required but not installed.',
232 $install->getChecks());
234 $this->mockFunctionL10TCalls();
235 $this->setFunctions(['posix_kill' => false]);
236 $install = new Installer();
237 self::assertFalse($install->checkFunctions());
238 self::assertCheckExist(8,
240 'Error: POSIX PHP module required but not installed.',
243 $install->getChecks());
245 $this->mockFunctionL10TCalls();
246 $this->setFunctions(['proc_open' => false]);
247 $install = new Installer();
248 self::assertFalse($install->checkFunctions());
249 self::assertCheckExist(9,
250 'Program execution functions',
251 'Error: Program execution functions required but not enabled.',
254 $install->getChecks());
255 $this->mockFunctionL10TCalls();
256 $this->setFunctions(['json_encode' => false]);
257 $install = new Installer();
258 self::assertFalse($install->checkFunctions());
259 self::assertCheckExist(10,
261 'Error: JSON PHP module required but not installed.',
264 $install->getChecks());
266 $this->mockFunctionL10TCalls();
267 $this->setFunctions(['finfo_open' => false]);
268 $install = new Installer();
269 self::assertFalse($install->checkFunctions());
270 self::assertCheckExist(11,
271 'File Information PHP module',
272 'Error: File Information PHP module required but not installed.',
275 $install->getChecks());
277 $this->mockFunctionL10TCalls();
278 $this->setFunctions([
280 'imagecreatefromjpeg' => true,
281 'openssl_public_encrypt' => true,
283 'iconv_strlen' => true,
284 'posix_kill' => true,
285 'json_encode' => true,
286 'finfo_open' => true,
288 $install = new Installer();
289 self::assertTrue($install->checkFunctions());
295 public function testCheckLocalIni()
297 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
299 self::assertTrue($this->root->hasChild('config/local.config.php'));
301 $install = new Installer();
302 self::assertTrue($install->checkLocalIni());
304 $this->delConfigFile('local.config.php');
306 self::assertFalse($this->root->hasChild('config/local.config.php'));
308 $install = new Installer();
309 self::assertTrue($install->checkLocalIni());
314 * @runInSeparateProcess
315 * @preserveGlobalState disabled
317 public function testCheckHtAccessFail()
319 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
321 // Mocking the CURL Response
322 $curlResult = Mockery::mock(CurlResult::class);
324 ->shouldReceive('getReturnCode')
327 ->shouldReceive('getRedirectUrl')
330 ->shouldReceive('getError')
331 ->andReturn('test Error');
333 // Mocking the CURL Request
334 $networkMock = Mockery::mock(IHTTPRequest::class);
336 ->shouldReceive('fetchFull')
337 ->with('https://test/install/testrewrite')
338 ->andReturn($curlResult);
340 ->shouldReceive('fetchFull')
341 ->with('http://test/install/testrewrite')
342 ->andReturn($curlResult);
344 $this->dice->shouldReceive('create')
345 ->with(IHTTPRequest::class)
346 ->andReturn($networkMock);
348 DI::init($this->dice);
350 // Mocking that we can use CURL
351 $this->setFunctions(['curl_init' => true]);
353 $install = new Installer();
355 self::assertFalse($install->checkHtAccess('https://test'));
356 self::assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
361 * @runInSeparateProcess
362 * @preserveGlobalState disabled
364 public function testCheckHtAccessWork()
366 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
368 // Mocking the failed CURL Response
369 $curlResultF = Mockery::mock(CurlResult::class);
371 ->shouldReceive('getReturnCode')
374 // Mocking the working CURL Response
375 $curlResultW = Mockery::mock(CurlResult::class);
377 ->shouldReceive('getReturnCode')
380 // Mocking the CURL Request
381 $networkMock = Mockery::mock(IHTTPRequest::class);
383 ->shouldReceive('fetchFull')
384 ->with('https://test/install/testrewrite')
385 ->andReturn($curlResultF);
387 ->shouldReceive('fetchFull')
388 ->with('http://test/install/testrewrite')
389 ->andReturn($curlResultW);
391 $this->dice->shouldReceive('create')
392 ->with(IHTTPRequest::class)
393 ->andReturn($networkMock);
395 DI::init($this->dice);
397 // Mocking that we can use CURL
398 $this->setFunctions(['curl_init' => true]);
400 $install = new Installer();
402 self::assertTrue($install->checkHtAccess('https://test'));
407 * @runInSeparateProcess
408 * @preserveGlobalState disabled
410 public function testImagick()
412 static::markTestIncomplete('needs adapted class_exists() mock');
414 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
416 $this->setClasses(['Imagick' => true]);
418 $install = new Installer();
420 // even there is no supported type, Imagick should return true (because it is not required)
421 self::assertTrue($install->checkImagick());
423 self::assertCheckExist(1,
424 $this->l10nMock->t('ImageMagick supports GIF'),
428 $install->getChecks());
433 * @runInSeparateProcess
434 * @preserveGlobalState disabled
436 public function testImagickNotFound()
438 static::markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
440 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
442 $this->setClasses(['Imagick' => true]);
444 $install = new Installer();
446 // even there is no supported type, Imagick should return true (because it is not required)
447 self::assertTrue($install->checkImagick());
448 self::assertCheckExist(1,
449 $this->l10nMock->t('ImageMagick supports GIF'),
453 $install->getChecks());
456 public function testImagickNotInstalled()
458 $this->setClasses(['Imagick' => false]);
459 $this->mockL10nT('ImageMagick PHP extension is not installed');
461 $install = new Installer();
463 // even there is no supported type, Imagick should return true (because it is not required)
464 self::assertTrue($install->checkImagick());
465 self::assertCheckExist(0,
466 'ImageMagick PHP extension is not installed',
470 $install->getChecks());
474 * Test the setup of the config cache for installation
475 * @doesNotPerformAssertions
477 public function testSetUpCache()
479 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
481 $install = new Installer();
482 $configCache = Mockery::mock(Cache::class);
483 $configCache->shouldReceive('set')->with('config', 'php_path', Mockery::any())->once();
484 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
486 $install->setUpCache($configCache, '/test/');
491 * A workaround to replace the PHP native function_exists with a mocked function
493 * @param string $function_name the Name of the function
495 * @return bool true or false
497 function function_exists(string $function_name)
500 if (isset($phpMock['function_exists'])) {
501 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
502 if ($result !== '__phpunit_continue__') {
506 return call_user_func_array('\function_exists', func_get_args());
509 function class_exists($class_name)
512 if (isset($phpMock['class_exists'])) {
513 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
514 if ($result !== '__phpunit_continue__') {
518 return call_user_func_array('\class_exists', func_get_args());