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