Revert "Replace IHTTPResult for CurlResult usages"
[friendica.git/.git] / tests / src / Core / InstallerTest.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
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 // this is in the same namespace as Install for mocking 'function_exists'
23 namespace Friendica\Core;
24
25 use Dice\Dice;
26 use Friendica\Core\Config\Cache;
27 use Friendica\DI;
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;
33
34 class InstallerTest extends MockedTest
35 {
36         use VFSTrait;
37
38         /**
39          * @var \Friendica\Core\L10n|MockInterface
40          */
41         private $l10nMock;
42         /**
43          * @var Dice|MockInterface
44          */
45         private $dice;
46
47         public function setUp()
48         {
49                 parent::setUp();
50
51                 $this->setUpVfsDir();
52
53                 $this->l10nMock = \Mockery::mock(\Friendica\Core\L10n::class);
54
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');
58
59                 $this->dice->shouldReceive('create')
60                            ->with(\Friendica\Core\L10n::class)
61                            ->andReturn($this->l10nMock);
62
63                 DI::init($this->dice);
64         }
65
66         private function mockL10nT(string $text, $times = null)
67         {
68                 $this->l10nMock->shouldReceive('t')->with($text)->andReturn($text)->times($times);
69         }
70
71         /**
72          * Mocking the DI::l10n()->t() calls for the function checks
73          */
74         private function mockFunctionL10TCalls()
75         {
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);
95         }
96
97         private function assertCheckExist($position, $title, $help, $status, $required, $assertionArray)
98         {
99                 $subSet = [$position => [
100                         'title' => $title,
101                         'status' => $status,
102                         'required' => $required,
103                         'error_msg' => null,
104                         'help' => $help]
105                 ];
106
107                 $this->assertArraySubset($subSet, $assertionArray, false, "expected subset: " . PHP_EOL . print_r($subSet, true) . PHP_EOL . "current subset: " . print_r($assertionArray, true));
108         }
109
110         /**
111          * Replaces function_exists results with given mocks
112          *
113          * @param array $functions a list from function names and their result
114          */
115         private function setFunctions($functions)
116         {
117                 global $phpMock;
118                 $phpMock['function_exists'] = function($function) use ($functions) {
119                         foreach ($functions as $name => $value) {
120                                 if ($function == $name) {
121                                         return $value;
122                                 }
123                         }
124                         return '__phpunit_continue__';
125                 };
126         }
127
128         /**
129          * Replaces class_exist results with given mocks
130          *
131          * @param array $classes a list from class names and their results
132          */
133         private function setClasses($classes)
134         {
135                 global $phpMock;
136                 $phpMock['class_exists'] = function($class) use ($classes) {
137                         foreach ($classes as $name => $value) {
138                                 if ($class == $name) {
139                                         return $value;
140                                 }
141                         }
142                         return '__phpunit_continue__';
143                 };
144         }
145
146         /**
147          * @small
148          */
149         public function testCheckKeys()
150         {
151                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
152
153                 $this->setFunctions(['openssl_pkey_new' => false]);
154                 $install = new Installer();
155                 $this->assertFalse($install->checkKeys());
156
157                 $this->setFunctions(['openssl_pkey_new' => true]);
158                 $install = new Installer();
159                 $this->assertTrue($install->checkKeys());
160         }
161
162         /**
163          * @small
164          */
165         public function testCheckFunctions()
166         {
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.',
174                         false,
175                         true,
176                         $install->getChecks());
177
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.',
185                         false,
186                         true,
187                         $install->getChecks());
188
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.',
196                         false,
197                         true,
198                         $install->getChecks());
199
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.',
207                         false,
208                         true,
209                         $install->getChecks());
210
211                 $this->mockFunctionL10TCalls();
212                 $this->setFunctions(['iconv_strlen' => false]);
213                 $install = new Installer();
214                 $this->assertFalse($install->checkFunctions());
215                 $this->assertCheckExist(7,
216                         'iconv PHP module',
217                         'Error: iconv PHP module required but not installed.',
218                         false,
219                         true,
220                         $install->getChecks());
221
222                 $this->mockFunctionL10TCalls();
223                 $this->setFunctions(['posix_kill' => false]);
224                 $install = new Installer();
225                 $this->assertFalse($install->checkFunctions());
226                 $this->assertCheckExist(8,
227                         'POSIX PHP module',
228                         'Error: POSIX PHP module required but not installed.',
229                         false,
230                         true,
231                         $install->getChecks());
232
233                 $this->mockFunctionL10TCalls();
234                 $this->setFunctions(['json_encode' => false]);
235                 $install = new Installer();
236                 $this->assertFalse($install->checkFunctions());
237                 $this->assertCheckExist(9,
238                         'JSON PHP module',
239                         'Error: JSON PHP module required but not installed.',
240                         false,
241                         true,
242                         $install->getChecks());
243
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.',
251                         false,
252                         true,
253                         $install->getChecks());
254
255                 $this->mockFunctionL10TCalls();
256                 $this->setFunctions([
257                         'curl_init' => true,
258                         'imagecreatefromjpeg' => true,
259                         'openssl_public_encrypt' => true,
260                         'mb_strlen' => true,
261                         'iconv_strlen' => true,
262                         'posix_kill' => true,
263                         'json_encode' => true,
264                         'finfo_open' => true,
265                 ]);
266                 $install = new Installer();
267                 $this->assertTrue($install->checkFunctions());
268         }
269
270         /**
271          * @small
272          */
273         public function testCheckLocalIni()
274         {
275                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
276
277                 $this->assertTrue($this->root->hasChild('config/local.config.php'));
278
279                 $install = new Installer();
280                 $this->assertTrue($install->checkLocalIni());
281
282                 $this->delConfigFile('local.config.php');
283
284                 $this->assertFalse($this->root->hasChild('config/local.config.php'));
285
286                 $install = new Installer();
287                 $this->assertTrue($install->checkLocalIni());
288         }
289
290         /**
291          * @small
292          * @runInSeparateProcess
293          * @preserveGlobalState disabled
294          */
295         public function testCheckHtAccessFail()
296         {
297                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
298
299                 // Mocking the CURL Response
300                 $curlResult = \Mockery::mock(CurlResult::class);
301                 $curlResult
302                         ->shouldReceive('getReturnCode')
303                         ->andReturn('404');
304                 $curlResult
305                         ->shouldReceive('getRedirectUrl')
306                         ->andReturn('');
307                 $curlResult
308                         ->shouldReceive('getError')
309                         ->andReturn('test Error');
310
311                 // Mocking the CURL Request
312                 $networkMock = \Mockery::mock(IHTTPRequest::class);
313                 $networkMock
314                         ->shouldReceive('fetchFull')
315                         ->with('https://test/install/testrewrite')
316                         ->andReturn($curlResult);
317                 $networkMock
318                         ->shouldReceive('fetchFull')
319                         ->with('http://test/install/testrewrite')
320                         ->andReturn($curlResult);
321
322                 $this->dice->shouldReceive('create')
323                      ->with(IHTTPRequest::class)
324                      ->andReturn($networkMock);
325
326                 DI::init($this->dice);
327
328                 // Mocking that we can use CURL
329                 $this->setFunctions(['curl_init' => true]);
330
331                 $install = new Installer();
332
333                 $this->assertFalse($install->checkHtAccess('https://test'));
334                 $this->assertSame('test Error', $install->getChecks()[0]['error_msg']['msg']);
335         }
336
337         /**
338          * @small
339          * @runInSeparateProcess
340          * @preserveGlobalState disabled
341          */
342         public function testCheckHtAccessWork()
343         {
344                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
345
346                 // Mocking the failed CURL Response
347                 $curlResultF = \Mockery::mock(CurlResult::class);
348                 $curlResultF
349                         ->shouldReceive('getReturnCode')
350                         ->andReturn('404');
351
352                 // Mocking the working CURL Response
353                 $curlResultW = \Mockery::mock(CurlResult::class);
354                 $curlResultW
355                         ->shouldReceive('getReturnCode')
356                         ->andReturn('204');
357
358                 // Mocking the CURL Request
359                 $networkMock = \Mockery::mock(IHTTPRequest::class);
360                 $networkMock
361                         ->shouldReceive('fetchFull')
362                         ->with('https://test/install/testrewrite')
363                         ->andReturn($curlResultF);
364                 $networkMock
365                         ->shouldReceive('fetchFull')
366                         ->with('http://test/install/testrewrite')
367                         ->andReturn($curlResultW);
368
369                 $this->dice->shouldReceive('create')
370                            ->with(IHTTPRequest::class)
371                            ->andReturn($networkMock);
372
373                 DI::init($this->dice);
374
375                 // Mocking that we can use CURL
376                 $this->setFunctions(['curl_init' => true]);
377
378                 $install = new Installer();
379
380                 $this->assertTrue($install->checkHtAccess('https://test'));
381         }
382
383         /**
384          * @small
385          * @runInSeparateProcess
386          * @preserveGlobalState disabled
387          */
388         public function testImagick()
389         {
390                 $this->markTestIncomplete('needs adapted class_exists() mock');
391
392                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
393
394                 $this->setClasses(['Imagick' => true]);
395
396                 $install = new Installer();
397
398                 // even there is no supported type, Imagick should return true (because it is not required)
399                 $this->assertTrue($install->checkImagick());
400
401                 $this->assertCheckExist(1,
402                         $this->l10nMock->t('ImageMagick supports GIF'),
403                         '',
404                         true,
405                         false,
406                         $install->getChecks());
407         }
408
409         /**
410          * @small
411          * @runInSeparateProcess
412          * @preserveGlobalState disabled
413          */
414         public function testImagickNotFound()
415         {
416                 $this->markTestIncomplete('Disabled due not working/difficult mocking global functions - needs more care!');
417
418                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
419
420                 $this->setClasses(['Imagick' => true]);
421
422                 $install = new Installer();
423
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'),
428                         '',
429                         false,
430                         false,
431                         $install->getChecks());
432         }
433
434         public function testImagickNotInstalled()
435         {
436                 $this->setClasses(['Imagick' => false]);
437                 $this->mockL10nT('ImageMagick PHP extension is not installed');
438
439                 $install = new Installer();
440
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',
445                         '',
446                         false,
447                         false,
448                         $install->getChecks());
449         }
450
451         /**
452          * Test the setup of the config cache for installation
453          */
454         public function testSetUpCache()
455         {
456                 $this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
457
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();
462
463                 $install->setUpCache($configCache, '/test/');
464         }
465 }
466
467 /**
468  * A workaround to replace the PHP native function_exists with a mocked function
469  *
470  * @param string $function_name the Name of the function
471  *
472  * @return bool true or false
473  */
474 function function_exists($function_name)
475 {
476         global $phpMock;
477         if (isset($phpMock['function_exists'])) {
478                 $result = call_user_func_array($phpMock['function_exists'], func_get_args());
479                 if ($result !== '__phpunit_continue__') {
480                         return $result;
481                 }
482         }
483         return call_user_func_array('\function_exists', func_get_args());
484 }
485
486 function class_exists($class_name)
487 {
488         global $phpMock;
489         if (isset($phpMock['class_exists'])) {
490                 $result = call_user_func_array($phpMock['class_exists'], func_get_args());
491                 if ($result !== '__phpunit_continue__') {
492                         return $result;
493                 }
494         }
495         return call_user_func_array('\class_exists', func_get_args());
496 }