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 // 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;
32 use Mockery\MockInterface;
34 class InstallerTest extends MockedTest
39 * @var \Friendica\Core\L10n|MockInterface
43 * @var Dice|MockInterface
47 public function setUp()
53 $this->l10nMock = \Mockery::mock(\Friendica\Core\L10n::class);
55 /** @var Dice|MockInterface $dice */
56 $this->dice = \Mockery::mock(Dice::class)->makePartial();
57 $this->dice = $this->dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php');
59 $this->dice->shouldReceive('create')
60 ->with(\Friendica\Core\L10n::class)
61 ->andReturn($this->l10nMock);
63 DI::init($this->dice);
66 private function mockL10nT(string $text, $times = null)
68 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
72 * Mocking the DI::l10n()->t() calls for the function checks
74 private function mockFunctionL10TCalls()
76 $this->mockL10nT('Apache mod_rewrite module', 1);
77 $this->mockL10nT('PDO or MySQLi PHP module', 1);
78 $this->mockL10nT('libCurl PHP module', 1);
79 $this->mockL10nT('Error: libCURL PHP module required but not installed.', 1);
80 $this->mockL10nT('XML PHP module', 1);
81 $this->mockL10nT('GD graphics PHP module', 1);
82 $this->mockL10nT('Error: GD graphics PHP module with JPEG support required but not installed.', 1);
83 $this->mockL10nT('OpenSSL PHP module', 1);
84 $this->mockL10nT('Error: openssl PHP module required but not installed.', 1);
85 $this->mockL10nT('mb_string PHP module', 1);
86 $this->mockL10nT('Error: mb_string PHP module required but not installed.', 1);
87 $this->mockL10nT('iconv PHP module', 1);
88 $this->mockL10nT('Error: iconv PHP module required but not installed.', 1);
89 $this->mockL10nT('POSIX PHP module', 1);
90 $this->mockL10nT('Error: POSIX PHP module required but not installed.', 1);
91 $this->mockL10nT('JSON PHP module', 1);
92 $this->mockL10nT('Error: JSON PHP module required but not installed.', 1);
93 $this->mockL10nT('File Information PHP module', 1);
94 $this->mockL10nT('Error: File Information PHP module required but not installed.', 1);
97 private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
99 $subSet = [$position => [
102 'required' => $required,
107 $this->assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
111 * Replaces function_exists results with given mocks
113 * @param array $functions a list from function names and their result
115 private function setFunctions($functions)
118 $phpMock['function_exists'] = function($function) use ($functions) {
119 foreach ($functions as $name => $value) {
120 if ($function == $name) {
124 return '__phpunit_continue__';
129 * Replaces class_exist results with given mocks
131 * @param array $classes a list from class names and their results
133 private function setClasses($classes)
136 $phpMock['class_exists'] = function($class) use ($classes) {
137 foreach ($classes as $name => $value) {
138 if ($class == $name) {
142 return '__phpunit_continue__';
149 public function testCheckKeys()
151 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
153 $this->setFunctions(['openssl_pkey_new' => false]);
154 $install = new Installer();
155 $this->assertFalse($install->checkKeys());
157 $this->setFunctions(['openssl_pkey_new' => true]);
158 $install = new Installer();
159 $this->assertTrue($install->checkKeys());
165 public function testCheckFunctions()
167 $this->mockFunctionL10TCalls();
168 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
169 $install = new Installer();
170 $this->assertFalse($install->checkFunctions());
171 $this->assertCheckExist(3,
172 'libCurl PHP module',
173 'Error: libCURL PHP module required but not installed.',
176 $install->getChecks());
178 $this->mockFunctionL10TCalls();
179 $this->setFunctions(['imagecreatefromjpeg' => false]);
180 $install = new Installer();
181 $this->assertFalse($install->checkFunctions());
182 $this->assertCheckExist(4,
183 'GD graphics PHP module',
184 'Error: GD graphics PHP module with JPEG support required but not installed.',
187 $install->getChecks());
189 $this->mockFunctionL10TCalls();
190 $this->setFunctions(['openssl_public_encrypt' => false]);
191 $install = new Installer();
192 $this->assertFalse($install->checkFunctions());
193 $this->assertCheckExist(5,
194 'OpenSSL PHP module',
195 'Error: openssl PHP module required but not installed.',
198 $install->getChecks());
200 $this->mockFunctionL10TCalls();
201 $this->setFunctions(['mb_strlen' => false]);
202 $install = new Installer();
203 $this->assertFalse($install->checkFunctions());
204 $this->assertCheckExist(6,
205 'mb_string PHP module',
206 'Error: mb_string PHP module required but not installed.',
209 $install->getChecks());
211 $this->mockFunctionL10TCalls();
212 $this->setFunctions(['iconv_strlen' => false]);
213 $install = new Installer();
214 $this->assertFalse($install->checkFunctions());
215 $this->assertCheckExist(7,
217 'Error: iconv PHP module required but not installed.',
220 $install->getChecks());
222 $this->mockFunctionL10TCalls();
223 $this->setFunctions(['posix_kill' => false]);
224 $install = new Installer();
225 $this->assertFalse($install->checkFunctions());
226 $this->assertCheckExist(8,
228 'Error: POSIX PHP module required but not installed.',
231 $install->getChecks());
233 $this->mockFunctionL10TCalls();
234 $this->setFunctions(['json_encode' => false]);
235 $install = new Installer();
236 $this->assertFalse($install->checkFunctions());
237 $this->assertCheckExist(9,
239 'Error: JSON PHP module required but not installed.',
242 $install->getChecks());
244 $this->mockFunctionL10TCalls();
245 $this->setFunctions(['finfo_open' => false]);
246 $install = new Installer();
247 $this->assertFalse($install->checkFunctions());
248 $this->assertCheckExist(10,
249 'File Information PHP module',
250 'Error: File Information PHP module required but not installed.',
253 $install->getChecks());
255 $this->mockFunctionL10TCalls();
256 $this->setFunctions([
258 'imagecreatefromjpeg' => true,
259 'openssl_public_encrypt' => true,
261 'iconv_strlen' => true,
262 'posix_kill' => true,
263 'json_encode' => true,
264 'finfo_open' => true,
266 $install = new Installer();
267 $this->assertTrue($install->checkFunctions());
273 public function testCheckLocalIni()
275 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
277 $this->assertTrue($this->root->hasChild('config/local.config.php'));
279 $install = new Installer();
280 $this->assertTrue($install->checkLocalIni());
282 $this->delConfigFile('local.config.php');
284 $this->assertFalse($this->root->hasChild('config/local.config.php'));
286 $install = new Installer();
287 $this->assertTrue($install->checkLocalIni());
292 * @runInSeparateProcess
293 * @preserveGlobalState disabled
295 public function testCheckHtAccessFail()
297 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
299 // Mocking the CURL Response
300 $curlResult = \Mockery::mock(CurlResult::class);
302 ->shouldReceive('getReturnCode')
305 ->shouldReceive('getRedirectUrl')
308 ->shouldReceive('getError')
309 ->andReturn('test Error');
311 // Mocking the CURL Request
312 $networkMock = \Mockery::mock(IHTTPRequest::class);
314 ->shouldReceive('fetchFull')
315 ->with('https://test/install/testrewrite')
316 ->andReturn($curlResult);
318 ->shouldReceive('fetchFull')
319 ->with('http://test/install/testrewrite')
320 ->andReturn($curlResult);
322 $this->dice->shouldReceive('create')
323 ->with(IHTTPRequest::class)
324 ->andReturn($networkMock);
326 DI::init($this->dice);
328 // Mocking that we can use CURL
329 $this->setFunctions(['curl_init' => true]);
331 $install = new Installer();
333 $this->assertFalse($install->checkHtAccess('https://test'));
334 $this->assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
339 * @runInSeparateProcess
340 * @preserveGlobalState disabled
342 public function testCheckHtAccessWork()
344 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
346 // Mocking the failed CURL Response
347 $curlResultF = \Mockery::mock(CurlResult::class);
349 ->shouldReceive('getReturnCode')
352 // Mocking the working CURL Response
353 $curlResultW = \Mockery::mock(CurlResult::class);
355 ->shouldReceive('getReturnCode')
358 // Mocking the CURL Request
359 $networkMock = \Mockery::mock(IHTTPRequest::class);
361 ->shouldReceive('fetchFull')
362 ->with('https://test/install/testrewrite')
363 ->andReturn($curlResultF);
365 ->shouldReceive('fetchFull')
366 ->with('http://test/install/testrewrite')
367 ->andReturn($curlResultW);
369 $this->dice->shouldReceive('create')
370 ->with(IHTTPRequest::class)
371 ->andReturn($networkMock);
373 DI::init($this->dice);
375 // Mocking that we can use CURL
376 $this->setFunctions(['curl_init' => true]);
378 $install = new Installer();
380 $this->assertTrue($install->checkHtAccess('https://test'));
385 * @runInSeparateProcess
386 * @preserveGlobalState disabled
388 public function testImagick()
390 $this->markTestIncomplete('needs adapted class_exists() mock');
392 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
394 $this->setClasses(['Imagick' => true]);
396 $install = new Installer();
398 // even there is no supported type, Imagick should return true (because it is not required)
399 $this->assertTrue($install->checkImagick());
401 $this->assertCheckExist(1,
402 $this->l10nMock->t('ImageMagick supports GIF'),
406 $install->getChecks());
411 * @runInSeparateProcess
412 * @preserveGlobalState disabled
414 public function testImagickNotFound()
416 $this->markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
418 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
420 $this->setClasses(['Imagick' => true]);
422 $install = new Installer();
424 // even there is no supported type, Imagick should return true (because it is not required)
425 $this->assertTrue($install->checkImagick());
426 $this->assertCheckExist(1,
427 $this->l10nMock->t('ImageMagick supports GIF'),
431 $install->getChecks());
434 public function testImagickNotInstalled()
436 $this->setClasses(['Imagick' => false]);
437 $this->mockL10nT('ImageMagick PHP extension is not installed');
439 $install = new Installer();
441 // even there is no supported type, Imagick should return true (because it is not required)
442 $this->assertTrue($install->checkImagick());
443 $this->assertCheckExist(0,
444 'ImageMagick PHP extension is not installed',
448 $install->getChecks());
452 * Test the setup of the config cache for installation
454 public function testSetUpCache()
456 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
458 $install = new Installer();
459 $configCache = \Mockery::mock(Cache::class);
460 $configCache->shouldReceive('set')->with('config', 'php_path', \Mockery::any())->once();
461 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
463 $install->setUpCache($configCache, '/test/');
468 * A workaround to replace the PHP native function_exists with a mocked function
470 * @param string $function_name the Name of the function
472 * @return bool true or false
474 function function_exists($function_name)
477 if (isset($phpMock['function_exists'])) {
478 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
479 if ($result !== '__phpunit_continue__') {
483 return call_user_func_array('\function_exists', func_get_args());
486 function class_exists($class_name)
489 if (isset($phpMock['class_exists'])) {
490 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
491 if ($result !== '__phpunit_continue__') {
495 return call_user_func_array('\class_exists', func_get_args());