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);
96 $this->mockL10nT('Program execution functions', 1);
97 $this->mockL10nT('Error: Program execution functions required but not enabled.', 1);
100 private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
102 $subSet = [$position => [
105 'required' => $required,
110 self::assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
114 * Replaces function_exists results with given mocks
116 * @param array $functions a list from function names and their result
118 private function setFunctions(array $functions)
121 $phpMock['function_exists'] = function($function) use ($functions) {
122 foreach ($functions as $name => $value) {
123 if ($function == $name) {
127 return '__phpunit_continue__';
132 * Replaces class_exist results with given mocks
134 * @param array $classes a list from class names and their results
136 private function setClasses(array $classes)
139 $phpMock['class_exists'] = function($class) use ($classes) {
140 foreach ($classes as $name => $value) {
141 if ($class == $name) {
145 return '__phpunit_continue__';
152 public function testCheckKeys()
154 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
156 $this->setFunctions(['openssl_pkey_new' => false]);
157 $install = new Installer();
158 self::assertFalse($install->checkKeys());
160 $this->setFunctions(['openssl_pkey_new' => true]);
161 $install = new Installer();
162 self::assertTrue($install->checkKeys());
168 public function testCheckFunctions()
170 $this->mockFunctionL10TCalls();
171 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
172 $install = new Installer();
173 self::assertFalse($install->checkFunctions());
174 self::assertCheckExist(3,
175 'libCurl PHP module',
176 'Error: libCURL PHP module required but not installed.',
179 $install->getChecks());
181 $this->mockFunctionL10TCalls();
182 $this->setFunctions(['imagecreatefromjpeg' => false]);
183 $install = new Installer();
184 self::assertFalse($install->checkFunctions());
185 self::assertCheckExist(4,
186 'GD graphics PHP module',
187 'Error: GD graphics PHP module with JPEG support required but not installed.',
190 $install->getChecks());
192 $this->mockFunctionL10TCalls();
193 $this->setFunctions(['openssl_public_encrypt' => false]);
194 $install = new Installer();
195 self::assertFalse($install->checkFunctions());
196 self::assertCheckExist(5,
197 'OpenSSL PHP module',
198 'Error: openssl PHP module required but not installed.',
201 $install->getChecks());
203 $this->mockFunctionL10TCalls();
204 $this->setFunctions(['mb_strlen' => false]);
205 $install = new Installer();
206 self::assertFalse($install->checkFunctions());
207 self::assertCheckExist(6,
208 'mb_string PHP module',
209 'Error: mb_string PHP module required but not installed.',
212 $install->getChecks());
214 $this->mockFunctionL10TCalls();
215 $this->setFunctions(['iconv_strlen' => false]);
216 $install = new Installer();
217 self::assertFalse($install->checkFunctions());
218 self::assertCheckExist(7,
220 'Error: iconv PHP module required but not installed.',
223 $install->getChecks());
225 $this->mockFunctionL10TCalls();
226 $this->setFunctions(['posix_kill' => false]);
227 $install = new Installer();
228 self::assertFalse($install->checkFunctions());
229 self::assertCheckExist(8,
231 'Error: POSIX PHP module required but not installed.',
234 $install->getChecks());
236 $this->mockFunctionL10TCalls();
237 $this->setFunctions(['proc_open' => false]);
238 $install = new Installer();
239 self::assertFalse($install->checkFunctions());
240 self::assertCheckExist(9,
241 'Program execution functions',
242 'Error: Program execution functions required but not enabled.',
245 $install->getChecks());
246 $this->mockFunctionL10TCalls();
247 $this->setFunctions(['json_encode' => false]);
248 $install = new Installer();
249 self::assertFalse($install->checkFunctions());
250 self::assertCheckExist(10,
252 'Error: JSON PHP module required but not installed.',
255 $install->getChecks());
257 $this->mockFunctionL10TCalls();
258 $this->setFunctions(['finfo_open' => false]);
259 $install = new Installer();
260 self::assertFalse($install->checkFunctions());
261 self::assertCheckExist(11,
262 'File Information PHP module',
263 'Error: File Information PHP module required but not installed.',
266 $install->getChecks());
268 $this->mockFunctionL10TCalls();
269 $this->setFunctions([
271 'imagecreatefromjpeg' => true,
272 'openssl_public_encrypt' => true,
274 'iconv_strlen' => true,
275 'posix_kill' => true,
276 'json_encode' => true,
277 'finfo_open' => true,
279 $install = new Installer();
280 self::assertTrue($install->checkFunctions());
286 public function testCheckLocalIni()
288 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
290 self::assertTrue($this->root->hasChild('config/local.config.php'));
292 $install = new Installer();
293 self::assertTrue($install->checkLocalIni());
295 $this->delConfigFile('local.config.php');
297 self::assertFalse($this->root->hasChild('config/local.config.php'));
299 $install = new Installer();
300 self::assertTrue($install->checkLocalIni());
305 * @runInSeparateProcess
306 * @preserveGlobalState disabled
308 public function testCheckHtAccessFail()
310 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
312 // Mocking the CURL Response
313 $curlResult = Mockery::mock(CurlResult::class);
315 ->shouldReceive('getReturnCode')
318 ->shouldReceive('getRedirectUrl')
321 ->shouldReceive('getError')
322 ->andReturn('test Error');
324 // Mocking the CURL Request
325 $networkMock = Mockery::mock(IHTTPRequest::class);
327 ->shouldReceive('fetchFull')
328 ->with('https://test/install/testrewrite')
329 ->andReturn($curlResult);
331 ->shouldReceive('fetchFull')
332 ->with('http://test/install/testrewrite')
333 ->andReturn($curlResult);
335 $this->dice->shouldReceive('create')
336 ->with(IHTTPRequest::class)
337 ->andReturn($networkMock);
339 DI::init($this->dice);
341 // Mocking that we can use CURL
342 $this->setFunctions(['curl_init' => true]);
344 $install = new Installer();
346 self::assertFalse($install->checkHtAccess('https://test'));
347 self::assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
352 * @runInSeparateProcess
353 * @preserveGlobalState disabled
355 public function testCheckHtAccessWork()
357 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
359 // Mocking the failed CURL Response
360 $curlResultF = Mockery::mock(CurlResult::class);
362 ->shouldReceive('getReturnCode')
365 // Mocking the working CURL Response
366 $curlResultW = Mockery::mock(CurlResult::class);
368 ->shouldReceive('getReturnCode')
371 // Mocking the CURL Request
372 $networkMock = Mockery::mock(IHTTPRequest::class);
374 ->shouldReceive('fetchFull')
375 ->with('https://test/install/testrewrite')
376 ->andReturn($curlResultF);
378 ->shouldReceive('fetchFull')
379 ->with('http://test/install/testrewrite')
380 ->andReturn($curlResultW);
382 $this->dice->shouldReceive('create')
383 ->with(IHTTPRequest::class)
384 ->andReturn($networkMock);
386 DI::init($this->dice);
388 // Mocking that we can use CURL
389 $this->setFunctions(['curl_init' => true]);
391 $install = new Installer();
393 self::assertTrue($install->checkHtAccess('https://test'));
398 * @runInSeparateProcess
399 * @preserveGlobalState disabled
401 public function testImagick()
403 static::markTestIncomplete('needs adapted class_exists() mock');
405 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
407 $this->setClasses(['Imagick' => true]);
409 $install = new Installer();
411 // even there is no supported type, Imagick should return true (because it is not required)
412 self::assertTrue($install->checkImagick());
414 self::assertCheckExist(1,
415 $this->l10nMock->t('ImageMagick supports GIF'),
419 $install->getChecks());
424 * @runInSeparateProcess
425 * @preserveGlobalState disabled
427 public function testImagickNotFound()
429 static::markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
431 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
433 $this->setClasses(['Imagick' => true]);
435 $install = new Installer();
437 // even there is no supported type, Imagick should return true (because it is not required)
438 self::assertTrue($install->checkImagick());
439 self::assertCheckExist(1,
440 $this->l10nMock->t('ImageMagick supports GIF'),
444 $install->getChecks());
447 public function testImagickNotInstalled()
449 $this->setClasses(['Imagick' => false]);
450 $this->mockL10nT('ImageMagick PHP extension is not installed');
452 $install = new Installer();
454 // even there is no supported type, Imagick should return true (because it is not required)
455 self::assertTrue($install->checkImagick());
456 self::assertCheckExist(0,
457 'ImageMagick PHP extension is not installed',
461 $install->getChecks());
465 * Test the setup of the config cache for installation
467 public function testSetUpCache()
469 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
471 $install = new Installer();
472 $configCache = Mockery::mock(Cache::class);
473 $configCache->shouldReceive('set')->with('config', 'php_path', Mockery::any())->once();
474 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
476 $install->setUpCache($configCache, '/test/');
481 * A workaround to replace the PHP native function_exists with a mocked function
483 * @param string $function_name the Name of the function
485 * @return bool true or false
487 function function_exists(string $function_name)
490 if (isset($phpMock['function_exists'])) {
491 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
492 if ($result !== '__phpunit_continue__') {
496 return call_user_func_array('\function_exists', func_get_args());
499 function class_exists($class_name)
502 if (isset($phpMock['class_exists'])) {
503 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
504 if ($result !== '__phpunit_continue__') {
508 return call_user_func_array('\class_exists', func_get_args());