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\Test\MockedTest;
30 use Friendica\Test\Util\VFSTrait;
31 use Friendica\Util\Network;
32 use Mockery\MockInterface;
34 class InstallerTest extends MockedTest
39 * @var \Friendica\Core\L10n|MockInterface
43 public function setUp()
49 $this->l10nMock = \Mockery::mock(\Friendica\Core\L10n::class);
51 /** @var Dice|MockInterface $dice */
52 $dice = \Mockery::mock(Dice::class)->makePartial();
53 $dice = $dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php');
55 $dice->shouldReceive('create')
56 ->with(\Friendica\Core\L10n::class)
57 ->andReturn($this->l10nMock);
62 private function mockL10nT(string $text, $times = null)
64 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
68 * Mocking the DI::l10n()->t() calls for the function checks
70 private function mockFunctionL10TCalls()
72 $this->mockL10nT('Apache mod_rewrite module', 1);
73 $this->mockL10nT('PDO or MySQLi PHP module', 1);
74 $this->mockL10nT('libCurl PHP module', 1);
75 $this->mockL10nT('Error: libCURL PHP module required but not installed.', 1);
76 $this->mockL10nT('XML PHP module', 1);
77 $this->mockL10nT('GD graphics PHP module', 1);
78 $this->mockL10nT('Error: GD graphics PHP module with JPEG support required but not installed.', 1);
79 $this->mockL10nT('OpenSSL PHP module', 1);
80 $this->mockL10nT('Error: openssl PHP module required but not installed.', 1);
81 $this->mockL10nT('mb_string PHP module', 1);
82 $this->mockL10nT('Error: mb_string PHP module required but not installed.', 1);
83 $this->mockL10nT('iconv PHP module', 1);
84 $this->mockL10nT('Error: iconv PHP module required but not installed.', 1);
85 $this->mockL10nT('POSIX PHP module', 1);
86 $this->mockL10nT('Error: POSIX PHP module required but not installed.', 1);
87 $this->mockL10nT('JSON PHP module', 1);
88 $this->mockL10nT('Error: JSON PHP module required but not installed.', 1);
89 $this->mockL10nT('File Information PHP module', 1);
90 $this->mockL10nT('Error: File Information PHP module required but not installed.', 1);
93 private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
95 $subSet = [$position => [
98 'required' => $required,
103 $this->assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
107 * Replaces function_exists results with given mocks
109 * @param array $functions a list from function names and their result
111 private function setFunctions($functions)
114 $phpMock['function_exists'] = function($function) use ($functions) {
115 foreach ($functions as $name => $value) {
116 if ($function == $name) {
120 return '__phpunit_continue__';
125 * Replaces class_exist results with given mocks
127 * @param array $classes a list from class names and their results
129 private function setClasses($classes)
132 $phpMock['class_exists'] = function($class) use ($classes) {
133 foreach ($classes as $name => $value) {
134 if ($class == $name) {
138 return '__phpunit_continue__';
145 public function testCheckKeys()
147 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
149 $this->setFunctions(['openssl_pkey_new' => false]);
150 $install = new Installer();
151 $this->assertFalse($install->checkKeys());
153 $this->setFunctions(['openssl_pkey_new' => true]);
154 $install = new Installer();
155 $this->assertTrue($install->checkKeys());
161 public function testCheckFunctions()
163 $this->mockFunctionL10TCalls();
164 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
165 $install = new Installer();
166 $this->assertFalse($install->checkFunctions());
167 $this->assertCheckExist(3,
168 'libCurl PHP module',
169 'Error: libCURL PHP module required but not installed.',
172 $install->getChecks());
174 $this->mockFunctionL10TCalls();
175 $this->setFunctions(['imagecreatefromjpeg' => false]);
176 $install = new Installer();
177 $this->assertFalse($install->checkFunctions());
178 $this->assertCheckExist(4,
179 'GD graphics PHP module',
180 'Error: GD graphics PHP module with JPEG support required but not installed.',
183 $install->getChecks());
185 $this->mockFunctionL10TCalls();
186 $this->setFunctions(['openssl_public_encrypt' => false]);
187 $install = new Installer();
188 $this->assertFalse($install->checkFunctions());
189 $this->assertCheckExist(5,
190 'OpenSSL PHP module',
191 'Error: openssl PHP module required but not installed.',
194 $install->getChecks());
196 $this->mockFunctionL10TCalls();
197 $this->setFunctions(['mb_strlen' => false]);
198 $install = new Installer();
199 $this->assertFalse($install->checkFunctions());
200 $this->assertCheckExist(6,
201 'mb_string PHP module',
202 'Error: mb_string PHP module required but not installed.',
205 $install->getChecks());
207 $this->mockFunctionL10TCalls();
208 $this->setFunctions(['iconv_strlen' => false]);
209 $install = new Installer();
210 $this->assertFalse($install->checkFunctions());
211 $this->assertCheckExist(7,
213 'Error: iconv PHP module required but not installed.',
216 $install->getChecks());
218 $this->mockFunctionL10TCalls();
219 $this->setFunctions(['posix_kill' => false]);
220 $install = new Installer();
221 $this->assertFalse($install->checkFunctions());
222 $this->assertCheckExist(8,
224 'Error: POSIX PHP module required but not installed.',
227 $install->getChecks());
229 $this->mockFunctionL10TCalls();
230 $this->setFunctions(['json_encode' => false]);
231 $install = new Installer();
232 $this->assertFalse($install->checkFunctions());
233 $this->assertCheckExist(9,
235 'Error: JSON PHP module required but not installed.',
238 $install->getChecks());
240 $this->mockFunctionL10TCalls();
241 $this->setFunctions(['finfo_open' => false]);
242 $install = new Installer();
243 $this->assertFalse($install->checkFunctions());
244 $this->assertCheckExist(10,
245 'File Information PHP module',
246 'Error: File Information PHP module required but not installed.',
249 $install->getChecks());
251 $this->mockFunctionL10TCalls();
252 $this->setFunctions([
254 'imagecreatefromjpeg' => true,
255 'openssl_public_encrypt' => true,
257 'iconv_strlen' => true,
258 'posix_kill' => true,
259 'json_encode' => true,
260 'finfo_open' => true,
262 $install = new Installer();
263 $this->assertTrue($install->checkFunctions());
269 public function testCheckLocalIni()
271 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
273 $this->assertTrue($this->root->hasChild('config/local.config.php'));
275 $install = new Installer();
276 $this->assertTrue($install->checkLocalIni());
278 $this->delConfigFile('local.config.php');
280 $this->assertFalse($this->root->hasChild('config/local.config.php'));
282 $install = new Installer();
283 $this->assertTrue($install->checkLocalIni());
288 * @runInSeparateProcess
289 * @preserveGlobalState disabled
291 public function testCheckHtAccessFail()
293 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
295 // Mocking the CURL Response
296 $curlResult = \Mockery::mock(CurlResult::class);
298 ->shouldReceive('getReturnCode')
301 ->shouldReceive('getRedirectUrl')
304 ->shouldReceive('getError')
305 ->andReturn('test Error');
307 // Mocking the CURL Request
308 $networkMock = \Mockery::mock('alias:' . Network::class);
310 ->shouldReceive('fetchUrlFull')
311 ->with('https://test/install/testrewrite')
312 ->andReturn($curlResult);
314 ->shouldReceive('fetchUrlFull')
315 ->with('http://test/install/testrewrite')
316 ->andReturn($curlResult);
318 // Mocking that we can use CURL
319 $this->setFunctions(['curl_init' => true]);
321 $install = new Installer();
323 $this->assertFalse($install->checkHtAccess('https://test'));
324 $this->assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
329 * @runInSeparateProcess
330 * @preserveGlobalState disabled
332 public function testCheckHtAccessWork()
334 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
336 // Mocking the failed CURL Response
337 $curlResultF = \Mockery::mock(CurlResult::class);
339 ->shouldReceive('getReturnCode')
342 // Mocking the working CURL Response
343 $curlResultW = \Mockery::mock(CurlResult::class);
345 ->shouldReceive('getReturnCode')
348 // Mocking the CURL Request
349 $networkMock = \Mockery::mock('alias:' . Network::class);
351 ->shouldReceive('fetchUrlFull')
352 ->with('https://test/install/testrewrite')
353 ->andReturn($curlResultF);
355 ->shouldReceive('fetchUrlFull')
356 ->with('http://test/install/testrewrite')
357 ->andReturn($curlResultW);
359 // Mocking that we can use CURL
360 $this->setFunctions(['curl_init' => true]);
362 $install = new Installer();
364 $this->assertTrue($install->checkHtAccess('https://test'));
369 * @runInSeparateProcess
370 * @preserveGlobalState disabled
372 public function testImagick()
374 $this->markTestIncomplete('needs adapted class_exists() mock');
376 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
378 $this->setClasses(['Imagick' => true]);
380 $install = new Installer();
382 // even there is no supported type, Imagick should return true (because it is not required)
383 $this->assertTrue($install->checkImagick());
385 $this->assertCheckExist(1,
386 $this->l10nMock->t('ImageMagick supports GIF'),
390 $install->getChecks());
395 * @runInSeparateProcess
396 * @preserveGlobalState disabled
398 public function testImagickNotFound()
400 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
402 $this->setClasses(['Imagick' => true]);
404 $install = new Installer();
406 // even there is no supported type, Imagick should return true (because it is not required)
407 $this->assertTrue($install->checkImagick());
408 $this->assertCheckExist(1,
409 $this->l10nMock->t('ImageMagick supports GIF'),
413 $install->getChecks());
416 public function testImagickNotInstalled()
418 $this->setClasses(['Imagick' => false]);
419 $this->mockL10nT('ImageMagick PHP extension is not installed');
421 $install = new Installer();
423 // even there is no supported type, Imagick should return true (because it is not required)
424 $this->assertTrue($install->checkImagick());
425 $this->assertCheckExist(0,
426 'ImageMagick PHP extension is not installed',
430 $install->getChecks());
434 * Test the setup of the config cache for installation
436 public function testSetUpCache()
438 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
440 $install = new Installer();
441 $configCache = \Mockery::mock(Cache::class);
442 $configCache->shouldReceive('set')->with('config', 'php_path', \Mockery::any())->once();
443 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
445 $install->setUpCache($configCache, '/test/');
450 * A workaround to replace the PHP native function_exists with a mocked function
452 * @param string $function_name the Name of the function
454 * @return bool true or false
456 function function_exists($function_name)
459 if (isset($phpMock['function_exists'])) {
460 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
461 if ($result !== '__phpunit_continue__') {
465 return call_user_func_array('\function_exists', func_get_args());
468 function class_exists($class_name)
471 if (isset($phpMock['class_exists'])) {
472 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
473 if ($result !== '__phpunit_continue__') {
477 return call_user_func_array('\class_exists', func_get_args());