3 * @copyright Copyright (C) 2020, Friendica
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()
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 private function mockL10nT(string $text, $times = null)
69 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
73 * Mocking the DI::l10n()->t() calls for the function checks
75 private function mockFunctionL10TCalls()
77 $this->mockL10nT('Apache mod_rewrite module', 1);
78 $this->mockL10nT('PDO or MySQLi PHP module', 1);
79 $this->mockL10nT('libCurl PHP module', 1);
80 $this->mockL10nT('Error: libCURL PHP module required but not installed.', 1);
81 $this->mockL10nT('XML PHP module', 1);
82 $this->mockL10nT('GD graphics PHP module', 1);
83 $this->mockL10nT('Error: GD graphics PHP module with JPEG support required but not installed.', 1);
84 $this->mockL10nT('OpenSSL PHP module', 1);
85 $this->mockL10nT('Error: openssl PHP module required but not installed.', 1);
86 $this->mockL10nT('mb_string PHP module', 1);
87 $this->mockL10nT('Error: mb_string PHP module required but not installed.', 1);
88 $this->mockL10nT('iconv PHP module', 1);
89 $this->mockL10nT('Error: iconv PHP module required but not installed.', 1);
90 $this->mockL10nT('POSIX PHP module', 1);
91 $this->mockL10nT('Error: POSIX PHP module required but not installed.', 1);
92 $this->mockL10nT('JSON PHP module', 1);
93 $this->mockL10nT('Error: JSON PHP module required but not installed.', 1);
94 $this->mockL10nT('File Information PHP module', 1);
95 $this->mockL10nT('Error: File Information PHP module required but not installed.', 1);
98 private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
100 $subSet = [$position => [
103 'required' => $required,
108 self::assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
112 * Replaces function_exists results with given mocks
114 * @param array $functions a list from function names and their result
116 private function setFunctions(array $functions)
119 $phpMock['function_exists'] = function($function) use ($functions) {
120 foreach ($functions as $name => $value) {
121 if ($function == $name) {
125 return '__phpunit_continue__';
130 * Replaces class_exist results with given mocks
132 * @param array $classes a list from class names and their results
134 private function setClasses(array $classes)
137 $phpMock['class_exists'] = function($class) use ($classes) {
138 foreach ($classes as $name => $value) {
139 if ($class == $name) {
143 return '__phpunit_continue__';
150 public function testCheckKeys()
152 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
154 $this->setFunctions(['openssl_pkey_new' => false]);
155 $install = new Installer();
156 self::assertFalse($install->checkKeys());
158 $this->setFunctions(['openssl_pkey_new' => true]);
159 $install = new Installer();
160 self::assertTrue($install->checkKeys());
166 public function testCheckFunctions()
168 $this->mockFunctionL10TCalls();
169 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
170 $install = new Installer();
171 self::assertFalse($install->checkFunctions());
172 self::assertCheckExist(3,
173 'libCurl PHP module',
174 'Error: libCURL PHP module required but not installed.',
177 $install->getChecks());
179 $this->mockFunctionL10TCalls();
180 $this->setFunctions(['imagecreatefromjpeg' => false]);
181 $install = new Installer();
182 self::assertFalse($install->checkFunctions());
183 self::assertCheckExist(4,
184 'GD graphics PHP module',
185 'Error: GD graphics PHP module with JPEG support required but not installed.',
188 $install->getChecks());
190 $this->mockFunctionL10TCalls();
191 $this->setFunctions(['openssl_public_encrypt' => false]);
192 $install = new Installer();
193 self::assertFalse($install->checkFunctions());
194 self::assertCheckExist(5,
195 'OpenSSL PHP module',
196 'Error: openssl PHP module required but not installed.',
199 $install->getChecks());
201 $this->mockFunctionL10TCalls();
202 $this->setFunctions(['mb_strlen' => false]);
203 $install = new Installer();
204 self::assertFalse($install->checkFunctions());
205 self::assertCheckExist(6,
206 'mb_string PHP module',
207 'Error: mb_string PHP module required but not installed.',
210 $install->getChecks());
212 $this->mockFunctionL10TCalls();
213 $this->setFunctions(['iconv_strlen' => false]);
214 $install = new Installer();
215 self::assertFalse($install->checkFunctions());
216 self::assertCheckExist(7,
218 'Error: iconv PHP module required but not installed.',
221 $install->getChecks());
223 $this->mockFunctionL10TCalls();
224 $this->setFunctions(['posix_kill' => false]);
225 $install = new Installer();
226 self::assertFalse($install->checkFunctions());
227 self::assertCheckExist(8,
229 'Error: POSIX PHP module required but not installed.',
232 $install->getChecks());
234 $this->mockFunctionL10TCalls();
235 $this->setFunctions(['json_encode' => false]);
236 $install = new Installer();
237 self::assertFalse($install->checkFunctions());
238 self::assertCheckExist(9,
240 'Error: JSON PHP module required but not installed.',
243 $install->getChecks());
245 $this->mockFunctionL10TCalls();
246 $this->setFunctions(['finfo_open' => false]);
247 $install = new Installer();
248 self::assertFalse($install->checkFunctions());
249 self::assertCheckExist(10,
250 'File Information PHP module',
251 'Error: File Information PHP module required but not installed.',
254 $install->getChecks());
256 $this->mockFunctionL10TCalls();
257 $this->setFunctions([
259 'imagecreatefromjpeg' => true,
260 'openssl_public_encrypt' => true,
262 'iconv_strlen' => true,
263 'posix_kill' => true,
264 'json_encode' => true,
265 'finfo_open' => true,
267 $install = new Installer();
268 self::assertTrue($install->checkFunctions());
274 public function testCheckLocalIni()
276 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
278 self::assertTrue($this->root->hasChild('config/local.config.php'));
280 $install = new Installer();
281 self::assertTrue($install->checkLocalIni());
283 $this->delConfigFile('local.config.php');
285 self::assertFalse($this->root->hasChild('config/local.config.php'));
287 $install = new Installer();
288 self::assertTrue($install->checkLocalIni());
293 * @runInSeparateProcess
294 * @preserveGlobalState disabled
296 public function testCheckHtAccessFail()
298 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
300 // Mocking the CURL Response
301 $curlResult = Mockery::mock(CurlResult::class);
303 ->shouldReceive('getReturnCode')
306 ->shouldReceive('getRedirectUrl')
309 ->shouldReceive('getError')
310 ->andReturn('test Error');
312 // Mocking the CURL Request
313 $networkMock = Mockery::mock(IHTTPRequest::class);
315 ->shouldReceive('fetchFull')
316 ->with('https://test/install/testrewrite')
317 ->andReturn($curlResult);
319 ->shouldReceive('fetchFull')
320 ->with('http://test/install/testrewrite')
321 ->andReturn($curlResult);
323 $this->dice->shouldReceive('create')
324 ->with(IHTTPRequest::class)
325 ->andReturn($networkMock);
327 DI::init($this->dice);
329 // Mocking that we can use CURL
330 $this->setFunctions(['curl_init' => true]);
332 $install = new Installer();
334 self::assertFalse($install->checkHtAccess('https://test'));
335 self::assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
340 * @runInSeparateProcess
341 * @preserveGlobalState disabled
343 public function testCheckHtAccessWork()
345 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
347 // Mocking the failed CURL Response
348 $curlResultF = Mockery::mock(CurlResult::class);
350 ->shouldReceive('getReturnCode')
353 // Mocking the working CURL Response
354 $curlResultW = Mockery::mock(CurlResult::class);
356 ->shouldReceive('getReturnCode')
359 // Mocking the CURL Request
360 $networkMock = Mockery::mock(IHTTPRequest::class);
362 ->shouldReceive('fetchFull')
363 ->with('https://test/install/testrewrite')
364 ->andReturn($curlResultF);
366 ->shouldReceive('fetchFull')
367 ->with('http://test/install/testrewrite')
368 ->andReturn($curlResultW);
370 $this->dice->shouldReceive('create')
371 ->with(IHTTPRequest::class)
372 ->andReturn($networkMock);
374 DI::init($this->dice);
376 // Mocking that we can use CURL
377 $this->setFunctions(['curl_init' => true]);
379 $install = new Installer();
381 self::assertTrue($install->checkHtAccess('https://test'));
386 * @runInSeparateProcess
387 * @preserveGlobalState disabled
389 public function testImagick()
391 static::markTestIncomplete('needs adapted class_exists() mock');
393 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
395 $this->setClasses(['Imagick' => true]);
397 $install = new Installer();
399 // even there is no supported type, Imagick should return true (because it is not required)
400 self::assertTrue($install->checkImagick());
402 self::assertCheckExist(1,
403 $this->l10nMock->t('ImageMagick supports GIF'),
407 $install->getChecks());
412 * @runInSeparateProcess
413 * @preserveGlobalState disabled
415 public function testImagickNotFound()
417 static::markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
419 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
421 $this->setClasses(['Imagick' => true]);
423 $install = new Installer();
425 // even there is no supported type, Imagick should return true (because it is not required)
426 self::assertTrue($install->checkImagick());
427 self::assertCheckExist(1,
428 $this->l10nMock->t('ImageMagick supports GIF'),
432 $install->getChecks());
435 public function testImagickNotInstalled()
437 $this->setClasses(['Imagick' => false]);
438 $this->mockL10nT('ImageMagick PHP extension is not installed');
440 $install = new Installer();
442 // even there is no supported type, Imagick should return true (because it is not required)
443 self::assertTrue($install->checkImagick());
444 self::assertCheckExist(0,
445 'ImageMagick PHP extension is not installed',
449 $install->getChecks());
453 * Test the setup of the config cache for installation
455 public function testSetUpCache()
457 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
459 $install = new Installer();
460 $configCache = Mockery::mock(Cache::class);
461 $configCache->shouldReceive('set')->with('config', 'php_path', Mockery::any())->once();
462 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
464 $install->setUpCache($configCache, '/test/');
469 * A workaround to replace the PHP native function_exists with a mocked function
471 * @param string $function_name the Name of the function
473 * @return bool true or false
475 function function_exists(string $function_name)
478 if (isset($phpMock['function_exists'])) {
479 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
480 if ($result !== '__phpunit_continue__') {
484 return call_user_func_array('\function_exists', func_get_args());
487 function class_exists($class_name)
490 if (isset($phpMock['class_exists'])) {
491 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
492 if ($result !== '__phpunit_continue__') {
496 return call_user_func_array('\class_exists', func_get_args());