]> git.mxchange.org Git - friendica.git/blob - tests/src/Core/InstallerTest.php
Add more special chars at tests
[friendica.git] / tests / src / Core / InstallerTest.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
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.
11  *
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.
16  *
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/>.
19  *
20  */
21
22 /// @todo this is in the same namespace as Install for mocking 'function_exists'
23 namespace Friendica\Core;
24
25 use Dice\Dice;
26 use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
27 use Friendica\Core\Config\ValueObject\Cache;
28 use Friendica\DI;
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;
33 use Mockery;
34 use Mockery\MockInterface;
35
36 class InstallerTest extends MockedTest
37 {
38         use VFSTrait;
39         use ArraySubsetAsserts;
40
41         /**
42          * @var L10n|MockInterface
43          */
44         private $l10nMock;
45         /**
46          * @var Dice|MockInterface
47          */
48         private $dice;
49
50         protected function setUp(): void
51         {
52                 parent::setUp();
53
54                 $this->setUpVfsDir();
55
56                 $this->l10nMock = Mockery::mock(L10n::class);
57
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');
61
62                 $this->dice->shouldReceive('create')
63                            ->with(L10n::class)
64                            ->andReturn($this->l10nMock);
65
66                 DI::init($this->dice);
67         }
68
69         public static function tearDownAfterClass(): void
70         {
71                 // Reset mocking
72                 global $phpMock;
73                 $phpMock = [];
74
75                 parent::tearDownAfterClass();
76         }
77
78         private function mockL10nT(string $text, $times = null)
79         {
80                 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
81         }
82
83         /**
84          * Mocking the DI::l10n()->t() calls for the function checks
85          */
86         private function mockFunctionL10TCalls()
87         {
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('GNU Multiple Precision PHP module', 1);
108                 $this->mockL10nT('Error: GNU Multiple Precision PHP module required but not installed.', 1);
109                 $this->mockL10nT('Program execution functions', 1);
110                 $this->mockL10nT('Error: Program execution functions (proc_open) required but not enabled.', 1);
111         }
112
113         private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
114         {
115                 $subSet = [$position => [
116                         'title' => $title,
117                         'status' => $status,
118                         'required' => $required,
119                         'error_msg' => null,
120                         'help' => $help]
121                 ];
122
123                 self::assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
124         }
125
126         /**
127          * Replaces function_exists results with given mocks
128          *
129          * @param array $functions a list from function names and their result
130          */
131         private function setFunctions(array $functions)
132         {
133                 global $phpMock;
134                 $phpMock['function_exists'] = function($function) use ($functions) {
135                         foreach ($functions as $name => $value) {
136                                 if ($function == $name) {
137                                         return $value;
138                                 }
139                         }
140                         return '__phpunit_continue__';
141                 };
142         }
143
144         /**
145          * Replaces class_exist results with given mocks
146          *
147          * @param array $classes a list from class names and their results
148          */
149         private function setClasses(array $classes)
150         {
151                 global $phpMock;
152                 $phpMock['class_exists'] = function($class) use ($classes) {
153                         foreach ($classes as $name => $value) {
154                                 if ($class == $name) {
155                                         return $value;
156                                 }
157                         }
158                         return '__phpunit_continue__';
159                 };
160         }
161
162         /**
163          * @small
164          */
165         public function testCheckKeys()
166         {
167                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
168
169                 $this->setFunctions(['openssl_pkey_new' => false]);
170                 $install = new Installer();
171                 self::assertFalse($install->checkKeys());
172
173                 $this->setFunctions(['openssl_pkey_new' => true]);
174                 $install = new Installer();
175                 self::assertTrue($install->checkKeys());
176         }
177
178         /**
179          * @small
180          */
181         public function testCheckFunctions()
182         {
183                 $this->mockFunctionL10TCalls();
184                 $this->setFunctions(['curl_init' => false, 'imagecreatefromjpeg' => true]);
185                 $install = new Installer();
186                 self::assertFalse($install->checkFunctions());
187                 self::assertCheckExist(3,
188                         'libCurl PHP module',
189                         'Error: libCURL PHP module required but not installed.',
190                         false,
191                         true,
192                         $install->getChecks());
193
194                 $this->mockFunctionL10TCalls();
195                 $this->setFunctions(['imagecreatefromjpeg' => false]);
196                 $install = new Installer();
197                 self::assertFalse($install->checkFunctions());
198                 self::assertCheckExist(4,
199                         'GD graphics PHP module',
200                         'Error: GD graphics PHP module with JPEG support required but not installed.',
201                         false,
202                         true,
203                         $install->getChecks());
204
205                 $this->mockFunctionL10TCalls();
206                 $this->setFunctions(['openssl_public_encrypt' => false]);
207                 $install = new Installer();
208                 self::assertFalse($install->checkFunctions());
209                 self::assertCheckExist(5,
210                         'OpenSSL PHP module',
211                         'Error: openssl PHP module required but not installed.',
212                         false,
213                         true,
214                         $install->getChecks());
215
216                 $this->mockFunctionL10TCalls();
217                 $this->setFunctions(['mb_strlen' => false]);
218                 $install = new Installer();
219                 self::assertFalse($install->checkFunctions());
220                 self::assertCheckExist(6,
221                         'mb_string PHP module',
222                         'Error: mb_string PHP module required but not installed.',
223                         false,
224                         true,
225                         $install->getChecks());
226
227                 $this->mockFunctionL10TCalls();
228                 $this->setFunctions(['iconv_strlen' => false]);
229                 $install = new Installer();
230                 self::assertFalse($install->checkFunctions());
231                 self::assertCheckExist(7,
232                         'iconv PHP module',
233                         'Error: iconv PHP module required but not installed.',
234                         false,
235                         true,
236                         $install->getChecks());
237
238                 $this->mockFunctionL10TCalls();
239                 $this->setFunctions(['posix_kill' => false]);
240                 $install = new Installer();
241                 self::assertFalse($install->checkFunctions());
242                 self::assertCheckExist(8,
243                         'POSIX PHP module',
244                         'Error: POSIX PHP module required but not installed.',
245                         false,
246                         true,
247                         $install->getChecks());
248
249                 $this->mockFunctionL10TCalls();
250                 $this->setFunctions(['proc_open' => false]);
251                 $install = new Installer();
252                 self::assertFalse($install->checkFunctions());
253                 self::assertCheckExist(9,
254                         'Program execution functions',
255                         'Error: Program execution functions (proc_open) required but not enabled.',
256                         false,
257                         true,
258                         $install->getChecks());
259                 $this->mockFunctionL10TCalls();
260                 $this->setFunctions(['json_encode' => false]);
261                 $install = new Installer();
262                 self::assertFalse($install->checkFunctions());
263                 self::assertCheckExist(10,
264                         'JSON PHP module',
265                         'Error: JSON PHP module required but not installed.',
266                         false,
267                         true,
268                         $install->getChecks());
269
270                 $this->mockFunctionL10TCalls();
271                 $this->setFunctions(['finfo_open' => false]);
272                 $install = new Installer();
273                 self::assertFalse($install->checkFunctions());
274                 self::assertCheckExist(11,
275                         'File Information PHP module',
276                         'Error: File Information PHP module required but not installed.',
277                         false,
278                         true,
279                         $install->getChecks());
280
281                 $this->mockFunctionL10TCalls();
282                 $this->setFunctions(['gmp_strval' => false]);
283                 $install = new Installer();
284                 self::assertFalse($install->checkFunctions());
285                 self::assertCheckExist(12,
286                         'GNU Multiple Precision PHP module',
287                         'Error: GNU Multiple Precision PHP module required but not installed.',
288                 false,
289                         true,
290                         $install->getChecks());
291
292                 $this->mockFunctionL10TCalls();
293                 $this->setFunctions([
294                         'curl_init' => true,
295                         'imagecreatefromjpeg' => true,
296                         'openssl_public_encrypt' => true,
297                         'mb_strlen' => true,
298                         'iconv_strlen' => true,
299                         'posix_kill' => true,
300                         'json_encode' => true,
301                         'finfo_open' => true,
302                         'gmp_strval' => true,
303                 ]);
304                 $install = new Installer();
305                 self::assertTrue($install->checkFunctions());
306         }
307
308         /**
309          * @small
310          */
311         public function testCheckLocalIni()
312         {
313                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
314
315                 self::assertTrue($this->root->hasChild('config/local.config.php'));
316
317                 $install = new Installer();
318                 self::assertTrue($install->checkLocalIni());
319
320                 $this->delConfigFile('local.config.php');
321
322                 self::assertFalse($this->root->hasChild('config/local.config.php'));
323
324                 $install = new Installer();
325                 self::assertTrue($install->checkLocalIni());
326         }
327
328         /**
329          * @small
330          * @runInSeparateProcess
331          * @preserveGlobalState disabled
332          */
333         public function testCheckHtAccessFail()
334         {
335                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
336
337                 // Mocking the CURL Response
338                 $IHTTPResult = Mockery::mock(ICanHandleHttpResponses::class);
339                 $IHTTPResult
340                         ->shouldReceive('getReturnCode')
341                         ->andReturn('404');
342                 $IHTTPResult
343                         ->shouldReceive('getRedirectUrl')
344                         ->andReturn('');
345                 $IHTTPResult
346                         ->shouldReceive('getError')
347                         ->andReturn('test Error');
348
349                 // Mocking the CURL Request
350                 $networkMock = Mockery::mock(ICanSendHttpRequests::class);
351                 $networkMock
352                         ->shouldReceive('fetchFull')
353                         ->with('https://test/install/testrewrite')
354                         ->andReturn($IHTTPResult);
355                 $networkMock
356                         ->shouldReceive('fetchFull')
357                         ->with('http://test/install/testrewrite')
358                         ->andReturn($IHTTPResult);
359
360                 $this->dice->shouldReceive('create')
361                      ->with(ICanSendHttpRequests::class)
362                      ->andReturn($networkMock);
363
364                 DI::init($this->dice);
365
366                 // Mocking that we can use CURL
367                 $this->setFunctions(['curl_init' => true]);
368
369                 $install = new Installer();
370
371                 self::assertFalse($install->checkHtAccess('https://test'));
372                 self::assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
373         }
374
375         /**
376          * @small
377          * @runInSeparateProcess
378          * @preserveGlobalState disabled
379          */
380         public function testCheckHtAccessWork()
381         {
382                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
383
384                 // Mocking the failed CURL Response
385                 $IHTTPResultF = Mockery::mock(ICanHandleHttpResponses::class);
386                 $IHTTPResultF
387                         ->shouldReceive('getReturnCode')
388                         ->andReturn('404');
389
390                 // Mocking the working CURL Response
391                 $IHTTPResultW = Mockery::mock(ICanHandleHttpResponses::class);
392                 $IHTTPResultW
393                         ->shouldReceive('getReturnCode')
394                         ->andReturn('204');
395
396                 // Mocking the CURL Request
397                 $networkMock = Mockery::mock(ICanSendHttpRequests::class);
398                 $networkMock
399                         ->shouldReceive('fetchFull')
400                         ->with('https://test/install/testrewrite')
401                         ->andReturn($IHTTPResultF);
402                 $networkMock
403                         ->shouldReceive('fetchFull')
404                         ->with('http://test/install/testrewrite')
405                         ->andReturn($IHTTPResultW);
406
407                 $this->dice->shouldReceive('create')
408                            ->with(ICanSendHttpRequests::class)
409                            ->andReturn($networkMock);
410
411                 DI::init($this->dice);
412
413                 // Mocking that we can use CURL
414                 $this->setFunctions(['curl_init' => true]);
415
416                 $install = new Installer();
417
418                 self::assertTrue($install->checkHtAccess('https://test'));
419         }
420
421         /**
422          * @small
423          * @runInSeparateProcess
424          * @preserveGlobalState disabled
425          */
426         public function testImagick()
427         {
428                 static::markTestIncomplete('needs adapted class_exists() mock');
429
430                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
431
432                 $this->setClasses(['Imagick' => true]);
433
434                 $install = new Installer();
435
436                 // even there is no supported type, Imagick should return true (because it is not required)
437                 self::assertTrue($install->checkImagick());
438
439                 self::assertCheckExist(1,
440                         $this->l10nMock->t('ImageMagick supports GIF'),
441                         '',
442                         true,
443                         false,
444                         $install->getChecks());
445         }
446
447         /**
448          * @small
449          * @runInSeparateProcess
450          * @preserveGlobalState disabled
451          */
452         public function testImagickNotFound()
453         {
454                 static::markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
455
456                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
457
458                 $this->setClasses(['Imagick' => true]);
459
460                 $install = new Installer();
461
462                 // even there is no supported type, Imagick should return true (because it is not required)
463                 self::assertTrue($install->checkImagick());
464                 self::assertCheckExist(1,
465                         $this->l10nMock->t('ImageMagick supports GIF'),
466                         '',
467                         false,
468                         false,
469                         $install->getChecks());
470         }
471
472         public function testImagickNotInstalled()
473         {
474                 $this->setClasses(['Imagick' => false]);
475                 $this->mockL10nT('ImageMagick PHP extension is not installed');
476
477                 $install = new Installer();
478
479                 // even there is no supported type, Imagick should return true (because it is not required)
480                 self::assertTrue($install->checkImagick());
481                 self::assertCheckExist(0,
482                         'ImageMagick PHP extension is not installed',
483                         '',
484                         false,
485                         false,
486                         $install->getChecks());
487         }
488
489         /**
490          * Test the setup of the config cache for installation
491          * @doesNotPerformAssertions
492          */
493         public function testSetUpCache()
494         {
495                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
496
497                 $install = new Installer();
498                 $configCache = Mockery::mock(Cache::class);
499                 $configCache->shouldReceive('set')->with('config', 'php_path', Mockery::any())->once();
500                 $configCache->shouldReceive('set')->with('system', 'basepath', '/test/')->once();
501
502                 $install->setUpCache($configCache, '/test/');
503         }
504 }
505
506 /**
507  * A workaround to replace the PHP native function_exists with a mocked function
508  *
509  * @param string $function_name the Name of the function
510  *
511  * @return bool true or false
512  */
513 function function_exists(string $function_name)
514 {
515         global $phpMock;
516         if (isset($phpMock['function_exists'])) {
517                 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
518                 if ($result !== '__phpunit_continue__') {
519                         return $result;
520                 }
521         }
522         return call_user_func_array('\function_exists', func_get_args());
523 }
524
525 function class_exists($class_name)
526 {
527         global $phpMock;
528         if (isset($phpMock['class_exists'])) {
529                 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
530                 if ($result !== '__phpunit_continue__') {
531                         return $result;
532                 }
533         }
534         return call_user_func_array('\class_exists', func_get_args());
535 }