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([
282 'imagecreatefromjpeg' => true,
283 'openssl_public_encrypt' => true,
285 'iconv_strlen' => true,
286 'posix_kill' => true,
287 'json_encode' => true,
288 'finfo_open' => true,
290 $install = new Installer();
291 self::assertTrue($install->checkFunctions());
297 public function testCheckLocalIni()
299 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
301 self::assertTrue($this->root->hasChild('config/local.config.php'));
303 $install = new Installer();
304 self::assertTrue($install->checkLocalIni());
306 $this->delConfigFile('local.config.php');
308 self::assertFalse($this->root->hasChild('config/local.config.php'));
310 $install = new Installer();
311 self::assertTrue($install->checkLocalIni());
316 * @runInSeparateProcess
317 * @preserveGlobalState disabled
319 public function testCheckHtAccessFail()
321 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
323 // Mocking the CURL Response
324 $IHTTPResult = Mockery::mock(ICanHandleHttpResponses::class);
326 ->shouldReceive('getReturnCode')
329 ->shouldReceive('getRedirectUrl')
332 ->shouldReceive('getError')
333 ->andReturn('test Error');
335 // Mocking the CURL Request
336 $networkMock = Mockery::mock(ICanSendHttpRequests::class);
338 ->shouldReceive('fetchFull')
339 ->with('https://test/install/testrewrite')
340 ->andReturn($IHTTPResult);
342 ->shouldReceive('fetchFull')
343 ->with('http://test/install/testrewrite')
344 ->andReturn($IHTTPResult);
346 $this->dice->shouldReceive('create')
347 ->with(ICanSendHttpRequests::class)
348 ->andReturn($networkMock);
350 DI::init($this->dice);
352 // Mocking that we can use CURL
353 $this->setFunctions(['curl_init' => true]);
355 $install = new Installer();
357 self::assertFalse($install->checkHtAccess('https://test'));
358 self::assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
363 * @runInSeparateProcess
364 * @preserveGlobalState disabled
366 public function testCheckHtAccessWork()
368 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
370 // Mocking the failed CURL Response
371 $IHTTPResultF = Mockery::mock(ICanHandleHttpResponses::class);
373 ->shouldReceive('getReturnCode')
376 // Mocking the working CURL Response
377 $IHTTPResultW = Mockery::mock(ICanHandleHttpResponses::class);
379 ->shouldReceive('getReturnCode')
382 // Mocking the CURL Request
383 $networkMock = Mockery::mock(ICanSendHttpRequests::class);
385 ->shouldReceive('fetchFull')
386 ->with('https://test/install/testrewrite')
387 ->andReturn($IHTTPResultF);
389 ->shouldReceive('fetchFull')
390 ->with('http://test/install/testrewrite')
391 ->andReturn($IHTTPResultW);
393 $this->dice->shouldReceive('create')
394 ->with(ICanSendHttpRequests::class)
395 ->andReturn($networkMock);
397 DI::init($this->dice);
399 // Mocking that we can use CURL
400 $this->setFunctions(['curl_init' => true]);
402 $install = new Installer();
404 self::assertTrue($install->checkHtAccess('https://test'));
409 * @runInSeparateProcess
410 * @preserveGlobalState disabled
412 public function testImagick()
414 static::markTestIncomplete('needs adapted class_exists() mock');
416 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
418 $this->setClasses(['Imagick' => true]);
420 $install = new Installer();
422 // even there is no supported type, Imagick should return true (because it is not required)
423 self::assertTrue($install->checkImagick());
425 self::assertCheckExist(1,
426 $this->l10nMock->t('ImageMagick supports GIF'),
430 $install->getChecks());
435 * @runInSeparateProcess
436 * @preserveGlobalState disabled
438 public function testImagickNotFound()
440 static::markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
442 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
444 $this->setClasses(['Imagick' => true]);
446 $install = new Installer();
448 // even there is no supported type, Imagick should return true (because it is not required)
449 self::assertTrue($install->checkImagick());
450 self::assertCheckExist(1,
451 $this->l10nMock->t('ImageMagick supports GIF'),
455 $install->getChecks());
458 public function testImagickNotInstalled()
460 $this->setClasses(['Imagick' => false]);
461 $this->mockL10nT('ImageMagick PHP extension is not installed');
463 $install = new Installer();
465 // even there is no supported type, Imagick should return true (because it is not required)
466 self::assertTrue($install->checkImagick());
467 self::assertCheckExist(0,
468 'ImageMagick PHP extension is not installed',
472 $install->getChecks());
476 * Test the setup of the config cache for installation
477 * @doesNotPerformAssertions
479 public function testSetUpCache()
481 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
483 $install = new Installer();
484 $configCache = Mockery::mock(Cache::class);
485 $configCache->shouldReceive('set')->with('config', 'php_path', Mockery::any())->once();
486 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
488 $install->setUpCache($configCache, '/test/');
493 * A workaround to replace the PHP native function_exists with a mocked function
495 * @param string $function_name the Name of the function
497 * @return bool true or false
499 function function_exists(string $function_name)
502 if (isset($phpMock['function_exists'])) {
503 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
504 if ($result !== '__phpunit_continue__') {
508 return call_user_func_array('\function_exists', func_get_args());
511 function class_exists($class_name)
514 if (isset($phpMock['class_exists'])) {
515 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
516 if ($result !== '__phpunit_continue__') {
520 return call_user_func_array('\class_exists', func_get_args());