3 * @copyright Copyright (C) 2010-2022, 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 DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
27 use Friendica\Core\Config\ValueObject\Cache;
29 use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
30 use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
31 use Friendica\Test\MockedTest;
32 use Friendica\Test\Util\VFSTrait;
34 use Mockery\MockInterface;
36 class InstallerTest extends MockedTest
39 use ArraySubsetAsserts;
42 * @var L10n|MockInterface
46 * @var Dice|MockInterface
50 protected function setUp(): void
56 $this->l10nMock = Mockery::mock(L10n::class);
58 /** @var Dice|MockInterface $dice */
59 $this->dice = Mockery::mock(Dice::class)->makePartial();
60 $this->dice = $this->dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php');
62 $this->dice->shouldReceive('create')
64 ->andReturn($this->l10nMock);
66 DI::init($this->dice);
69 public static function tearDownAfterClass(): void
75 parent::tearDownAfterClass();
78 private function mockL10nT(string $text, $times = null)
80 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
84 * Mocking the DI::l10n()->t() calls for the function checks
86 private function mockFunctionL10TCalls()
88 $this->mockL10nT('Apache mod_rewrite module', 1);
89 $this->mockL10nT('PDO or MySQLi PHP module', 1);
90 $this->mockL10nT('libCurl PHP module', 1);
91 $this->mockL10nT('Error: libCURL PHP module required but not installed.', 1);
92 $this->mockL10nT('XML PHP module', 1);
93 $this->mockL10nT('GD graphics PHP module', 1);
94 $this->mockL10nT('Error: GD graphics PHP module with JPEG support required but not installed.', 1);
95 $this->mockL10nT('OpenSSL PHP module', 1);
96 $this->mockL10nT('Error: openssl PHP module required but not installed.', 1);
97 $this->mockL10nT('mb_string PHP module', 1);
98 $this->mockL10nT('Error: mb_string PHP module required but not installed.', 1);
99 $this->mockL10nT('iconv PHP module', 1);
100 $this->mockL10nT('Error: iconv PHP module required but not installed.', 1);
101 $this->mockL10nT('POSIX PHP module', 1);
102 $this->mockL10nT('Error: POSIX PHP module required but not installed.', 1);
103 $this->mockL10nT('JSON PHP module', 1);
104 $this->mockL10nT('Error: JSON PHP module required but not installed.', 1);
105 $this->mockL10nT('File Information PHP module', 1);
106 $this->mockL10nT('Error: File Information PHP module required but not installed.', 1);
107 $this->mockL10nT('Program execution functions', 1);
108 $this->mockL10nT('Error: Program execution functions (proc_open) required but not enabled.', 1);
111 private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
113 $subSet = [$position => [
116 'required' => $required,
121 self::assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
125 * Replaces function_exists results with given mocks
127 * @param array $functions a list from function names and their result
129 private function setFunctions(array $functions)
132 $phpMock['function_exists'] = function($function) use ($functions) {
133 foreach ($functions as $name => $value) {
134 if ($function == $name) {
138 return '__phpunit_continue__';
143 * Replaces class_exist results with given mocks
145 * @param array $classes a list from class names and their results
147 private function setClasses(array $classes)
150 $phpMock['class_exists'] = function($class) use ($classes) {
151 foreach ($classes as $name => $value) {
152 if ($class == $name) {
156 return '__phpunit_continue__';
163 public function testCheckKeys()
165 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
167 $this->setFunctions(['openssl_pkey_new' => false]);
168 $install = new Installer();
169 self::assertFalse($install->checkKeys());
171 $this->setFunctions(['openssl_pkey_new' => true]);
172 $install = new Installer();
173 self::assertTrue($install->checkKeys());
179 public function testCheckFunctions()
181 $this->mockFunctionL10TCalls();
182 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
183 $install = new Installer();
184 self::assertFalse($install->checkFunctions());
185 self::assertCheckExist(3,
186 'libCurl PHP module',
187 'Error: libCURL PHP module required but not installed.',
190 $install->getChecks());
192 $this->mockFunctionL10TCalls();
193 $this->setFunctions(['imagecreatefromjpeg' => false]);
194 $install = new Installer();
195 self::assertFalse($install->checkFunctions());
196 self::assertCheckExist(4,
197 'GD graphics PHP module',
198 'Error: GD graphics PHP module with JPEG support required but not installed.',
201 $install->getChecks());
203 $this->mockFunctionL10TCalls();
204 $this->setFunctions(['openssl_public_encrypt' => false]);
205 $install = new Installer();
206 self::assertFalse($install->checkFunctions());
207 self::assertCheckExist(5,
208 'OpenSSL PHP module',
209 'Error: openssl PHP module required but not installed.',
212 $install->getChecks());
214 $this->mockFunctionL10TCalls();
215 $this->setFunctions(['mb_strlen' => false]);
216 $install = new Installer();
217 self::assertFalse($install->checkFunctions());
218 self::assertCheckExist(6,
219 'mb_string PHP module',
220 'Error: mb_string PHP module required but not installed.',
223 $install->getChecks());
225 $this->mockFunctionL10TCalls();
226 $this->setFunctions(['iconv_strlen' => false]);
227 $install = new Installer();
228 self::assertFalse($install->checkFunctions());
229 self::assertCheckExist(7,
231 'Error: iconv PHP module required but not installed.',
234 $install->getChecks());
236 $this->mockFunctionL10TCalls();
237 $this->setFunctions(['posix_kill' => false]);
238 $install = new Installer();
239 self::assertFalse($install->checkFunctions());
240 self::assertCheckExist(8,
242 'Error: POSIX PHP module required but not installed.',
245 $install->getChecks());
247 $this->mockFunctionL10TCalls();
248 $this->setFunctions(['proc_open' => false]);
249 $install = new Installer();
250 self::assertFalse($install->checkFunctions());
251 self::assertCheckExist(9,
252 'Program execution functions',
253 'Error: Program execution functions (proc_open) required but not enabled.',
256 $install->getChecks());
257 $this->mockFunctionL10TCalls();
258 $this->setFunctions(['json_encode' => false]);
259 $install = new Installer();
260 self::assertFalse($install->checkFunctions());
261 self::assertCheckExist(10,
263 'Error: JSON PHP module required but not installed.',
266 $install->getChecks());
268 $this->mockFunctionL10TCalls();
269 $this->setFunctions(['finfo_open' => false]);
270 $install = new Installer();
271 self::assertFalse($install->checkFunctions());
272 self::assertCheckExist(11,
273 'File Information PHP module',
274 'Error: File Information PHP module required but not installed.',
277 $install->getChecks());
279 $this->mockFunctionL10TCalls();
280 $this->setFunctions(['gmp_strval' => false]);
281 $install = new Installer();
282 self::assertFalse($install->checkFunctions());
283 self::assertCheckExist(11,
284 'GNU Multiple Precision PHP module',
285 'Error: GNU Multiple Precision PHP module required but not installed.',
288 $install->getChecks());
290 $this->mockFunctionL10TCalls();
291 $this->setFunctions([
293 'imagecreatefromjpeg' => true,
294 'openssl_public_encrypt' => true,
296 'iconv_strlen' => true,
297 'posix_kill' => true,
298 'json_encode' => true,
299 'finfo_open' => true,
301 $install = new Installer();
302 self::assertTrue($install->checkFunctions());
308 public function testCheckLocalIni()
310 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
312 self::assertTrue($this->root->hasChild('config/local.config.php'));
314 $install = new Installer();
315 self::assertTrue($install->checkLocalIni());
317 $this->delConfigFile('local.config.php');
319 self::assertFalse($this->root->hasChild('config/local.config.php'));
321 $install = new Installer();
322 self::assertTrue($install->checkLocalIni());
327 * @runInSeparateProcess
328 * @preserveGlobalState disabled
330 public function testCheckHtAccessFail()
332 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
334 // Mocking the CURL Response
335 $IHTTPResult = Mockery::mock(ICanHandleHttpResponses::class);
337 ->shouldReceive('getReturnCode')
340 ->shouldReceive('getRedirectUrl')
343 ->shouldReceive('getError')
344 ->andReturn('test Error');
346 // Mocking the CURL Request
347 $networkMock = Mockery::mock(ICanSendHttpRequests::class);
349 ->shouldReceive('fetchFull')
350 ->with('https://test/install/testrewrite')
351 ->andReturn($IHTTPResult);
353 ->shouldReceive('fetchFull')
354 ->with('http://test/install/testrewrite')
355 ->andReturn($IHTTPResult);
357 $this->dice->shouldReceive('create')
358 ->with(ICanSendHttpRequests::class)
359 ->andReturn($networkMock);
361 DI::init($this->dice);
363 // Mocking that we can use CURL
364 $this->setFunctions(['curl_init' => true]);
366 $install = new Installer();
368 self::assertFalse($install->checkHtAccess('https://test'));
369 self::assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
374 * @runInSeparateProcess
375 * @preserveGlobalState disabled
377 public function testCheckHtAccessWork()
379 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
381 // Mocking the failed CURL Response
382 $IHTTPResultF = Mockery::mock(ICanHandleHttpResponses::class);
384 ->shouldReceive('getReturnCode')
387 // Mocking the working CURL Response
388 $IHTTPResultW = Mockery::mock(ICanHandleHttpResponses::class);
390 ->shouldReceive('getReturnCode')
393 // Mocking the CURL Request
394 $networkMock = Mockery::mock(ICanSendHttpRequests::class);
396 ->shouldReceive('fetchFull')
397 ->with('https://test/install/testrewrite')
398 ->andReturn($IHTTPResultF);
400 ->shouldReceive('fetchFull')
401 ->with('http://test/install/testrewrite')
402 ->andReturn($IHTTPResultW);
404 $this->dice->shouldReceive('create')
405 ->with(ICanSendHttpRequests::class)
406 ->andReturn($networkMock);
408 DI::init($this->dice);
410 // Mocking that we can use CURL
411 $this->setFunctions(['curl_init' => true]);
413 $install = new Installer();
415 self::assertTrue($install->checkHtAccess('https://test'));
420 * @runInSeparateProcess
421 * @preserveGlobalState disabled
423 public function testImagick()
425 static::markTestIncomplete('needs adapted class_exists() mock');
427 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
429 $this->setClasses(['Imagick' => true]);
431 $install = new Installer();
433 // even there is no supported type, Imagick should return true (because it is not required)
434 self::assertTrue($install->checkImagick());
436 self::assertCheckExist(1,
437 $this->l10nMock->t('ImageMagick supports GIF'),
441 $install->getChecks());
446 * @runInSeparateProcess
447 * @preserveGlobalState disabled
449 public function testImagickNotFound()
451 static::markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
453 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
455 $this->setClasses(['Imagick' => true]);
457 $install = new Installer();
459 // even there is no supported type, Imagick should return true (because it is not required)
460 self::assertTrue($install->checkImagick());
461 self::assertCheckExist(1,
462 $this->l10nMock->t('ImageMagick supports GIF'),
466 $install->getChecks());
469 public function testImagickNotInstalled()
471 $this->setClasses(['Imagick' => false]);
472 $this->mockL10nT('ImageMagick PHP extension is not installed');
474 $install = new Installer();
476 // even there is no supported type, Imagick should return true (because it is not required)
477 self::assertTrue($install->checkImagick());
478 self::assertCheckExist(0,
479 'ImageMagick PHP extension is not installed',
483 $install->getChecks());
487 * Test the setup of the config cache for installation
488 * @doesNotPerformAssertions
490 public function testSetUpCache()
492 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
494 $install = new Installer();
495 $configCache = Mockery::mock(Cache::class);
496 $configCache->shouldReceive('set')->with('config', 'php_path', Mockery::any())->once();
497 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
499 $install->setUpCache($configCache, '/test/');
504 * A workaround to replace the PHP native function_exists with a mocked function
506 * @param string $function_name the Name of the function
508 * @return bool true or false
510 function function_exists(string $function_name)
513 if (isset($phpMock['function_exists'])) {
514 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
515 if ($result !== '__phpunit_continue__') {
519 return call_user_func_array('\function_exists', func_get_args());
522 function class_exists($class_name)
525 if (isset($phpMock['class_exists'])) {
526 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
527 if ($result !== '__phpunit_continue__') {
531 return call_user_func_array('\class_exists', func_get_args());